Files
SGF/scripts/benchmarks/ModuleAssociationAccessParityTest.php
T
2026-11-02 20:29:34 +02:00

75 lines
5.0 KiB
PHP

<?php
namespace Tests\Benchmarks;
use App\Modules\Identity\Http\ModuleRecordsController;
use Illuminate\Support\Facades\DB;
require_once __DIR__.'/ModuleAssociationCompareTest.php';
final class ModuleAssociationAccessParityTest extends ModuleAssociationLoadTest
{
public function test_synthetic_volume_and_query_plans(): void
{
$run = (string) getenv('SGF_ISOLATED_REGRESSION_RUN');
$this->assertMatchesRegularExpression('/^[a-f0-9]{12}$/D', $run);
$id = DB::selectOne("SELECT current_database() AS database,current_user AS role,inet_server_port() AS port,current_setting('cluster_name') AS cluster");
$this->assertSame('sgf_tempo_test', $id->database); $this->assertSame('sgf_test_runner', $id->role);
$this->assertSame('sgf_modular_regression_'.$run, $id->cluster); $this->assertSame(55439, (int) $id->port);
require_once base_path('../baseline-review.php');
config(['app.debug' => false]);
$f = $this->fixture(1000); $actor = $f['identity']; $report = ['date' => '2026-11-02', 'variants' => []];
foreach ([false, true] as $indexed) {
DB::beginTransaction();
try {
if ($indexed) {
DB::statement("CREATE INDEX sgf_qa_assoc_links ON identity_audit (tenant_id,company_id,(details->>'area'),(details->>'record_id'),(details->>'module_id'),id DESC) WHERE action IN ('module.record.associated','module.record.disassociated')");
DB::statement("CREATE INDEX sgf_qa_assoc_revision ON identity_audit (tenant_id,company_id,(details->>'area'),(details->>'record_id'),id DESC) WHERE action IN ('module.record.associated','module.record.disassociated','module.record.reviewed')");
}
$checks = [];
foreach (['reader', 'limited'] as $profile) {
$this->selectActor($actor, $profile);
foreach (['baseline', 'batch'] as $mode) { $this->useController($mode); $this->getJson('/api/module-records')->assertForbidden(); }
$checks[$profile] = 403;
}
$token = $this->selectActor($actor, 'manager');
foreach (['baseline', 'batch'] as $mode) {
$this->useController($mode);
$this->getJson('/api/module-records?unit_id='.$actor['hidden'])->assertForbidden();
$this->getJson('/api/module-records?per_page=101')->assertUnprocessable();
$this->withHeader('X-SGF-Context', 'obsolete')->getJson('/api/module-records')->assertConflict();
$this->withHeader('X-SGF-Context', $token);
}
$checks += ['unselected_unit' => 403, 'invalid_page_size' => 422, 'old_generation' => 409];
DB::table('permission_grants')->where('user_id', $actor['manager']->id)->where('company_id', $actor['company'])->where('permission', 'services.view')->delete();
$hashes = [];
foreach (['baseline', 'batch'] as $mode) {
$this->useController($mode);
$r = $this->getJson('/api/module-records?area=billing_drafts&per_page=25')->assertOk();
foreach ($r->json('records.billing_drafts') as $row) { $this->assertTrue($row['dependencies_restricted']); $this->assertSame([], $row['dependencies']); }
$hashes[] = hash('sha256', $r->getContent());
}
$this->assertSame($hashes[0], $hashes[1]);
$report['variants'][] = ['experimental_indexes' => $indexed, 'refusals_equal' => $checks, 'revoked_dependency_response_identical' => true, 'response_sha256' => $hashes[0]];
} finally { DB::rollBack(); }
}
$this->assertSame(0, (int) DB::selectOne("SELECT count(*) AS n FROM pg_indexes WHERE schemaname='public' AND indexname LIKE 'sgf_qa_assoc_%'")->n);
$report['indexes_rolled_back'] = true;
$output = getenv('SGF_ASSOCIATION_LOAD_OUTPUT');
if (!$output || realpath(dirname($output)) !== realpath(base_path('..'))) throw new \RuntimeException('Unexpected report directory');
file_put_contents($output, json_encode($report, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR).PHP_EOL);
}
private function selectActor(array $f, string $profile): string
{
$user = $f[$profile]; $this->actingAs($user)->withSession(['auth_version' => $user->auth_version]);
$token = $this->postJson('/api/context', ['tenant_id' => $f['tenant'], 'company_id' => $f['company'], 'unit_id' => $f['unit'], 'module_id' => null])->assertOk()->json('context.token');
$this->withHeader('X-SGF-Context', $token); return $token;
}
private function useController(string $mode): void
{
app()->bind(ModuleRecordsController::class, fn () => $mode === 'baseline' ? new BaselineAssociationController() : new ModuleRecordsController());
foreach (app('router')->getRoutes() as $route) if ($route->uri() === 'api/module-records') $route->flushController();
}
}