diff --git a/tests/Filament/Admin/AdminActivityListenerTest.php b/tests/Filament/Admin/AdminActivityListenerTest.php new file mode 100644 index 000000000..fab679d88 --- /dev/null +++ b/tests/Filament/Admin/AdminActivityListenerTest.php @@ -0,0 +1,146 @@ +newInstanceWithoutConstructor(); +} + +beforeEach(function () { + [$this->admin] = generateTestAccount([]); + $this->admin = $this->admin->syncRoles(Role::getRootAdmin()); + $this->actingAs($this->admin); + + Filament::setCurrentPanel('admin'); +}); + +it('logs create activity for an egg', function () { + $egg = Egg::first(); + + $listener = new AdminActivityListener(); + $listener->handle($egg, ['name' => 'Test Egg'], pageInstance(CreateEgg::class)); + + $this->assertActivityLogged('admin:egg.create'); +}); + +it('logs update activity for an egg', function () { + $egg = Egg::first(); + + $listener = new AdminActivityListener(); + $listener->handle($egg, ['name' => 'Updated Egg'], pageInstance(EditEgg::class)); + + $this->assertActivityLogged('admin:egg.update'); +}); + +it('logs create activity for a node', function () { + $node = Node::first(); + + $listener = new AdminActivityListener(); + $listener->handle($node, ['name' => 'Test Node'], pageInstance(CreateNode::class)); + + $this->assertActivityLogged('admin:node.create'); +}); + +it('logs update activity for a node', function () { + $node = Node::first(); + + $listener = new AdminActivityListener(); + $listener->handle($node, ['name' => 'Updated Node'], pageInstance(EditNode::class)); + + $this->assertActivityLogged('admin:node.update'); +}); + +it('does not log activity for non-admin panels', function () { + Filament::setCurrentPanel('app'); + + $egg = Egg::first(); + + $listener = new AdminActivityListener(); + $listener->handle($egg, ['name' => 'Test'], pageInstance(CreateEgg::class)); + + Event::assertNotDispatched(ActivityLogged::class); +}); + +it('sets the record as the activity subject', function () { + $egg = Egg::first(); + + $listener = new AdminActivityListener(); + $listener->handle($egg, ['name' => 'Test'], pageInstance(CreateEgg::class)); + + $this->assertActivityFor('admin:egg.create', $this->admin, $egg); +}); + +it('redacts sensitive fields from activity properties', function () { + $egg = Egg::first(); + + $data = [ + 'name' => 'Visible', + 'password' => 'should-be-redacted', + 'password_confirmation' => 'should-be-redacted', + 'token' => 'should-be-redacted', + 'secret' => 'should-be-redacted', + 'api_key' => 'should-be-redacted', + ]; + + $listener = new AdminActivityListener(); + $listener->handle($egg, $data, pageInstance(EditEgg::class)); + + Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $event) { + $properties = $event->model->properties; + + expect($properties)->toHaveKey('name', 'Visible') + ->not->toHaveKey('password') + ->not->toHaveKey('password_confirmation') + ->not->toHaveKey('token') + ->not->toHaveKey('secret') + ->not->toHaveKey('api_key'); + + return true; + }); +}); + +it('redacts sensitive fields in nested arrays', function () { + $egg = Egg::first(); + + $data = [ + 'name' => 'Visible', + 'nested' => [ + 'safe' => 'value', + 'password' => 'should-be-redacted', + 'token' => 'should-be-redacted', + ], + ]; + + $listener = new AdminActivityListener(); + $listener->handle($egg, $data, pageInstance(EditEgg::class)); + + Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $event) { + $properties = $event->model->properties; + + expect($properties['nested'])->toHaveKey('safe', 'value') + ->not->toHaveKey('password') + ->not->toHaveKey('token'); + + return true; + }); +}); + +it('generates kebab-case event names from model class names', function () { + $node = Node::first(); + + $listener = new AdminActivityListener(); + $listener->handle($node, ['name' => 'Test'], pageInstance(CreateNode::class)); + + $this->assertActivityLogged('admin:node.create'); +});