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']); } }