otherConstraints = $this->unrelatedConstraints(); if (! Schema::hasColumn('investment_legal_sources', 'fiscal_acceptance')) { (require database_path('migrations/2026_10_21_130000_correct_fiscal_maintenance_actor_constraints.php'))->up(); } $this->maintainer = User::factory()->withTwoFactor()->create(); config(['portal.managers' => [$this->maintainer->email], 'simulation-tax-governance.editors' => [], 'simulation-tax-governance.reviewers' => [], 'simulation-tax-governance.approvers' => [], 'simulation-tax-governance.publishers' => [], 'simulation-fiscal-consumer.enabled' => false]); } private function unrelatedConstraints(): array { return array_map(fn ($row) => (array) $row, DB::select("SELECT conrelid::regclass::text AS relation, conname, pg_get_constraintdef(oid) AS definition FROM pg_constraint WHERE connamespace='public'::regnamespace AND conrelid NOT IN ('public.investment_legal_sources'::regclass,'public.simulation_tax_rule_versions'::regclass) ORDER BY relation,conname")); } private function sourceInput(): array { return ['title' => 'Synthetic QA official source curation; not a destination record', 'url' => 'https://www.inm.gov.mz/', 'diploma' => 'Lei 22/2022', 'article' => 'Artigo 17(1)', 'competent_entity' => 'Assembleia da República', 'valid_from' => '2023-01-01', 'valid_until' => null, 'sha256' => str_repeat('a', 64), 'citation' => 'QA fixture: PDF 3, art.17(1); no legal opinion.', 'official_origin' => 'QA fixture of an identified official publication; not a new legal validation.']; } private function source(): string { return $this->actingAs($this->maintainer)->postJson('/api/v2/investment/admin/tax-rule-sources', $this->sourceInput()) ->assertCreated()->assertJsonPath('fiscal_acceptance.legal_opinion', false) ->assertJsonPath('fiscal_acceptance.independent_review', false) ->assertJsonPath('owner_id', $this->maintainer->id)->assertJsonPath('validator_id', null) ->assertJsonPath('validation_status', 'pending')->assertJsonPath('fiscal_acceptance.responsible_user_id', $this->maintainer->id)->json('id'); } private function rule(string $source): string { $payload = json_decode(file_get_contents(database_path('qa/iva-general-net-v1.json')), true, flags: JSON_THROW_ON_ERROR); $payload['legal_source_id'] = $source; $payload['source_sha256'] = str_repeat('a', 64); return $this->postJson('/api/v2/investment/admin/tax-rules', $payload)->assertCreated()->json('id'); } public function test_one_existing_administrator_completes_fiscal_curation_with_responsibility_and_history(): void { $simulationCount = DB::table('simulations')->count(); $source = $this->source(); $id = $this->rule($source); $evidence = 'QA technical parameter maintenance by the authorised same actor; not independent review or legal validation.'; $this->postJson('/api/v2/investment/admin/tax-rules/'.$id.'/publish', ['version_no' => 1, 'evidence' => $evidence])->assertUnprocessable(); foreach (['submit', 'review', 'approve', 'publish'] as $offset => $action) { $this->postJson('/api/v2/investment/admin/tax-rules/'.$id.'/'.$action, ['version_no' => $offset + 1, 'evidence' => $evidence])->assertOk()->assertJsonPath('version_no', $offset + 2); } $rule = SimulationTaxRuleVersion::findOrFail($id); foreach (['author_id', 'reviewer_id', 'approver_id', 'publisher_id'] as $column) { $this->assertSame($this->maintainer->id, (int) $rule->$column); } $this->assertSame('published', $rule->status); $this->assertFalse(InvestmentLegalSource::whereKey($source)->where('validation_status', 'validated')->exists()); // Source eligibility is checked through the existing resolver, without executing a formula. DB::statement('CREATE ROLE qa_fiscal_maintenance_read_20261021 NOLOGIN NOSUPERUSER NOBYPASSRLS'); DB::statement('GRANT USAGE ON SCHEMA public TO qa_fiscal_maintenance_read_20261021'); DB::statement('GRANT SELECT ON simulation_tax_rule_versions, investment_legal_sources TO qa_fiscal_maintenance_read_20261021'); DB::statement("SELECT set_config('app.simulation_tax_governance', 'off', true)"); DB::statement('SET LOCAL ROLE qa_fiscal_maintenance_read_20261021'); try { $this->assertSame(1, SimulationTaxRuleVersion::whereKey($id)->count()); $parameters = app(SimulationTaxParameterResolver::class)->resolvePublishedForPeriod('IVA', '2026-10-21', '2026-10-21', ['currency' => 'MZN', 'operation' => 'internal_general_net']); $this->assertSame('resolved', $parameters[0]['status']); } finally { DB::statement('RESET ROLE'); } $this->assertSame(1, (int) $rule->source_version_no); $this->assertSame(str_repeat('a', 64), $rule->source_sha256); $events = DB::table('simulation_tax_rule_events')->where('rule_version_id', $id)->orderBy('version_no')->get(); $this->assertSame(['created', 'submit', 'review', 'approve', 'publish'], $events->pluck('action')->all()); foreach ($events as $event) { $this->assertSame($this->maintainer->id, (int) $event->actor_user_id); $canonical = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$event->snapshot])->canonical; $this->assertSame(hash('sha256', $canonical), $event->sha256); } $this->assertDatabaseHas('audit_events', ['entity_id' => $source, 'actor_user_id' => $this->maintainer->id, 'action' => 'official_source_acceptance']); $this->getJson('/api/v2/investment/admin/tax-rules/'.$id)->assertOk()->assertJsonPath('id', $id); $this->getJson('/api/v1/fiscal-consumer')->assertOk()->assertJsonPath('data.enabled', false); $this->assertSame($simulationCount, DB::table('simulations')->count()); $this->assertSame($this->otherConstraints, $this->unrelatedConstraints()); $this->postJson('/api/v2/investment/admin/tax-rules/'.$id.'/suspend', ['version_no' => 5, 'evidence' => $evidence])->assertOk(); $this->assertSame(6, DB::table('simulation_tax_rule_events')->where('rule_version_id', $id)->count()); } public function test_investor_and_unconfirmed_mfa_cannot_maintain_fiscal_parameters(): void { $path = '/api/v2/investment/admin/tax-rule-sources'; $this->postJson($path, $this->sourceInput())->assertUnauthorized(); $this->actingAs(User::factory()->withTwoFactor()->create())->postJson($path, $this->sourceInput())->assertForbidden(); $withoutMfa = User::factory()->create(); config(['portal.managers' => [$withoutMfa->email]]); $this->actingAs($withoutMfa)->postJson($path, $this->sourceInput())->assertForbidden(); config(['portal.managers' => [$this->maintainer->email], 'portal.profile_access_enabled' => true]); app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), User::factory()->withTwoFactor()->create(), ['user_id' => $this->maintainer->id, 'role' => 'auditor', 'organization_id' => null, 'opportunity_types' => ['investment_project'], 'source' => 'synthetic-qa-fiscal-control']); $this->actingAs($this->maintainer)->postJson($path, $this->sourceInput())->assertForbidden(); } public function test_evidence_source_version_locking_and_immutable_events_remain_required(): void { $this->actingAs($this->maintainer)->postJson('/api/v2/investment/admin/tax-rule-sources', [...$this->sourceInput(), 'sha256' => ''])->assertUnprocessable(); $source = $this->source(); $wrong = json_decode(file_get_contents(database_path('qa/iva-general-net-v1.json')), true, flags: JSON_THROW_ON_ERROR); $wrong['legal_source_id'] = $source; $wrong['source_sha256'] = str_repeat('b', 64); $this->postJson('/api/v2/investment/admin/tax-rules', $wrong)->assertUnprocessable(); $id = $this->rule($source); $path = '/api/v2/investment/admin/tax-rules/'.$id.'/submit'; $this->postJson($path, ['version_no' => 1, 'evidence' => 'short'])->assertUnprocessable(); $this->postJson($path, ['version_no' => 2, 'evidence' => 'QA evidence and correct actor; stale version.'])->assertConflict(); InvestmentLegalSource::whereKey($source)->update(['version_no' => 2]); $this->postJson($path, ['version_no' => 1, 'evidence' => 'QA evidence and correct actor; source changed.'])->assertUnprocessable(); $this->assertSame('draft', SimulationTaxRuleVersion::findOrFail($id)->status); try { DB::transaction(fn () => DB::table('simulation_tax_rule_events')->where('rule_version_id', $id)->update(['action' => 'tampered'])); $this->fail('Audit must stay immutable.'); } catch (QueryException) { $this->assertDatabaseHas('simulation_tax_rule_events', ['rule_version_id' => $id, 'action' => 'created']); } try { (require database_path('migrations/2026_10_21_130000_correct_fiscal_maintenance_actor_constraints.php'))->down(); $this->fail('Rollback must preserve legitimate fiscal curation.'); } catch (\LogicException $exception) { $this->assertStringContainsString('conservar', $exception->getMessage()); } } public function test_other_source_validation_keeps_its_independence_and_active_migration_is_refused(): void { $data = $this->sourceInput(); unset($data['sha256'], $data['citation'], $data['official_origin']); $source = InvestmentLegalSource::create([...$data, 'owner_id' => $this->maintainer->id, 'validation_status' => 'pending']); config(['investment-zones.legal_reviewers' => [$this->maintainer->email]]); $this->actingAs($this->maintainer)->postJson('/api/v2/investment/admin/legal-sources/'.$source->id.'/validate', ['version_no' => 1])->assertForbidden(); try { DB::transaction(fn () => $source->update(['validator_id' => $this->maintainer->id, 'validated_at' => now(), 'validation_status' => 'validated'])); $this->fail('Other source curation must retain its check.'); } catch (QueryException) { $this->assertSame('pending', $source->fresh()->validation_status); } config(['database.connections.pgsql.url' => 'not-an-isolated-qa-target']); $this->expectException(\LogicException::class); (require database_path('migrations/2026_10_21_130000_correct_fiscal_maintenance_actor_constraints.php'))->up(); } public function test_scoped_administration_is_reused_without_four_email_designations(): void { config(['portal.managers' => []]); config(['portal.profile_access_enabled' => true]); $second = User::factory()->withTwoFactor()->create(); // Pre-existing-authority fixture, exclusively inside the reverted QA transaction. DB::table('portal_superuser_pair')->updateOrInsert(['id' => 1], [ 'first_user_id' => $this->maintainer->id, 'second_user_id' => $second->id, 'approved_by' => $second->id, 'version' => 1, 'created_at' => now(), 'updated_at' => now(), ]); $this->actingAs($this->maintainer)->withSession(['portal.mfa_actor' => $this->maintainer->id, 'portal.mfa_at' => time()]); $source = $this->source(); $id = $this->rule($source); foreach (['submit', 'review', 'approve', 'publish'] as $offset => $action) { $this->postJson('/api/v2/investment/admin/tax-rules/'.$id.'/'.$action, ['version_no' => $offset + 1, 'evidence' => 'QA existing scoped administrator completes fiscal maintenance; not independent legal validation.'])->assertOk(); } $this->withSession(['portal.mfa_at' => time() - 901])->postJson('/api/v2/investment/admin/tax-rule-sources', $this->sourceInput())->assertForbidden(); } public function test_unused_correction_can_be_rolled_back_and_reapplied_only_in_qa(): void { $migration = require database_path('migrations/2026_10_21_130000_correct_fiscal_maintenance_actor_constraints.php'); $migration->down(); $this->assertFalse(Schema::hasColumn('investment_legal_sources', 'fiscal_acceptance')); $migration->up(); $this->assertTrue(Schema::hasColumn('investment_legal_sources', 'fiscal_acceptance')); $this->assertSame($this->otherConstraints, $this->unrelatedConstraints()); } }