Files
portal/app/tests/Feature/Database/SupplierManualSyncQueueSchemaTest.php
T
Дмитрий d369383c7d feat(supplier): supplier_manual_sync_queue table (Tier 3 queue)
SaaS-level (без tenant_id, без RLS, как supplier_csv_reconcile_log).
+3 CHECK (platform/operation/status), +2 индекса, +2 FK
(project_id→projects CASCADE, resolved_by_user_id→users SET NULL).

Миграция через DB::unprepared (PG prepared statement не разрешает multi-SQL).
schema.sql bumped v8.24 → v8.25 (64 base tables / 121 indexes / 40 RLS).
SchemaDeltaTest обновлён под новые метрики (63→64 tables, 119→121 indexes).

§15.2 pre-flight: rebase на origin/main f7f37fb выполнен до коммита.
Spec §4.5. Task 3 of 12. Регрессия: schema+delta тесты 11/11.

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

98 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\SupplierManualSyncQueue;
use App\Models\Tenant;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
uses(DatabaseTransactions::class);
it('table supplier_manual_sync_queue exists with required columns', function (): void {
$cols = collect(DB::select(
"SELECT column_name FROM information_schema.columns WHERE table_name = 'supplier_manual_sync_queue'"
))->pluck('column_name')->all();
expect($cols)->toContain(
'id', 'project_id', 'platform', 'operation', 'external_id',
'payload_snapshot', 'failure_reason', 'status',
'resolved_by_user_id', 'created_at', 'resolved_at',
);
});
it('platform CHECK constraint rejects non-B1/B2/B3', function (): void {
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
expect(fn () => DB::table('supplier_manual_sync_queue')->insert([
'project_id' => $project->id,
'platform' => 'B9',
'operation' => 'create',
'payload_snapshot' => json_encode([]),
'failure_reason' => 'portal_unreachable',
'status' => 'pending',
'created_at' => now(),
]))->toThrow(QueryException::class);
});
it('operation CHECK constraint rejects non-create/update', function (): void {
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
expect(fn () => DB::table('supplier_manual_sync_queue')->insert([
'project_id' => $project->id,
'platform' => 'B1',
'operation' => 'delete',
'payload_snapshot' => json_encode([]),
'failure_reason' => 'portal_unreachable',
'status' => 'pending',
'created_at' => now(),
]))->toThrow(QueryException::class);
});
it('status CHECK constraint rejects non-pending/resolved/cancelled', function (): void {
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
expect(fn () => DB::table('supplier_manual_sync_queue')->insert([
'project_id' => $project->id,
'platform' => 'B1',
'operation' => 'create',
'payload_snapshot' => json_encode([]),
'failure_reason' => 'portal_unreachable',
'status' => 'archived',
'created_at' => now(),
]))->toThrow(QueryException::class);
});
it('FK on project_id enforces referential integrity', function (): void {
expect(fn () => DB::table('supplier_manual_sync_queue')->insert([
'project_id' => 999_999_999,
'platform' => 'B1',
'operation' => 'create',
'payload_snapshot' => json_encode([]),
'failure_reason' => 'portal_unreachable',
'status' => 'pending',
'created_at' => now(),
]))->toThrow(QueryException::class);
});
it('Eloquent model SupplierManualSyncQueue creates row and casts payload_snapshot to array', function (): void {
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
$row = SupplierManualSyncQueue::create([
'project_id' => $project->id,
'platform' => 'B1',
'operation' => 'create',
'payload_snapshot' => ['limit' => 10, 'workdays' => [1, 2, 3]],
'failure_reason' => 'contract_break',
'status' => 'pending',
]);
expect($row->fresh()->payload_snapshot)->toBe(['limit' => 10, 'workdays' => [1, 2, 3]]);
});