admin = User::factory()->create(); // Settings survive the per-test rollback, so nothing here may assume // a default — see the note in CaptchaSettingsTest. $settings = app(Settings::class); $settings->set(Setting::CheckForUpdates, true); $settings->set(Setting::FetchNews, true); config()->set('projectsend.edition', Edition::Community); }); /* |-------------------------------------------------------------------------- | Two daily calls out of the container, and who may stop them |-------------------------------------------------------------------------- | | An operator could stop neither. The news feed had no switch of any kind, | and the update check had one whose default is on — so a managed fleet | believed it had disabled updates through an environment variable that | nothing in this application reads. | | They are not the same case, and are not fixed the same way. Which | mechanism each gets is the point of these tests. */ test('the news feed can be switched off, and says so rather than failing', function () { Http::fake(); app(Settings::class)->set(Setting::FetchNews, false); $this->artisan('projectsend:fetch-news') ->expectsOutputToContain('switched off') ->assertSuccessful(); // Not merely "no items stored" — the request never left. Http::assertNothingSent(); }); test('the news feed is on by default, so nothing changes for an existing install', function () { Http::fake(['*' => Http::response([])]); $this->artisan('projectsend:fetch-news')->assertSuccessful(); Http::assertSentCount(1); }); // The news is informational and both editions show it: a Cloud client // with view_news sees that card today (DashboardController), so this must // NOT be gated on a capability. Switching it off is an operator's choice, // not an edition's. test('the news feed is not an edition difference', function () { Http::fake(['*' => Http::response([])]); config()->set('projectsend.edition', Edition::Cloud); $this->artisan('projectsend:fetch-news')->assertSuccessful(); Http::assertSentCount(1); }); /* |-------------------------------------------------------------------------- | The update check is the other kind |-------------------------------------------------------------------------- | | On a managed installation the result is unreachable rather than | unwanted: the dashboard's System card and the update UI are both gated | on Capability::SystemUpdates, which is Community-only, and the image is | chosen by whoever provisioned the instance. That is a fact about the | edition, not a preference — so it is a capability, not a Setting. */ test('the update check does not run where its answer could never be seen', function () { Http::fake(); config()->set('projectsend.edition', Edition::Cloud); // On, and it still must not call out: the capability decides first. app(Settings::class)->set(Setting::CheckForUpdates, true); $this->artisan('projectsend:check-for-updates') ->expectsOutputToContain('do not apply') ->assertSuccessful(); Http::assertNothingSent(); }); test('a self-hosted install keeps its own switch, both ways', function () { Http::fake(['*' => Http::response([])]); app(Settings::class)->set(Setting::CheckForUpdates, false); $this->artisan('projectsend:check-for-updates') ->expectsOutputToContain('disabled') ->assertSuccessful(); Http::assertNothingSent(); app(Settings::class)->set(Setting::CheckForUpdates, true); $this->artisan('projectsend:check-for-updates')->assertSuccessful(); Http::assertSentCount(1); }); /* |-------------------------------------------------------------------------- | Reachable without a shell |-------------------------------------------------------------------------- | | A setting an operator cannot find is not a switch, it is a row. The | update toggle beside it is hidden where the capability is absent; this | one must not be, because the card it controls is shown in both editions. */ test('the news switch is on the settings page in both editions', function () { foreach ([Edition::Community, Edition::Cloud] as $edition) { config()->set('projectsend.edition', $edition); $this->actingAs($this->admin)->get('/system/settings/general')->assertInertia( fn (Inertia\Testing\AssertableInertia $page) => $page->where('fetch_news', true), ); } }); test('saving the settings page can turn the feed off and on', function () { Http::fake(); $this->actingAs($this->admin) ->patch('/system/settings/general', generalPayload(['fetch_news' => false])) ->assertRedirect(); expect(app(Settings::class)->get(Setting::FetchNews))->toBeFalse(); $this->artisan('projectsend:fetch-news')->assertSuccessful(); Http::assertNothingSent(); $this->actingAs($this->admin) ->patch('/system/settings/general', generalPayload(['fetch_news' => true])) ->assertRedirect(); expect(app(Settings::class)->get(Setting::FetchNews))->toBeTrue(); }); /** The general form posts every field it owns; only the interesting one varies. */ function generalPayload(array $overrides = []): array { return array_merge([ 'site_name' => 'ProjectSend', 'timezone' => 'UTC', ], $overrides); }