shouldReceive('fetchPostsSince')->andReturn(collect()); $this->app->bind(FediverseClientInterface::class, fn () => $stub); } public function test_it_exits_zero_when_there_are_no_enabled_instances(): void { $this->artisan('fedi-discover:poll') ->assertExitCode(0); } public function test_it_calls_the_action_for_each_enabled_instance_and_skips_disabled(): void { $enabled1 = Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://mastodon.social', 'enabled' => true, 'interval_seconds' => 600, ]); $enabled2 = Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://fosstodon.org', 'enabled' => true, 'interval_seconds' => 600, ]); Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://disabled.example', 'enabled' => false, 'interval_seconds' => 600, ]); $calledWith = []; $action = Mockery::mock(PollFediverseAction::class); $action->shouldReceive('execute') ->twice() ->withArgs(function (Instance $instance) use (&$calledWith): bool { $calledWith[] = $instance->url; return true; }); $this->app->instance(PollFediverseAction::class, $action); $this->artisan('fedi-discover:poll')->assertExitCode(0); $this->assertEqualsCanonicalizing( [$enabled1->url, $enabled2->url], $calledWith, ); } public function test_one_instance_throwing_does_not_stop_remaining_instances_from_being_polled(): void { Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://failing.example', 'enabled' => true, 'interval_seconds' => 600, ]); $healthy = Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://healthy.example', 'enabled' => true, 'interval_seconds' => 600, ]); $calledWith = []; $action = Mockery::mock(PollFediverseAction::class); $action->shouldReceive('execute') ->twice() ->andReturnUsing(function (Instance $instance) use (&$calledWith): void { $calledWith[] = $instance->url; if ($instance->url === 'https://failing.example') { throw new RuntimeException('Connection refused'); } }); $this->app->instance(PollFediverseAction::class, $action); $this->artisan('fedi-discover:poll')->assertExitCode(1); $this->assertEqualsCanonicalizing( ['https://failing.example', $healthy->url], $calledWith, ); } public function test_it_exits_one_when_at_least_one_instance_fails(): void { Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://failing.example', 'enabled' => true, 'interval_seconds' => 600, ]); Instance::create([ 'type' => InstanceType::Mastodon, 'url' => 'https://healthy.example', 'enabled' => true, 'interval_seconds' => 600, ]); $action = Mockery::mock(PollFediverseAction::class); $action->shouldReceive('execute') ->twice() ->andReturnUsing(function (Instance $instance): void { if ($instance->url === 'https://failing.example') { throw new RuntimeException('Connection refused'); } }); $this->app->instance(PollFediverseAction::class, $action); $this->artisan('fedi-discover:poll')->assertExitCode(1); } }