Files
Investidor-V2/tests/Feature/FundingDirectoryTest.php
T

152 lines
10 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use App\Services\FundingDossierPreparation;
use App\Services\FundingProgrammeReferences;
use App\Services\FundingProgrammes;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
final class FundingDirectoryTest extends TestCase
{
use DatabaseTransactions;
private User $editor;
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
$identity = DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $identity->name);
$this->assertSame(55441, (int) $identity->port);
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
Http::preventStrayRequests();
$this->editor = User::factory()->create();
config(['portal.managers' => [$this->editor->email]]);
$this->actingAs($this->editor);
}
private function prepared(string $code): string
{
$references = app(FundingProgrammeReferences::class);
$plan = $references->preview($this->editor, $code);
return $references->prepare($this->editor, $code, $plan['hash'])['id'];
}
private function publish(string $id): array
{
$service = app(FundingProgrammes::class);
$plan = $service->preview($this->editor, $id);
$service->publish($this->editor, $id, ['version' => $plan['version'], 'hash' => $plan['hash'], 'confirm' => true, 'reason' => 'SYNTHETIC QA editorial publication; rolled back, not live content.']);
return $service->published($id);
}
private function province(string $name): string
{
return DB::table('geo_locations')->where('location_type', 'province')->where('name', $name)->value('id');
}
public function test_provided_content_is_queryable_with_sources_contacts_coverage_and_no_automatic_publication_or_duplicates(): void
{
$references = app(FundingProgrammeReferences::class);
$service = app(FundingProgrammes::class);
$before = $service->publicQuery()->count();
$codes = ['bci', 'millennium-bim', 'absa', 'standard-bank', 'moza', 'bni', 'gapi', 'fecop', 'semeia', 'fcid-viii', 'fae'];
$records = [];
foreach ($codes as $code) {
$id = $this->prepared($code);
$this->assertSame($id, $this->prepared($code));
$this->assertNull(DB::table('funding_programmes')->where('id', $id)->value('published_version_id'));
$this->assertSame($before + count($records), $service->publicQuery()->count());
$records[$code] = $this->publish($id);
$this->assertNotEmpty($records[$code]['payload']['source_url']);
}
$this->assertSame($before + 11, $service->publicQuery()->count());
$this->assertCount(2, $records['bni']['payload']['products']);
$this->assertContains('Maputo Cidade', array_column($records['bni']['payload']['locations'][0]['territory_path'], 'name'));
$this->assertSame('national', $records['bni']['payload']['products'][0]['coverage']);
$this->assertCount(6, $records['fcid-viii']['payload']['territory_ids']);
$this->assertSame('closed', $records['semeia']['payload']['windows'][0]['state']);
$this->assertSame('unknown', $records['fae']['payload']['coverage']);
$filters = ['territory' => $this->province('Cabo Delgado'), 'need' => 'investment'];
$found = $service->catalogueQuery($filters)->pluck('p.code')->all();
$this->assertContains('bci', $found);
$this->assertContains('bni', $found);
$this->assertNotContains('fcid-viii', $found);
$this->get('/fundos?'.http_build_query($filters))->assertOk()->assertInertia(fn ($p) => $p->component('funding/index')->where('filters.need', 'investment'));
$this->get('/fundos/'.$records['bni']['id'])->assertOk()->assertInertia(fn ($p) => $p->component('funding/show')->where('record.payload.contacts.1.value', '+258 21 498 581'));
file_put_contents(base_path('outputs/funding-directory-20261110/content-qa.json'), json_encode(['synthetic_editorial_publication_rolled_back' => true, 'provided_source_sha256' => '065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617', 'records' => $records, 'cabo_delgado_investment_results' => $found], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
Http::assertNothingSent();
}
public function test_location_and_need_must_match_the_same_product_and_unknown_is_explicit(): void
{
$service = app(FundingProgrammes::class);
$draft = app(FundingProgrammeReferences::class)->preview($this->editor, 'bci')['draft'];
$draft['institution_key'] = 'synthetic-same-product';
$draft['title'] = 'SYNTHETIC QA — cross product scope';
$draft['products'][0]['coverage'] = 'territorial';
$draft['products'][0]['territory_ids'] = [$this->province('Maputo Província')];
$draft['products'][0]['needs'] = ['investment'];
$other = $draft['products'][0];
$other['code'] = 'synthetic-working';
$other['needs'] = ['working_capital'];
$other['territory_ids'] = [$this->province('Nampula')];
$draft['products'][] = $other;
$id = $service->save($this->editor, null, ['version' => 0, 'code' => 'synthetic-cross', 'reason' => 'SYNTHETIC QA cross product coverage fixture.', 'draft' => $draft]);
$this->publish($id);
$base = ['territory' => $this->province('Nampula')];
$this->assertFalse($service->catalogueQuery([...$base, 'need' => 'investment'])->where('p.id', $id)->exists());
$this->assertTrue($service->catalogueQuery([...$base, 'need' => 'working_capital'])->where('p.id', $id)->exists());
$draft['products'][0]['coverage'] = 'unknown';
$draft['products'][0]['territory_ids'] = [];
$draft['products'][0]['availability'] = 'unknown';
$service->save($this->editor, $id, ['version' => 1, 'code' => 'synthetic-cross', 'reason' => 'SYNTHETIC QA unknown coverage, not absence.', 'draft' => $draft]);
$this->publish($id);
$this->assertTrue($service->catalogueQuery([...$base, 'need' => 'investment'])->where('p.id', $id)->exists());
$this->assertFalse($service->catalogueQuery([...$base, 'need' => 'investment', 'certainty' => 'documented'])->where('p.id', $id)->exists());
$this->expectException(ValidationException::class);
$service->save($this->editor, null, ['version' => 0, 'code' => 'synthetic-duplicate', 'reason' => 'SYNTHETIC QA duplicate stable institution guard.', 'draft' => $draft]);
}
public function test_selection_continues_in_existing_dossier_and_recovers_snapshot_without_sending_an_application(): void
{
$id = $this->prepared('bni');
$record = $this->publish($id);
$caseId = $this->postJson('/investimentos', ['title' => 'SYNTHETIC QA funding preparation', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
$context = app(FundingDossierPreparation::class)->context($this->editor, $caseId, null);
$this->get('/fundos?'.http_build_query(['context' => $caseId, 'territory' => $this->province('Nampula'), 'need' => 'investment']))->assertOk()->assertInertia(fn ($page) => $page->component('funding/index')->where('items.data', fn ($rows) => collect($rows)->contains('id', $id)));
$this->get('/fundos/'.$id.'?'.http_build_query(['context' => $caseId, 'territory' => $this->province('Nampula'), 'need' => 'investment']))->assertOk()->assertInertia(fn ($page) => $page->component('funding/show')->where('record.id', $id)->where('dossier.id', $caseId)->where('record.payload.contacts.1.value', '+258 21 498 581'));
$body = ['context' => $caseId, 'version' => $context['version'], 'version_id' => $record['version_id'], 'product' => 'bni-oferta', 'need' => 'investment', 'territory' => $this->province('Nampula'), 'operation_key' => (string) Str::uuid(), 'consent' => true];
$saved = $this->postJson('/fundos/'.$id.'/dossier', $body)->assertOk()->json();
$this->assertCount(1, $saved['selections']);
$this->assertSame($record['version_id'], $saved['selections'][0]['version_id']);
$this->postJson('/fundos/'.$id.'/dossier', $body)->assertOk();
$this->postJson('/fundos/'.$id.'/dossier', [...$body, 'operation_key' => (string) Str::uuid()])->assertOk()->assertJsonCount(1, 'selections');
$this->postJson('/fundos/'.$id.'/dossier', [...$body, 'need' => 'equity'])->assertConflict();
$this->postJson('/fundos/'.$id.'/dossier', [...$body, 'opportunity' => 'opportunity:not-in-this-dossier', 'operation_key' => (string) Str::uuid()])->assertUnprocessable();
$this->assertSame(1, DB::table('desk_events')->where('case_id', $caseId)->where('action', 'funding_preparation_saved')->count());
$this->getJson('/investimentos/'.$caseId)->assertOk()->assertJsonCount(1, 'funding_selections');
$service = app(FundingProgrammes::class);
$service->withdraw($this->editor, $id, $record['version'], 'SYNTHETIC QA withdrawal preserves consulted reference.');
$recovered = app(FundingDossierPreparation::class)->context($this->editor, $caseId, null);
$this->assertSame($saved['selections'], $recovered['selections']);
$this->actingAs(User::factory()->create())->postJson('/fundos/'.$id.'/dossier', $body)->assertForbidden();
$this->actingAs($this->editor);
DB::table('desk_cases')->where('id', $caseId)->update(['closed_at' => now()]);
$this->postJson('/fundos/'.$id.'/dossier', $body)->assertConflict();
$this->assertCount(1, app(FundingDossierPreparation::class)->context($this->editor, $caseId, null)['selections']);
file_put_contents(base_path('outputs/funding-directory-20261110/dossier-qa.json'), json_encode(['synthetic' => true, 'recovered' => $recovered, 'one_native_event' => true, 'foreign_actor_refused' => true, 'closed_dossier_read_only' => true, 'no_external_submission' => true], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
Http::assertNothingSent();
}
}