d369383c7d
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>
98 lines
3.4 KiB
PHP
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]]);
|
|
});
|