2026-05-10 12:47:25 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
|
|
|
|
|
|
test('supplier_projects table exists with required columns', function () {
|
|
|
|
|
expect(Schema::hasTable('supplier_projects'))->toBeTrue();
|
|
|
|
|
|
|
|
|
|
foreach ([
|
|
|
|
|
'id',
|
|
|
|
|
'platform',
|
|
|
|
|
'signal_type',
|
|
|
|
|
'unique_key',
|
|
|
|
|
'supplier_external_id',
|
|
|
|
|
'current_limit',
|
|
|
|
|
'current_workdays',
|
|
|
|
|
'current_regions',
|
|
|
|
|
'sync_status',
|
|
|
|
|
'last_synced_at',
|
|
|
|
|
'inactive_since',
|
|
|
|
|
'created_at',
|
|
|
|
|
'updated_at',
|
|
|
|
|
] as $col) {
|
|
|
|
|
expect(Schema::hasColumn('supplier_projects', $col))->toBeTrue();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 19:23:13 +03:00
|
|
|
test('supplier_projects has unique constraint on (platform, unique_key, subject_code)', function () {
|
|
|
|
|
// v8.26 (project-migration-redesign Plan 1): per-субъект экспорт — composite unique
|
|
|
|
|
// расширен до (platform, unique_key, subject_code) NULLS NOT DISTINCT. Старый
|
|
|
|
|
// 2-колоночный индекс supplier_projects_platform_unique_key_unique заменён.
|
2026-05-10 12:47:25 +03:00
|
|
|
$idx = DB::selectOne(
|
|
|
|
|
"SELECT indexdef
|
|
|
|
|
FROM pg_indexes
|
|
|
|
|
WHERE tablename = 'supplier_projects'
|
2026-05-20 19:23:13 +03:00
|
|
|
AND indexname = 'supplier_projects_platform_key_subject_unique'"
|
2026-05-10 12:47:25 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect($idx)->not->toBeNull();
|
|
|
|
|
expect($idx->indexdef)
|
|
|
|
|
->toContain('UNIQUE')
|
|
|
|
|
->toContain('platform')
|
2026-05-20 19:23:13 +03:00
|
|
|
->toContain('unique_key')
|
|
|
|
|
->toContain('subject_code');
|
2026-05-10 12:47:25 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('supplier_projects platform check constraint allows only B1, B2, B3', function () {
|
|
|
|
|
$check = DB::selectOne(
|
|
|
|
|
"SELECT pg_get_constraintdef(c.oid) AS def
|
|
|
|
|
FROM pg_constraint c
|
|
|
|
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
|
|
|
WHERE t.relname = 'supplier_projects' AND c.conname = 'chk_supplier_projects_platform'"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect($check)->not->toBeNull();
|
|
|
|
|
expect($check->def)
|
|
|
|
|
->toContain("'B1'")
|
|
|
|
|
->toContain("'B2'")
|
|
|
|
|
->toContain("'B3'");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('supplier_projects sync_status check constraint', function () {
|
|
|
|
|
$check = DB::selectOne(
|
|
|
|
|
"SELECT pg_get_constraintdef(c.oid) AS def
|
|
|
|
|
FROM pg_constraint c
|
|
|
|
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
|
|
|
WHERE t.relname = 'supplier_projects' AND c.conname = 'chk_supplier_projects_sync_status'"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect($check)->not->toBeNull();
|
|
|
|
|
expect($check->def)
|
|
|
|
|
->toContain("'pending'")
|
|
|
|
|
->toContain("'ok'")
|
|
|
|
|
->toContain("'failed'");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('supplier_projects has NO RLS policy (relrowsecurity = false)', function () {
|
|
|
|
|
$row = DB::selectOne(
|
|
|
|
|
"SELECT relrowsecurity
|
|
|
|
|
FROM pg_class
|
|
|
|
|
WHERE relname = 'supplier_projects' AND relkind = 'r'"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect($row)->not->toBeNull();
|
|
|
|
|
expect($row->relrowsecurity)->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('supplier_projects has REVOKE ALL on crm_app_user (no privileges in role_table_grants)', function () {
|
|
|
|
|
$rows = DB::select(
|
|
|
|
|
"SELECT privilege_type
|
|
|
|
|
FROM information_schema.role_table_grants
|
|
|
|
|
WHERE table_name = 'supplier_projects'
|
|
|
|
|
AND grantee = 'crm_app_user'"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect($rows)->toBe([]);
|
|
|
|
|
});
|