2026-05-11 18:08:01 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Models\Project;
|
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
|
|
|
|
|
it('returns paginated list of active projects for current tenant', function () {
|
|
|
|
|
$tenant = Tenant::factory()->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']]);
|
|
|
|
|
});
|
2026-05-11 18:15:36 +03:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
});
|