Files
portal/app/tests/Feature/Admin/SupplierManualQueueTest.php
T
Дмитрий 6c30c248bc fix(supplier): larastan deadCode + Pest higher-order cleanup (T12)
FailoverProjectChannel: убран unreachable throw new LogicException после
try-catch в createProjectForLiderra — все ветки уже терминируют (return /
throw WindowDeferred / escalateToTier3(): never). phpstan deadCode.unreachable.

SupplierManualQueueTest: test()-> заменён на $this-> (идиома проекта,
как AdminPricingTiersControllerTest) — phpstan не типизирует Pest
higher-order test(); authAdmin() helper убран, actingAs inline в it().

Изолированный phpstan по supplier-failover файлам — 0 реальных ошибок.
Task 12 cleanup. Channel-тесты 7/7, admin-тесты 4/4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:55:11 +03:00

125 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\SupplierManualSyncQueue;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Supplier\Channel\SupplierProjectChannel;
use App\Services\Supplier\Dto\SupplierProjectDto;
use Illuminate\Foundation\Testing\DatabaseTransactions;
uses(DatabaseTransactions::class);
// EnsureSaasAdmin — стаб (Sprint 3F): в testing пропускает всех без проверки
// роли. actingAs нужен только чтобы $request->user() в manualQueueResolve дал
// id для resolved_by_user_id.
it('GET /api/admin/supplier-integration/manual-queue returns pending rows', function (): void {
$this->actingAs(User::factory()->create());
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
SupplierManualSyncQueue::create([
'project_id' => $project->id,
'platform' => 'B1',
'operation' => 'create',
'payload_snapshot' => ['limit' => 10],
'failure_reason' => 'contract_break',
'status' => 'pending',
]);
$r = $this->getJson('/api/admin/supplier-integration/manual-queue');
$r->assertOk()
->assertJsonStructure(['queue' => [['id', 'project_id', 'platform', 'operation', 'payload_snapshot', 'failure_reason', 'created_at']]])
->assertJsonCount(1, 'queue');
});
it('GET excludes resolved rows', function (): void {
$this->actingAs(User::factory()->create());
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
SupplierManualSyncQueue::create([
'project_id' => $project->id, 'platform' => 'B1', 'operation' => 'create',
'payload_snapshot' => [], 'failure_reason' => 'contract_break',
'status' => 'resolved', 'resolved_at' => now(),
]);
$this->getJson('/api/admin/supplier-integration/manual-queue')
->assertOk()->assertJsonCount(0, 'queue');
});
it('POST /resolve marks row resolved when listProjects matches', function (): void {
$admin = User::factory()->create();
$this->actingAs($admin);
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
$row = SupplierManualSyncQueue::create([
'project_id' => $project->id, 'platform' => 'B1', 'operation' => 'create',
'payload_snapshot' => ['signal_type' => 'site', 'unique_key' => 'foo.com'],
'failure_reason' => 'contract_break', 'status' => 'pending',
]);
$channelMock = new class implements SupplierProjectChannel
{
public function createProject(SupplierProjectDto $dto): int
{
return 0;
}
public function updateProject(int $externalId, SupplierProjectDto $dto): void {}
public function listProjects(): array
{
return [['id' => 99999, 'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'foo.com']];
}
};
app()->instance(SupplierProjectChannel::class, $channelMock);
$this->postJson("/api/admin/supplier-integration/manual-queue/{$row->id}/resolve")
->assertOk();
expect($row->fresh()->status)->toBe('resolved');
expect($row->fresh()->resolved_by_user_id)->toBe($admin->id);
// FK ведёт на local supplier_projects.id; portal external_id (99999) хранится
// в supplier_external_id созданной строки + в queue-row.external_id.
expect($project->fresh()->supplier_b1_project_id)->not->toBeNull();
expect(SupplierProject::find($project->fresh()->supplier_b1_project_id)->supplier_external_id)->toBe('99999');
expect($row->fresh()->external_id)->toBe('99999');
});
it('POST /resolve returns 409 when listProjects does not match', function (): void {
$this->actingAs(User::factory()->create());
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
$row = SupplierManualSyncQueue::create([
'project_id' => $project->id, 'platform' => 'B1', 'operation' => 'create',
'payload_snapshot' => ['signal_type' => 'site', 'unique_key' => 'foo.com'],
'failure_reason' => 'contract_break', 'status' => 'pending',
]);
$channelMock = new class implements SupplierProjectChannel
{
public function createProject(SupplierProjectDto $dto): int
{
return 0;
}
public function updateProject(int $externalId, SupplierProjectDto $dto): void {}
public function listProjects(): array
{
return [];
}
};
app()->instance(SupplierProjectChannel::class, $channelMock);
$this->postJson("/api/admin/supplier-integration/manual-queue/{$row->id}/resolve")
->assertStatus(409);
expect($row->fresh()->status)->toBe('pending');
});