diff --git a/app/Events/Server/Created.php b/app/Events/Server/Created.php deleted file mode 100644 index 869279104..000000000 --- a/app/Events/Server/Created.php +++ /dev/null @@ -1,19 +0,0 @@ -schema([ + Forms\Components\TextInput::make('endpoint')->activeUrl()->required(), + Forms\Components\TextInput::make('description')->nullable(), + Forms\Components\CheckboxList::make('events')->lazy()->options( + fn () => WebhookConfiguration::filamentCheckboxList() + ) + ->columns(3) + ->columnSpanFull() + ->gridDirection('row') + ->required(), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + // + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListWebhookConfigurations::route('/'), + 'create' => Pages\CreateWebhookConfiguration::route('/create'), + 'edit' => Pages\EditWebhookConfiguration::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/WebhookResource/Pages/CreateWebhookConfiguration.php b/app/Filament/Resources/WebhookResource/Pages/CreateWebhookConfiguration.php new file mode 100644 index 000000000..96f45742c --- /dev/null +++ b/app/Filament/Resources/WebhookResource/Pages/CreateWebhookConfiguration.php @@ -0,0 +1,11 @@ +transaction(function ($instance) use ($server, $subuser) { $subuser->delete(); + $subuser->user->notify(new RemovedFromServer([ + 'user' => $subuser->user->name_first, + 'name' => $subuser->server->name, + ])); + try { $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); } catch (DaemonConnectionException $exception) { diff --git a/app/Jobs/ProcessWebhook.php b/app/Jobs/ProcessWebhook.php new file mode 100644 index 000000000..2206e0028 --- /dev/null +++ b/app/Jobs/ProcessWebhook.php @@ -0,0 +1,40 @@ +webhookConfiguration->endpoint, $this->data)->throw(); + $successful = now(); + } catch (\Exception) { + $successful = null; + } + + $this->webhookConfiguration->webhooks()->create([ + 'payload' => $this->data, + 'successful_at' => $successful, + 'event' => $this->eventName, + 'endpoint' => $this->webhookConfiguration->endpoint, + ]); + } +} diff --git a/app/Listeners/DispatchWebhooks.php b/app/Listeners/DispatchWebhooks.php new file mode 100644 index 000000000..102782083 --- /dev/null +++ b/app/Listeners/DispatchWebhooks.php @@ -0,0 +1,39 @@ +eventIsWatched($eventName)) { + return; + } + + $matchingHooks = cache()->rememberForever("webhooks.$eventName", function () use ($eventName) { + return WebhookConfiguration::query()->whereJsonContains('events', $eventName)->get(); + }); + + foreach ($matchingHooks ?? [] as $webhookConfig) { + if (in_array($eventName, $webhookConfig->events)) { + ProcessWebhook::dispatch($webhookConfig, $eventName, $data); + } + } + } + + protected function eventIsWatched(string $eventName): bool + { + $watchedEvents = cache()->rememberForever('watchedWebhooks', function () { + return WebhookConfiguration::pluck('events') + ->flatten() + ->unique() + ->values() + ->all(); + }); + + return in_array($eventName, $watchedEvents); + } +} diff --git a/app/Models/Webhook.php b/app/Models/Webhook.php new file mode 100644 index 000000000..28c029122 --- /dev/null +++ b/app/Models/Webhook.php @@ -0,0 +1,21 @@ + 'array', + 'successful_at' => 'datetime', + ]; + } +} diff --git a/app/Models/WebhookConfiguration.php b/app/Models/WebhookConfiguration.php new file mode 100644 index 000000000..092eac334 --- /dev/null +++ b/app/Models/WebhookConfiguration.php @@ -0,0 +1,120 @@ + 'json', + ]; + } + + protected static function booted(): void + { + self::saved(static function (self $webhookConfiguration): void { + $changedEvents = collect([ + ...((array) $webhookConfiguration->events), + ...$webhookConfiguration->getOriginal('events', '[]'), + ])->unique(); + + $changedEvents->each(function (string $event) { + cache()->forever("webhooks.$event", WebhookConfiguration::query()->whereJsonContains('events', $event)->get()); + }); + + cache()->forever('watchedWebhooks', WebhookConfiguration::pluck('events')->flatten()->unique()->values()->all()); + }); + } + + public function webhooks(): HasMany + { + return $this->hasMany(Webhook::class); + } + + public static function allPossibleEvents(): array + { + return static::discoverCustomEvents() + static::allModelEvents(); + } + + public static function filamentCheckboxList(): array + { + $list = []; + $events = static::allPossibleEvents(); + foreach ($events as $event) { + $list[$event] = static::transformClassName($event); + } + + return $list; + } + + public static function transformClassName(string $event): string + { + return str($event) + ->after('eloquent.') + ->replace('App\\Models\\', '') + ->replace('App\\Events\\', 'event: ') + ->toString(); + } + + public static function allModelEvents(): array + { + $eventTypes = ['created', 'updated', 'deleted']; + $models = static::discoverModels(); + + $events = []; + foreach ($models as $model) { + foreach ($eventTypes as $eventType) { + $events[] = "eloquent.$eventType: $model"; + } + } + + return $events; + } + + public static function discoverModels(): array + { + $namespace = 'App\\Models\\'; + $directory = app_path('Models'); + + $models = []; + foreach (File::allFiles($directory) as $file) { + $models[] = $namespace . str($file->getFilename()) + ->replace([DIRECTORY_SEPARATOR, '.php'], ['\\', '']); + } + + return $models; + } + + public static function discoverCustomEvents(): array + { + $directory = app_path('Events'); + + $events = []; + foreach (File::allFiles($directory) as $file) { + $namespace = str($file->getPath()) + ->after(base_path()) + ->replace(DIRECTORY_SEPARATOR, '\\') + ->replace('\\app\\', 'App\\') + ->toString(); + + $events[] = $namespace . '\\' . str($file->getFilename()) + ->replace([DIRECTORY_SEPARATOR, '.php'], ['\\', '']); + } + + return $events; + } +} diff --git a/app/Observers/EggVariableObserver.php b/app/Observers/EggVariableObserver.php deleted file mode 100644 index 6fd6b12e6..000000000 --- a/app/Observers/EggVariableObserver.php +++ /dev/null @@ -1,22 +0,0 @@ -field_type)) { - unset($variable->field_type); - } - } - - public function updating(EggVariable $variable): void - { - if (isset($variable->field_type)) { - unset($variable->field_type); - } - } -} diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php deleted file mode 100644 index 1804511a0..000000000 --- a/app/Observers/ServerObserver.php +++ /dev/null @@ -1,76 +0,0 @@ -user->notify(new AddedToServer([ - 'user' => $subuser->user->name_first, - 'name' => $subuser->server->name, - 'uuid_short' => $subuser->server->uuid_short, - ])); - } - - /** - * Listen to the Subuser deleting event. - */ - public function deleting(Subuser $subuser): void - { - event(new Events\Subuser\Deleting($subuser)); - } - - /** - * Listen to the Subuser deleted event. - */ - public function deleted(Subuser $subuser): void - { - event(new Events\Subuser\Deleted($subuser)); - - $subuser->user->notify(new RemovedFromServer([ - 'user' => $subuser->user->name_first, - 'name' => $subuser->server->name, - ])); - } -} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php deleted file mode 100644 index 6ec088830..000000000 --- a/app/Observers/UserObserver.php +++ /dev/null @@ -1,43 +0,0 @@ - [ServerInstalledNotification::class], + 'App\\*' => [DispatchWebhooks::class], + 'eloquent.created*' => [DispatchWebhooks::class], + 'eloquent.deleted*' => [DispatchWebhooks::class], + 'eloquent.updated*' => [DispatchWebhooks::class], ]; - - /** - * Register any events for your application. - */ - public function boot(): void - { - parent::boot(); - - User::observe(UserObserver::class); - Server::observe(ServerObserver::class); - Subuser::observe(SubuserObserver::class); - EggVariable::observe(EggVariableObserver::class); - } } diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index 19c9a5e12..b03b50928 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -48,8 +48,7 @@ class EggExporterService ], 'variables' => $egg->variables->map(function (EggVariable $eggVariable) { return Collection::make($eggVariable->toArray()) - ->except(['id', 'egg_id', 'created_at', 'updated_at']) - ->merge(['field_type' => 'text']); + ->except(['id', 'egg_id', 'created_at', 'updated_at']); }), ]; diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index e539a7f28..41b56537d 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -48,6 +48,11 @@ class EggImporterService 'copy_script_from' => null, ]); + // Don't check for this anymore + for ($i = 0; $i < count($parsed['variables']); $i++) { + unset($parsed['variables'][$i]['field_type']); + } + $egg = $this->fillFromParsed($egg, $parsed); $egg->save(); @@ -157,17 +162,13 @@ class EggImporterService $images = $parsed['images']; } - unset($parsed['images'], $parsed['image']); + unset($parsed['images'], $parsed['image'], $parsed['field_type']); $parsed['docker_images'] = []; foreach ($images as $image) { $parsed['docker_images'][$image] = $image; } - $parsed['variables'] = array_map(function ($value) { - return array_merge($value, ['field_type' => 'text']); - }, $parsed['variables']); - return $parsed; } } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 0f9508ba7..e0c5ac39b 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -3,6 +3,7 @@ namespace App\Services\Subusers; use App\Models\User; +use App\Notifications\AddedToServer; use Illuminate\Support\Str; use App\Models\Server; use App\Models\Subuser; @@ -59,11 +60,19 @@ class SubuserCreationService throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); } - return Subuser::query()->create([ + $subuser = Subuser::query()->create([ 'user_id' => $user->id, 'server_id' => $server->id, 'permissions' => array_unique($permissions), ]); + + $subuser->user->notify(new AddedToServer([ + 'user' => $subuser->user->name_first, + 'name' => $subuser->server->name, + 'uuid_short' => $subuser->server->uuid_short, + ])); + + return $subuser; }); } } diff --git a/app/Traits/Services/HasWebhookPayload.php b/app/Traits/Services/HasWebhookPayload.php new file mode 100644 index 000000000..54768dc28 --- /dev/null +++ b/app/Traits/Services/HasWebhookPayload.php @@ -0,0 +1,15 @@ +__serialize(); + } + + return []; + } +} diff --git a/database/Factories/AllocationFactory.php b/database/Factories/AllocationFactory.php index 19405b702..3cbcc3e33 100644 --- a/database/Factories/AllocationFactory.php +++ b/database/Factories/AllocationFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Models\Node; use App\Models\Server; use App\Models\Allocation; use Illuminate\Database\Eloquent\Factories\Factory; @@ -23,6 +24,7 @@ class AllocationFactory extends Factory return [ 'ip' => $this->faker->unique()->ipv4(), 'port' => $this->faker->unique()->numberBetween(1024, 65535), + 'node_id' => Node::factory(), ]; } diff --git a/database/Factories/EggFactory.php b/database/Factories/EggFactory.php index 36ec942fa..219a179b0 100644 --- a/database/Factories/EggFactory.php +++ b/database/Factories/EggFactory.php @@ -22,6 +22,12 @@ class EggFactory extends Factory { return [ 'uuid' => Uuid::uuid4()->toString(), + 'author' => $this->faker->email(), + 'docker_images' => ['a', 'b', 'c'], + 'config_logs' => '{}', + 'config_startup' => '{}', + 'config_stop' => '{}', + 'config_files' => '{}', 'name' => $this->faker->name(), 'description' => implode(' ', $this->faker->sentences()), 'startup' => 'java -jar test.jar', diff --git a/database/Factories/NodeFactory.php b/database/Factories/NodeFactory.php index 19a1fbf7b..030bd3e38 100644 --- a/database/Factories/NodeFactory.php +++ b/database/Factories/NodeFactory.php @@ -40,6 +40,7 @@ class NodeFactory extends Factory 'daemon_listen' => 8080, 'daemon_sftp' => 2022, 'daemon_base' => '/var/lib/panel/volumes', + 'maintenance_mode' => false, ]; } } diff --git a/database/Factories/ServerFactory.php b/database/Factories/ServerFactory.php index e0ced8de5..2f1d8c652 100644 --- a/database/Factories/ServerFactory.php +++ b/database/Factories/ServerFactory.php @@ -2,6 +2,10 @@ namespace Database\Factories; +use App\Models\Allocation; +use App\Models\Egg; +use App\Models\Node; +use App\Models\User; use Carbon\Carbon; use Ramsey\Uuid\Uuid; use Illuminate\Support\Str; @@ -17,12 +21,28 @@ class ServerFactory extends Factory */ protected $model = Server::class; + public function withNode(?Node $node = null): static + { + $node ??= Node::factory()->create(); + + return $this->state(fn () => [ + 'node_id' => $node->id, + 'allocation_id' => Allocation::factory([ + 'node_id' => $node->id, + ]), + ]); + } + /** * Define the model's default state. */ public function definition(): array { return [ + 'owner_id' => User::factory(), + 'node_id' => Node::factory(), + 'allocation_id' => Allocation::factory(), + 'egg_id' => Egg::factory(), 'uuid' => Uuid::uuid4()->toString(), 'uuid_short' => Str::lower(Str::random(8)), 'name' => $this->faker->firstName(), diff --git a/database/Factories/WebhookConfigurationFactory.php b/database/Factories/WebhookConfigurationFactory.php new file mode 100644 index 000000000..e1a70954e --- /dev/null +++ b/database/Factories/WebhookConfigurationFactory.php @@ -0,0 +1,20 @@ + fake()->url(), + 'description' => fake()->sentence(), + 'events' => [], + ]; + } +} diff --git a/database/Seeders/EggSeeder.php b/database/Seeders/EggSeeder.php index d6a3743b7..e3f410348 100644 --- a/database/Seeders/EggSeeder.php +++ b/database/Seeders/EggSeeder.php @@ -37,7 +37,6 @@ class EggSeeder extends Seeder public function run(): void { foreach (static::$imports as $import) { - /* @noinspection PhpParamsInspection */ $this->parseEggFiles($import); } } diff --git a/database/migrations/2024_04_21_162544_create_webhook_configurations_table.php b/database/migrations/2024_04_21_162544_create_webhook_configurations_table.php new file mode 100644 index 000000000..c39805f08 --- /dev/null +++ b/database/migrations/2024_04_21_162544_create_webhook_configurations_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('endpoint'); + $table->string('description'); + $table->json('events'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('webhook_configurations'); + } +}; diff --git a/database/migrations/2024_04_21_162552_create_webhooks_table.php b/database/migrations/2024_04_21_162552_create_webhooks_table.php new file mode 100644 index 000000000..a7565e041 --- /dev/null +++ b/database/migrations/2024_04_21_162552_create_webhooks_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignIdFor(\App\Models\WebhookConfiguration::class)->constrained(); + $table->string('event'); + $table->string('endpoint'); + $table->timestamp('successful_at')->nullable(); + $table->json('payload'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('webhooks'); + } +}; diff --git a/tests/Feature/DispatchWebhooksTest.php b/tests/Feature/DispatchWebhooksTest.php new file mode 100644 index 000000000..5c032761c --- /dev/null +++ b/tests/Feature/DispatchWebhooksTest.php @@ -0,0 +1,69 @@ +create([ + 'events' => ['eloquent.created: '.Server::class], + ]); + + $this->createServer(); + + Queue::assertPushed(ProcessWebhook::class); + } + + public function test_sends_multiple_webhooks() + { + WebhookConfiguration::factory(2) + ->create(['events' => ['eloquent.created: '.Server::class]]); + + $this->createServer(); + + Queue::assertPushed(ProcessWebhook::class, 2); + } + + public function test_it_sends_no_webhooks() + { + WebhookConfiguration::factory()->create(); + + $this->createServer(); + + Queue::assertNothingPushed(); + } + + public function test_it_sends_some_webhooks() + { + WebhookConfiguration::factory(2) + ->sequence( + ['events' => ['eloquent.created: '.Server::class]], + ['events' => ['eloquent.deleted: '.Server::class]] + )->create(); + + $this->createServer(); + + Queue::assertPushed(ProcessWebhook::class, 1); + } + + public function createServer(): Server + { + return Server::factory()->withNode()->create(); + } +} diff --git a/tests/Feature/ProcessWebhooksTest.php b/tests/Feature/ProcessWebhooksTest.php new file mode 100644 index 000000000..7eb7a8d9a --- /dev/null +++ b/tests/Feature/ProcessWebhooksTest.php @@ -0,0 +1,204 @@ +create([ + 'events' => [$eventName = 'eloquent.created: '.Server::class], + ]); + + Http::fake([$webhook->endpoint => Http::response()]); + + $data = [ + 'status' => null, + 'oom_killer' => false, + 'installed_at' => null, + 'owner_id' => 1, + 'node_id' => 1, + 'allocation_id' => 1, + 'egg_id' => 1, + 'uuid' => '9ff9885f-ab79-4a6e-a53e-466a84cdb2d8', + 'uuid_short' => 'ypk27val', + 'name' => 'Delmer', + 'description' => 'Est sed quibusdam sed eos quae est. Ut similique non impedit voluptas. Aperiam repellendus impedit voluptas officiis id.', + 'skip_scripts' => false, + 'memory' => 512, + 'swap' => 0, + 'disk' => 512, + 'io' => 500, + 'cpu' => 0, + 'threads' => null, + 'startup' => '/bin/bash echo "hello world"', + 'image' => 'foo/bar:latest', + 'allocation_limit' => null, + 'database_limit' => null, + 'backup_limit' => 0, + 'created_at' => '2024-09-12T20:21:29.000000Z', + 'updated_at' => '2024-09-12T20:21:29.000000Z', + 'id' => 1, + ]; + + ProcessWebhook::dispatchSync( + $webhook, + 'eloquent.created: '.Server::class, + $data, + ); + + $this->assertCount(1, cache()->get("webhooks.$eventName")); + $this->assertEquals($webhook->id, cache()->get("webhooks.$eventName")->first()->id); + + Http::assertSentCount(1); + Http::assertSent(function (Request $request) use ($webhook, $data) { + return $webhook->endpoint === $request->url() + && $request->data() === $data; + }); + } + + public function test_sends_multiple_webhooks() + { + [$webhook1, $webhook2] = WebhookConfiguration::factory(2) + ->create(['events' => [$eventName = 'eloquent.created: '.Server::class]]); + + Http::fake([ + $webhook1->endpoint => Http::response(), + $webhook2->endpoint => Http::response(), + ]); + + $this->createServer(); + + $this->assertCount(2, cache()->get("webhooks.$eventName")); + $this->assertContains($webhook1->id, cache()->get("webhooks.$eventName")->pluck('id')); + $this->assertContains($webhook2->id, cache()->get("webhooks.$eventName")->pluck('id')); + + Http::assertSentCount(2); + Http::assertSent(fn (Request $request) => $webhook1->endpoint === $request->url()); + Http::assertSent(fn (Request $request) => $webhook2->endpoint === $request->url()); + } + + public function test_it_sends_no_webhooks() + { + Http::fake(); + + WebhookConfiguration::factory()->create(); + + $this->createServer(); + + Http::assertSentCount(0); + } + + public function test_it_sends_some_webhooks() + { + [$webhook1, $webhook2] = WebhookConfiguration::factory(2) + ->sequence( + ['events' => ['eloquent.created: '.Server::class]], + ['events' => ['eloquent.deleted: '.Server::class]] + )->create(); + + Http::fake([ + $webhook1->endpoint => Http::response(), + $webhook2->endpoint => Http::response(), + ]); + + $this->createServer(); + + Http::assertSentCount(1); + Http::assertSent(fn (Request $request) => $webhook1->endpoint === $request->url()); + Http::assertNotSent(fn (Request $request) => $webhook2->endpoint === $request->url()); + } + + public function test_it_records_when_a_webhook_is_sent() + { + $webhookConfig = WebhookConfiguration::factory() + ->create(['events' => ['eloquent.created: '.Server::class]]); + + Http::fake([$webhookConfig->endpoint => Http::response()]); + + $this->assertDatabaseCount(Webhook::class, 0); + + $server = $this->createServer(); + + $this->assertDatabaseCount(Webhook::class, 1); + + $webhook = Webhook::query()->first(); + $this->assertEquals($server->uuid, $webhook->payload[0]['uuid']); + + $this->assertDatabaseHas(Webhook::class, [ + 'endpoint' => $webhookConfig->endpoint, + 'successful_at' => now()->startOfSecond(), + 'event' => 'eloquent.created: '.Server::class, + ]); + } + + public function test_it_records_when_a_webhook_fails() + { + $webhookConfig = WebhookConfiguration::factory()->create([ + 'events' => ['eloquent.created: '.Server::class], + ]); + + Http::fake([$webhookConfig->endpoint => Http::response(status: 500)]); + + $this->assertDatabaseCount(Webhook::class, 0); + + $server = $this->createServer(); + + $this->assertDatabaseCount(Webhook::class, 1); + $this->assertDatabaseHas(Webhook::class, [ + 'payload' => json_encode([$server->toArray()]), + 'endpoint' => $webhookConfig->endpoint, + 'successful_at' => null, + 'event' => 'eloquent.created: '.Server::class, + ]); + } + + public function test_it_is_triggered_on_custom_events() + { + $webhookConfig = WebhookConfiguration::factory()->create([ + 'events' => [Installed::class], + ]); + + Http::fake([$webhookConfig->endpoint => Http::response()]); + + $this->assertDatabaseCount(Webhook::class, 0); + + $server = $this->createServer(); + + event(new Installed($server)); + + $this->assertDatabaseCount(Webhook::class, 1); + $this->assertDatabaseHas(Webhook::class, [ + // 'payload' => json_encode([['server' => $server->toArray()]]), + 'endpoint' => $webhookConfig->endpoint, + 'successful_at' => now()->startOfSecond(), + 'event' => Installed::class, + ]); + + } + + public function createServer(): Server + { + return Server::factory()->withNode()->create(); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index abcd9d84d..71242d56e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,6 +19,9 @@ abstract class TestCase extends BaseTestCase Carbon::setTestNow(Carbon::now()); CarbonImmutable::setTestNow(Carbon::now()); + // TODO: if unit tests suite, then force set DB_HOST=UNIT_NO_DB + // env('DB_DATABASE', 'UNIT_NO_DB'); + // Why, you ask? If we don't force this to false it is possible for certain exceptions // to show their error message properly in the integration test output, but not actually // be setup correctly to display their message in production. diff --git a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php index abdcab33c..8d1237294 100644 --- a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php +++ b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php @@ -29,10 +29,14 @@ class MaintenanceMiddlewareTest extends MiddlewareTestCase */ public function testHandle(): void { - $server = Server::factory()->make(); - $node = Node::factory()->make(['maintenance' => 0]); + // maintenance mode is off by default + $server = new Server(); + $node = new Node([ + 'maintenance_mode' => false, + ]); $server->setRelation('node', $node); + $this->setRequestAttribute('server', $server); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); @@ -43,10 +47,13 @@ class MaintenanceMiddlewareTest extends MiddlewareTestCase */ public function testHandleInMaintenanceMode(): void { - $server = Server::factory()->make(); - $node = Node::factory()->make(['maintenance_mode' => 1]); + $server = new Server(); + $node = new Node([ + 'maintenance_mode' => true, + ]); $server->setRelation('node', $node); + $this->setRequestAttribute('server', $server); $this->response->shouldReceive('view')