create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); Project::factory()->count(3)->create(['tenant_id' => $tenant->id, 'signal_type' => 'site', 'signal_identifier' => 'example.com']); $response = $this->actingAs($user)->getJson('/api/projects'); $response->assertOk(); $response->assertJsonStructure([ 'data' => [['id', 'name', 'signal_type', 'signal_identifier', 'daily_limit_target', 'delivered_today', 'is_active', 'archived_at', 'sync_status']], 'meta' => ['current_page', 'per_page', 'total'], ]); expect($response->json('meta.total'))->toBe(3); }); it('filters list by signal_type', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); Project::factory()->create(['tenant_id' => $tenant->id, 'signal_type' => 'site', 'signal_identifier' => 'example.com']); Project::factory()->create(['tenant_id' => $tenant->id, 'signal_type' => 'call', 'signal_identifier' => '+79001234567']); $response = $this->actingAs($user)->getJson('/api/projects?signal_type=site'); expect($response->json('meta.total'))->toBe(1); }); it('isolates projects per tenant (RLS)', function () { $tenantA = Tenant::factory()->create(); $tenantB = Tenant::factory()->create(); $userA = User::factory()->create(['tenant_id' => $tenantA->id]); Project::factory()->count(2)->create(['tenant_id' => $tenantA->id]); Project::factory()->count(5)->create(['tenant_id' => $tenantB->id]); $response = $this->actingAs($userA)->getJson('/api/projects'); expect($response->json('meta.total'))->toBe(2); }); it('excludes archived projects by default', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); Project::factory()->create(['tenant_id' => $tenant->id]); Project::factory()->create(['tenant_id' => $tenant->id, 'archived_at' => now()]); $response = $this->actingAs($user)->getJson('/api/projects'); expect($response->json('meta.total'))->toBe(1); }); it('returns archived when status=archived requested', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); Project::factory()->create(['tenant_id' => $tenant->id, 'archived_at' => now()]); $response = $this->actingAs($user)->getJson('/api/projects?status=archived'); expect($response->json('meta.total'))->toBe(1); }); it('returns batch fetch by ids without pagination', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); $projects = Project::factory()->count(3)->create(['tenant_id' => $tenant->id]); $ids = $projects->pluck('id')->take(2)->implode(','); $response = $this->actingAs($user)->getJson("/api/projects?ids={$ids}"); expect(count($response->json('data')))->toBe(2); }); it('show returns project with supplier_links array', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); $project = Project::factory()->create(['tenant_id' => $tenant->id]); $response = $this->actingAs($user)->getJson("/api/projects/{$project->id}"); $response->assertOk(); $response->assertJsonStructure(['data' => ['id', 'name', 'supplier_links']]); }); it('?ids batch filters out projects from foreign tenants silently', function () { $tenantA = Tenant::factory()->create(); $tenantB = Tenant::factory()->create(); $userA = User::factory()->create(['tenant_id' => $tenantA->id]); $ownProject = Project::factory()->create([ 'tenant_id' => $tenantA->id, 'signal_type' => 'site', 'signal_identifier' => 'own.ru', ]); $foreignProject = Project::factory()->create([ 'tenant_id' => $tenantB->id, 'signal_type' => 'site', 'signal_identifier' => 'foreign.ru', ]); $response = $this->actingAs($userA)->getJson( "/api/projects?ids={$ownProject->id},{$foreignProject->id}" ); $response->assertOk(); $data = $response->json('data'); expect(count($data))->toBe(1); expect($data[0]['id'])->toBe($ownProject->id); }); it('show returns 200 for archived project (read access preserved)', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); $project = Project::factory()->create([ 'tenant_id' => $tenant->id, 'archived_at' => now(), 'signal_type' => 'site', 'signal_identifier' => 'archived.ru', ]); $response = $this->actingAs($user)->getJson("/api/projects/{$project->id}"); $response->assertOk(); expect($response->json('data.id'))->toBe($project->id); expect($response->json('data.archived_at'))->not->toBeNull(); });