dpl/tests/Feature/SiteProfile/SiteProfileTest.php

200 lines
8.8 KiB
PHP

<?php
namespace Tests\Feature\SiteProfile;
use App\Providers\SettingServiceProvider;
use App\Support\Setting;
use App\Support\SiteProfile;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Bootstrap\RegisterProviders;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
class SiteProfileTest extends TestCase
{
private array $environmentSnapshot;
public function createApplication()
{
$app = require dirname(__DIR__, 3).'/bootstrap/app.php';
$app->beforeBootstrapping(RegisterProviders::class, function ($app) {
$app['config']->set(['database.default'=>'sqlite', 'database.connections.sqlite.database'=>':memory:',
'cache.default'=>'array', 'cache.stores.redis'=>['driver'=>'array'], 'queue.default'=>'sync',
'app.key'=>'base64:'.base64_encode(str_repeat('t',32)), 'site.profile'=>'dpl']);
});
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
protected function setUp(): void
{
parent::setUp();
$this->environmentSnapshot = [$_ENV, $_SERVER];
Schema::create('v2_plugins', fn (Blueprint $t) => [$t->id(), $t->boolean('is_enabled')->default(false)]);
Schema::create('v2_settings', fn (Blueprint $t) => [$t->id(),$t->string('name'),$t->text('value')->nullable(),$t->timestamps()]);
Cache::store('redis')->flush();
$this->app->forgetInstance(Setting::class);
}
protected function tearDown(): void
{
[$_ENV, $_SERVER] = $this->environmentSnapshot;
parent::tearDown();
}
public function test_dpl_can_render_its_existing_bundled_frontend(): void
{
admin_setting(['frontend_theme'=>'Xboard']);
$this->get('/')->assertOk()->assertSee('/theme/Xboard', false);
}
public function test_public_hostname_never_loads_local_dev_scripts(): void
{
try {
$assets = \App\Support\EzAssets::scripts(\Illuminate\Http\Request::create('https://hardnerd.net/'));
foreach ($assets['scripts'] as $script) $this->assertStringStartsWith('/theme/EZ/assets/', $script);
} catch (\RuntimeException $e) {
$this->assertStringContainsString('EZ assets missing', $e->getMessage());
}
}
public function test_default_dpl_keeps_database_brand_and_theme(): void
{
admin_setting(['frontend_theme'=>'Stellar','app_name'=>'Deep Link','logo'=>'/dpl.svg','app_description'=>'original']);
$before = DB::table('v2_settings')->get()->toJson();
$this->assertSame([], SiteProfile::publicSettings());
$this->assertSame('Stellar',admin_setting('frontend_theme'));
SiteProfile::assertSettingsWritable(['frontend_theme'=>'Xboard']);
$this->assertSame($before, DB::table('v2_settings')->get()->toJson());
}
public function test_hn_uses_ez_without_rewriting_stored_settings_or_cached_values(): void
{
admin_setting(['frontend_theme'=>'Stellar','app_name'=>'Deep Link','logo'=>'/dpl.svg','app_description'=>'original']);
$before = DB::table('v2_settings')->get()->toJson();
config(['site.profile'=>'hn']);
$this->app->forgetInstance(Setting::class);
(new SettingServiceProvider($this->app))->boot();
$this->assertSame('EZ',admin_setting('frontend_theme'));
$this->assertSame('hardnerd',admin_setting('app_name'));
$this->assertSame('hardnerd',config('app.name'));
$this->assertSame('hardnerd.net',config('app.full_name'));
$this->assertSame('/brand/hn/logo.svg',admin_setting('logo'));
$this->assertSame('Stellar', Cache::store('redis')->get(Setting::CACHE_KEY)['frontend_theme']);
$this->assertSame($before, DB::table('v2_settings')->get()->toJson());
$this->withoutExceptionHandling();
$this->getJson('/api/v1/guest/comm/config')->assertOk()->assertJsonPath('data.app_name','hardnerd');
config(['site.profile'=>'dpl']);
$this->app->forgetInstance(Setting::class);
$this->assertSame('Stellar',admin_setting('frontend_theme'));
}
public function test_hn_rejects_an_admin_theme_switch_before_writing(): void
{
config(['site.profile'=>'hn']);
$this->expectException(ValidationException::class);
(new \App\Services\ThemeService)->switch('Xboard');
}
public function test_admin_save_rejects_profile_changes_before_any_setting_write(): void
{
config(['site.profile'=>'hn']);
admin_setting(['currency'=>'CNY']);
$request = \Mockery::mock(\App\Http\Requests\Admin\ConfigSave::class);
$request->shouldReceive('validated')->once()->andReturn(['currency'=>'USD','frontend_theme'=>'Stellar']);
try {
(new \App\Http\Controllers\V2\Admin\ConfigController)->save($request);
$this->fail('hn must reject another profile theme');
} catch (ValidationException $exception) {
$this->assertArrayHasKey('frontend_theme', $exception->errors());
$this->assertSame('CNY',admin_setting('currency'));
}
}
public function test_missing_selector_defaults_to_dpl(): void
{
$old = getenv('SITE_PROFILE');
unset($_ENV['SITE_PROFILE'],$_SERVER['SITE_PROFILE']);
putenv('SITE_PROFILE');
try {
$this->assertSame('dpl',(require base_path('config/site.php'))['profile']);
} finally {
if ($old === false) putenv('SITE_PROFILE'); else putenv('SITE_PROFILE='.$old);
}
}
public function test_hn_admin_api_still_rejects_guests_and_regular_users(): void
{
config(['site.profile'=>'hn']);
$url = '/api/v2/'.hash('crc32b', config('app.key')).'/config/fetch';
$this->getJson($url)->assertForbidden();
\Laravel\Sanctum\Sanctum::actingAs(new \App\Models\User(['is_admin'=>false]));
$this->getJson($url)->assertForbidden();
}
public function test_ez_is_available_as_an_explicit_hn_comparison_theme(): void
{
config(['site.profile'=>'hn','site.theme'=>'EZ']);
$this->assertSame('EZ', SiteProfile::publicSettings()['frontend_theme']);
SiteProfile::assertSettingsWritable(['frontend_theme'=>'EZ']);
}
public function test_custom_theme_never_uses_dev_assets_for_a_public_hostname(): void
{
try {
$assets = \App\Support\HardnerdAssets::scripts(\Illuminate\Http\Request::create('https://hardnerd.net/'));
foreach ($assets['scripts'] as $script) $this->assertStringStartsWith('/theme/Hardnerd/assets/', $script);
} catch (\RuntimeException $exception) {
$this->assertStringContainsString('Hardnerd assets missing', $exception->getMessage());
}
}
public function test_hn_rejects_an_unknown_theme_selector(): void
{
$_ENV['SITE_PROFILE']=$_SERVER['SITE_PROFILE']='hn';
$_ENV['SITE_THEME']=$_SERVER['SITE_THEME']='Stellar';
$this->expectException(\InvalidArgumentException::class);
require base_path('config/site.php');
}
public function test_dpl_does_not_apply_the_hn_theme_selector(): void
{
$_ENV['SITE_PROFILE']=$_SERVER['SITE_PROFILE']='dpl';
$_ENV['SITE_THEME']=$_SERVER['SITE_THEME']='Stellar';
$this->assertSame('dpl',(require base_path('config/site.php'))['profile']);
$this->assertSame([], SiteProfile::publicSettings());
}
public function test_invalid_selector_fails_instead_of_silently_selecting_dpl(): void
{
$old = $_ENV['SITE_PROFILE'] ?? null;
$_SERVER['SITE_PROFILE']=$_ENV['SITE_PROFILE']='typo';
try {
$this->expectException(\InvalidArgumentException::class);
require base_path('config/site.php');
} finally {
if ($old === null) unset($_SERVER['SITE_PROFILE'],$_ENV['SITE_PROFILE']); else $_SERVER['SITE_PROFILE']=$_ENV['SITE_PROFILE']=$old;
}
}
public function test_hn_namespace_defaults_do_not_reuse_dpl_defaults(): void
{
$_SERVER['APP_NAME']=$_ENV['APP_NAME']='Shared';
foreach (['dpl','hn'] as $profile) {
$_SERVER['SITE_PROFILE']=$_ENV['SITE_PROFILE']=$profile;
$values[$profile] = [
(require base_path('config/cache.php'))['prefix'],
(require base_path('config/database.php'))['redis']['options']['prefix'],
(require base_path('config/session.php'))['cookie'],
(require base_path('config/horizon.php'))['prefix'],
];
}
$this->assertSame(['shared_cache','shared_database_','shared_session','shared_horizon:'],$values['dpl']);
$this->assertSame(['hn_cache','hn_database_','hn_session','hn_horizon:'],$values['hn']);
unset($_SERVER['APP_NAME'],$_SERVER['SITE_PROFILE'],$_ENV['APP_NAME'],$_ENV['SITE_PROFILE']);
}
}