GET /organisations/{organisation}/dashboard-stats returns members,
events (with status breakdown + active count), persons, the first five
members sorted by join date, and the five most recent activity log
entries. Business logic lives in OrganisationDashboardService; access
follows OrganisationPolicy@view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
161 lines
5.5 KiB
PHP
161 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Organisation;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\Organisation;
|
|
use App\Models\Person;
|
|
use App\Models\User;
|
|
use Database\Seeders\RoleSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Spatie\Activitylog\Models\Activity;
|
|
use Tests\TestCase;
|
|
|
|
class OrganisationDashboardStatsTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seed(RoleSeeder::class);
|
|
}
|
|
|
|
public function test_unauthenticated_request_returns_401(): void
|
|
{
|
|
$org = Organisation::factory()->create();
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$org->id}/dashboard-stats");
|
|
|
|
$response->assertUnauthorized();
|
|
}
|
|
|
|
public function test_member_from_other_org_returns_403(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$otherOrg = Organisation::factory()->create();
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$otherOrg->id}/dashboard-stats");
|
|
|
|
$response->assertForbidden();
|
|
}
|
|
|
|
public function test_authenticated_member_can_fetch_dashboard_stats(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$org = Organisation::factory()->create();
|
|
$org->users()->attach($user, ['role' => 'org_member']);
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$org->id}/dashboard-stats");
|
|
|
|
$response->assertOk()
|
|
->assertJsonStructure([
|
|
'data' => [
|
|
'members_count',
|
|
'events_count',
|
|
'events_by_status',
|
|
'active_events_count',
|
|
'persons_count',
|
|
'top_members',
|
|
'recent_activity',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_stats_return_correct_counts(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$org = Organisation::factory()->create();
|
|
$org->users()->attach($user, ['role' => 'org_admin']);
|
|
|
|
// 2 extra members -> 3 total
|
|
$org->users()->attach(User::factory()->create(), ['role' => 'org_member']);
|
|
$org->users()->attach(User::factory()->create(), ['role' => 'org_member']);
|
|
|
|
// Events: 2 draft, 1 published, 1 buildup, 1 closed = 5 total, 2 active (published + buildup)
|
|
Event::factory()->count(2)->create(['organisation_id' => $org->id, 'status' => 'draft']);
|
|
$published = Event::factory()->create(['organisation_id' => $org->id, 'status' => 'published']);
|
|
Event::factory()->create(['organisation_id' => $org->id, 'status' => 'buildup']);
|
|
Event::factory()->create(['organisation_id' => $org->id, 'status' => 'closed']);
|
|
|
|
// 3 persons in the published event
|
|
Person::factory()->count(3)->create(['event_id' => $published->id]);
|
|
|
|
// Noise: another org with its own data — should be filtered out
|
|
$otherOrg = Organisation::factory()->create();
|
|
$otherOrg->users()->attach(User::factory()->create(), ['role' => 'org_admin']);
|
|
$otherEvent = Event::factory()->create(['organisation_id' => $otherOrg->id, 'status' => 'published']);
|
|
Person::factory()->create(['event_id' => $otherEvent->id]);
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$org->id}/dashboard-stats");
|
|
|
|
$response->assertOk();
|
|
$data = $response->json('data');
|
|
|
|
$this->assertSame(3, $data['members_count']);
|
|
$this->assertSame(5, $data['events_count']);
|
|
$this->assertSame(2, $data['active_events_count']);
|
|
$this->assertSame(3, $data['persons_count']);
|
|
$this->assertSame(2, $data['events_by_status']['draft']);
|
|
$this->assertSame(1, $data['events_by_status']['published']);
|
|
$this->assertSame(1, $data['events_by_status']['buildup']);
|
|
$this->assertSame(1, $data['events_by_status']['closed']);
|
|
$this->assertSame(0, $data['events_by_status']['registration_open']);
|
|
}
|
|
|
|
public function test_top_members_limited_to_five_and_ordered_by_joined_at(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$org = Organisation::factory()->create();
|
|
|
|
// Attach user first with the earliest join timestamp
|
|
$org->users()->attach($user, ['role' => 'org_admin']);
|
|
|
|
// Attach 6 more members (total 7)
|
|
for ($i = 0; $i < 6; $i++) {
|
|
$org->users()->attach(User::factory()->create(), ['role' => 'org_member']);
|
|
}
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$org->id}/dashboard-stats");
|
|
|
|
$response->assertOk();
|
|
$members = $response->json('data.top_members');
|
|
|
|
$this->assertCount(5, $members);
|
|
$this->assertSame($user->id, $members[0]['id']);
|
|
$this->assertSame('org_admin', $members[0]['role']);
|
|
}
|
|
|
|
public function test_recent_activity_limited_to_five_entries(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$org = Organisation::factory()->create();
|
|
$org->users()->attach($user, ['role' => 'org_admin']);
|
|
|
|
Activity::query()->delete();
|
|
|
|
// Trigger 7 activity log entries on the organisation
|
|
for ($i = 0; $i < 7; $i++) {
|
|
$org->update(['name' => "Changed {$i}"]);
|
|
}
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$org->id}/dashboard-stats");
|
|
|
|
$response->assertOk();
|
|
$this->assertCount(5, $response->json('data.recent_activity'));
|
|
}
|
|
}
|