70 lines
3.8 KiB
PHP
70 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\TestCase;
|
|
|
|
class SkillsEducationSchemaTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function rejected(string $sql, string $state): void
|
|
{
|
|
DB::statement('SAVEPOINT invalid_skills_case');
|
|
try {
|
|
DB::statement($sql);
|
|
$this->fail('Invalid write accepted');
|
|
} catch (QueryException $e) {
|
|
$this->assertSame($state, $e->getCode());
|
|
} finally {
|
|
DB::statement('ROLLBACK TO SAVEPOINT invalid_skills_case');
|
|
}
|
|
}
|
|
|
|
public function test_master_types_private_defaults_and_immutable_audit(): void
|
|
{
|
|
$this->assertSame(24, DB::table('pg_tables')->where('schemaname', 'skills_edu')->count());
|
|
$missing = DB::selectOne(<<<'SQL'
|
|
SELECT count(*) AS n FROM pg_constraint c JOIN pg_namespace ns ON ns.oid=c.connamespace
|
|
WHERE ns.nspname='skills_edu' AND c.contype='f' AND NOT EXISTS (
|
|
SELECT 1 FROM pg_index i JOIN pg_class ix ON ix.oid=i.indexrelid JOIN pg_am am ON am.oid=ix.relam
|
|
WHERE i.indrelid=c.conrelid AND i.indisvalid AND i.indisready AND i.indpred IS NULL
|
|
AND am.amname='btree' AND i.indnkeyatts>=cardinality(c.conkey)
|
|
AND ARRAY(SELECT x FROM unnest(i.indkey::smallint[]) WITH ORDINALITY k(x,p) WHERE p<=cardinality(c.conkey) ORDER BY p)=c.conkey)
|
|
SQL);
|
|
$this->assertSame(0, (int) $missing->n);
|
|
$this->assertNull(DB::selectOne("SELECT to_regclass('skills_edu.skills') AS id")->id);
|
|
$this->assertSame('bigint', DB::selectOne("SELECT format_type(atttypid,atttypmod) AS type FROM pg_attribute WHERE attrelid='skills_edu.professional_profiles'::regclass AND attname='user_master_id'")->type);
|
|
$this->rejected("INSERT INTO skills_edu.skill_details(id,category) VALUES(gen_random_uuid(),'technical')", '23503');
|
|
$this->rejected("INSERT INTO skills_edu.professional_profiles(full_name,registration_mode,user_master_id) VALUES('Synthetic','individual',9223372036854775807)", '23503');
|
|
$profile = DB::selectOne("INSERT INTO skills_edu.professional_profiles(full_name,registration_mode) VALUES('Synthetic','individual') RETURNING id,visibility_level,status");
|
|
$this->assertSame('private', $profile->visibility_level);
|
|
$this->assertSame('draft', $profile->status);
|
|
$this->rejected("INSERT INTO skills_edu.profile_experiences(profile_id,job_title,started_on,ended_on) VALUES('$profile->id','Synthetic','2026-02-02','2026-01-01')", '23514');
|
|
DB::statement("INSERT INTO skills_edu.audit_events(action,object_type,result,correlation_id) VALUES('synthetic','test','success',gen_random_uuid())");
|
|
foreach (['UPDATE skills_edu.audit_events SET action=action', 'DELETE FROM skills_edu.audit_events', 'TRUNCATE skills_edu.audit_events'] as $sql) {
|
|
$this->rejected($sql, '23514');
|
|
}
|
|
}
|
|
|
|
public function test_reverse_order_rollback_reapply_and_nonempty_refusal(): void
|
|
{
|
|
$files = glob(database_path('migrations/2026_09_30_*_skills_education_*.php'));
|
|
foreach (array_reverse($files) as $file) {
|
|
(require $file)->down();
|
|
}
|
|
$this->assertNull(DB::selectOne("SELECT to_regnamespace('skills_edu') AS id")->id);
|
|
foreach ($files as $file) {
|
|
(require $file)->up();
|
|
}
|
|
$this->assertSame(24, DB::table('pg_tables')->where('schemaname', 'skills_edu')->count());
|
|
DB::statement("INSERT INTO skills_edu.audit_events(action,object_type,result,correlation_id) VALUES('synthetic','test','success',gen_random_uuid())");
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('destructive rollback refused');
|
|
(require end($files))->down();
|
|
}
|
|
}
|