f = receiptFixture(); $this->actingAs($this->f['issuer']); Http::fake(); }); function prepareAiPlan(object $test, array $changes = []): void { $plan = app(AiPreparationPlan::class)->checked([...aiPlanInput(), ...$changes]); $test->f['tenant']->update(['intelligence_policy' => ['revision' => 1, 'configured' => true, 'procurement_brief' => false, 'people_brief' => true, 'ai_plan' => $plan]]); } test('AI preparation filters facts by scope preserves the report and exposes no company budget or source hashes', function () { prepareAiPlan($this, ['data_scopes' => ['process_state']]); $this->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertOk()->assertInertia(fn (Assert $page) => $page ->where('brief', null)->where('report.requisition.id', $this->f['r']->id)->where('aiPreparation.status', 'draft') ->where('aiPreparation.external_execution_enabled', false)->where('aiPreparation.usage_checked', false) ->has('aiPreparation.facts', 1)->where('aiPreparation.facts.0.id', 'request') ->missing('aiPreparation.monthly_budget_usd')->missing('aiPreparation.facts.0.basis_sha256')); Http::assertNotSent(fn ($request) => ! in_array(parse_url($request->url(), PHP_URL_HOST), ['127.0.0.1', 'localhost'], true)); }); test('AI preparation respects financial access even when finance is allowed in the organisation plan', function () { prepareAiPlan($this, ['data_scopes' => ['financial_counts']]); $this->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertInertia(fn (Assert $page) => $page->where('aiPreparation.status', 'no_permitted_facts')->where('aiPreparation.facts', [])); $finance = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'purchasing_role' => PurchasingRole::Finance]); $this->actingAs($finance)->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertInertia(fn (Assert $page) => $page->where('aiPreparation.facts.0.id', 'finance')->has('aiPreparation.facts', 1)); Http::assertNotSent(fn ($request) => ! in_array(parse_url($request->url(), PHP_URL_HOST), ['127.0.0.1', 'localhost'], true)); }); test('AI preparation rejects an excluded domain and never truncates a payload to make it fit', function () { prepareAiPlan($this, ['domains' => ['people']]); $this->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertInertia(fn (Assert $page) => $page->where('aiPreparation.status', 'outside_scope')->where('aiPreparation.facts', [])); prepareAiPlan($this, ['data_scopes' => array_keys(AiPreparationPlan::SCOPES), 'max_input_bytes' => 512]); $this->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertInertia(fn (Assert $page) => $page->where('aiPreparation.status', 'input_exceeds_limit')->where('aiPreparation.facts', []) ->where('aiPreparation.input_bytes', fn ($bytes) => $bytes > 512)); Http::assertNotSent(fn ($request) => ! in_array(parse_url($request->url(), PHP_URL_HOST), ['127.0.0.1', 'localhost'], true)); }); test('AI preparation cannot disclose another company report or query an external provider', function () { $other = receiptFixture(); prepareAiPlan($this); $this->actingAs($this->f['issuer'])->get('/requisicoes/'.$other['r']->id.'/relatorio')->assertNotFound(); Http::assertNothingSent(); });