168 lines
13 KiB
PHP
168 lines
13 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Opportunity;
|
|
use App\Models\User;
|
|
use App\Services\CorridorAgribusinessOpportunities;
|
|
use App\Services\InvestmentCorridorContext;
|
|
use App\Services\InvestmentZones;
|
|
use App\Services\InvestorContext;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
final class InvestmentConsumerScopeTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
private User $actor;
|
|
|
|
private string $id;
|
|
|
|
private array $choices;
|
|
|
|
private array $parks;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
$this->withoutMiddleware(ThrottleRequests::class);
|
|
$db = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
|
$this->assertSame('simulator_fiscal_delta_20261021_test', $db->name);
|
|
$this->assertSame(55441, (int) $db->port);
|
|
config(['investor-path.enabled' => true, 'investor-path.private_preparation' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
|
|
$this->actor = User::factory()->create();
|
|
$this->actingAs($this->actor);
|
|
$this->id = $this->postJson('/investimentos', ['title' => 'SYNTHETIC QA — âmbito específico', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$corridors = app(InvestmentCorridorContext::class);
|
|
$records = [];
|
|
foreach (['beira', 'limpopo'] as $corridor) {
|
|
$entry = collect(app(CorridorAgribusinessOpportunities::class)->entries())->firstWhere('corridor', config('investment_corridors.'.$corridor.'.name'));
|
|
$records[] = Opportunity::where('management_creation_key', $entry['key'])->firstOrFail();
|
|
}
|
|
$sofala = collect($corridors->provinces('beira'))->firstWhere('source_code', '07')->id;
|
|
$gaza = $corridors->provinces('limpopo')[0]->id;
|
|
$manica = collect($corridors->provinces('beira'))->firstWhere('name', 'Manica')->id;
|
|
$template = app(InvestmentZones::class)->visible()->whereIn('category', ['industrial_park', 'zee', 'zfi'])->firstOrFail();
|
|
foreach ([$sofala, $gaza, $manica] as $territory) {
|
|
$p = $template->replicate();
|
|
$p->code = 'SYNTHETIC-QA-'.Str::uuid();
|
|
$p->name = 'SYNTHETIC QA — parque '.$territory;
|
|
$p->primary_territory_id = $territory;
|
|
$p->save();
|
|
$this->parks[] = $p;
|
|
}
|
|
$this->choices = [['source' => 'opportunity:'.$records[0]->id, 'corridor_key' => 'beira', 'territory' => 'territory:'.$sofala, 'park' => 'park:'.$this->parks[0]->id, 'activities' => ['production']], ['source' => 'opportunity:'.$records[1]->id, 'corridor_key' => 'limpopo', 'territory' => 'territory:'.$gaza, 'park' => 'park:'.$this->parks[1]->id, 'activities' => ['trade']]];
|
|
$this->confirm();
|
|
}
|
|
|
|
private function confirm(): array
|
|
{
|
|
$v = $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json();
|
|
$body = ['version' => $v['case']['version'], 'selection' => [], 'opportunity_choices' => $this->choices];
|
|
$plan = $this->postJson('/investimentos/'.$this->id.'/contexto/previa', $body)->assertOk()->json();
|
|
$this->postJson('/investimentos/'.$this->id.'/contexto/confirmar', [...$body, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
|
|
|
return $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json();
|
|
}
|
|
|
|
public function test_second_only_change_updates_its_parks_and_all_consumer_proposals_without_replacing_first(): void
|
|
{
|
|
$before = $this->getJson('/investimentos/'.$this->id.'/contexto')->json('context');
|
|
$key = $this->choices[1]['source'];
|
|
$this->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($key))->assertOk()->assertJsonPath('prefill.land.parcels.0.province_id', $this->parks[1]->primary_territory_id);
|
|
$pending = $this->choices;
|
|
$pending[1] = [...$pending[1], 'corridor_key' => 'beira', 'territory' => null, 'park' => null];
|
|
$this->postJson('/investimentos/'.$this->id.'/contexto/rascunho', ['version' => $this->getJson('/investimentos/'.$this->id.'/contexto')->json('case.version'), 'selection' => [], 'opportunity_choices' => $pending, 'draft_sequence' => 1000000])->assertOk();
|
|
$this->get('/investimentos/'.$this->id.'/contexto/editar')->assertOk()->assertInertia(fn ($p) => $p->where('requestedChoices.1.territory', null));
|
|
$this->choices[1] = [...$this->choices[1], 'corridor_key' => 'beira', 'territory' => 'territory:'.$this->parks[2]->primary_territory_id, 'park' => 'park:'.$this->parks[2]->id, 'confirm_location_change' => true, 'location_reason' => 'Alteração expressa apenas da segunda oportunidade para comparação QA.'];
|
|
$view = $this->confirm();
|
|
$this->assertSame($before['opportunity_choices'][0], $view['context']['opportunity_choices'][0]);
|
|
$this->assertSame($this->parks[2]->id, $view['context']['opportunity_choices'][1]['park']['id']);
|
|
$scope = $this->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($key))->assertOk();
|
|
$scope->assertJsonPath('prefill.land.parcels.0.province_id', $this->parks[2]->primary_territory_id)->assertJsonPath('prefill.desk.territory_id', $this->parks[2]->primary_territory_id)->assertJsonPath('prefill.submission.location', 'Manica');
|
|
$snapshot = json_decode($scope->json('prefill.simulator.context_applicability_snapshot'), true);
|
|
$this->assertSame($this->parks[2]->id, $snapshot['zone']['id']);
|
|
foreach ($snapshot['requirements_assessment']['requirements'] as $r) {
|
|
$this->assertSame($key, $r['opportunity_key']);
|
|
}
|
|
$this->assertCount(1, $snapshot['opportunity_choices']);
|
|
$this->choices[1]['park'] = 'park:'.$this->parks[1]->id;
|
|
$this->postJson('/investimentos/'.$this->id.'/contexto/previa', ['version' => $view['case']['version'], 'selection' => [], 'opportunity_choices' => $this->choices])->assertUnprocessable();
|
|
}
|
|
|
|
public function test_ambiguous_scope_proposes_only_common_data_and_rejects_foreign_identity(): void
|
|
{
|
|
$view = $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json();
|
|
$this->assertSame('SYNTHETIC QA — âmbito específico', $view['prefill']['land']['project_reference']);
|
|
$this->assertEmpty($view['prefill']['land']['parcels'][0]);
|
|
$this->assertArrayNotHasKey('territory_id', $view['prefill']['desk']);
|
|
$this->assertArrayNotHasKey('location', $view['prefill']['submission']);
|
|
$this->getJson('/investimentos/'.$this->id.'/contexto?opportunity=opportunity:99999999')->assertUnprocessable();
|
|
$this->actingAs(User::factory()->create())->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($this->choices[1]['source']))->assertForbidden();
|
|
}
|
|
|
|
public function test_land_desk_and_manifestation_save_and_recover_canonical_second_identity_and_preserve_deliberate_fields(): void
|
|
{
|
|
$first = $this->getJson('/investimentos/'.$this->id.'/contexto')->json('context.opportunity_choices.0');
|
|
$this->choices[1] = [...$this->choices[1], 'corridor_key' => 'beira', 'territory' => 'territory:'.$this->parks[2]->primary_territory_id, 'park' => 'park:'.$this->parks[2]->id, 'confirm_location_change' => true, 'location_reason' => 'Alteração expressa apenas da segunda oportunidade para comparação QA.'];
|
|
$this->confirm();
|
|
$key = $this->choices[1]['source'];
|
|
$scope = app(InvestorContext::class)->prefill($this->getJson('/investimentos/'.$this->id.'/contexto')->json('context'), $key);
|
|
$land = $this->postJson('/investidor/terra-duat', ['payload' => [...$scope['land'], 'investor_path_case_id' => $this->id, 'context_opportunity' => $key, 'context_opportunity_snapshot' => 'forged', 'purpose' => 'Texto deliberado do requerente']])->assertCreated()->json('id');
|
|
$landPayload = $this->getJson('/investidor/terra-duat/'.$land)->assertOk()->json('case.payload');
|
|
$this->assertSame('Texto deliberado do requerente', $landPayload['purpose']);
|
|
$this->assertSame($key, json_decode($landPayload['context_opportunity_snapshot'], true)['choice']['opportunity']['key']);
|
|
$this->get('/investidor/terra-duat/'.$land.'/imprimir')->assertOk()->assertInertia(fn ($p) => $p->where('case.payload.context_opportunity', $key));
|
|
$desk = app(VirtualDesk::class);
|
|
$service = DB::table('desk_service_versions')->where('status', 'published')->firstOrFail();
|
|
$request = $desk->createRequest($this->actor, $this->id, $service->id, (string) Str::uuid(), $key);
|
|
$payload = $desk->decode($request->payload);
|
|
$payload['context_opportunity_snapshot'] = 'forged';
|
|
if (isset($desk->definition($request)['fields']['description'])) {
|
|
$payload['description'] = 'Descrição deliberada de QA';
|
|
}
|
|
$saved = $desk->save($this->actor, $request->id, ['version' => $request->version, 'payload' => $payload]);
|
|
$recovered = $desk->decode($desk->request($this->actor, $saved->id)->payload);
|
|
$this->assertSame($key, $recovered['context_opportunity']);
|
|
$this->assertSame($this->parks[2]->id, json_decode($recovered['context_opportunity_snapshot'], true)['choice']['park']['id']);
|
|
$rendered = view('desk.receipt', ['record' => (object) [...(array) $saved, 'protocol' => 'SYNTHETIC-QA-SCOPE', 'submitted_at' => now()->toDateTimeString()], 'snapshot' => ['payload' => $recovered], 'definition' => $desk->definition($saved)])->render();
|
|
$this->assertStringContainsString('Manica', $rendered);
|
|
$this->assertStringContainsString($key, $rendered);
|
|
$submission = $this->postJson('/investidor/manifestacoes', ['opportunity_id' => (int) substr($key, 12)])->assertCreated()->json('id');
|
|
$v = $this->getJson('/investimentos/'.$this->id.'/contexto')->json('case.version');
|
|
$bound = $this->postJson('/investidor/manifestacoes/'.$submission.'/contexto', ['case_id' => $this->id, 'case_version' => $v, 'version' => 1])->assertOk()->json('version');
|
|
$this->putJson('/investidor/manifestacoes/'.$submission, ['version' => $bound, 'payload' => [...$scope['submission'], 'context_opportunity' => $key, 'context_opportunity_snapshot' => 'forged', 'objective' => 'Objectivo deliberado do investidor']])->assertOk();
|
|
$p = $this->getJson('/investidor/manifestacoes/'.$submission)->assertOk()->json('submission.payload');
|
|
$this->assertSame($key, $p['context_opportunity']);
|
|
$this->assertSame('Objectivo deliberado do investidor', $p['objective']);
|
|
$this->assertSame($key, json_decode($p['context_opportunity_snapshot'], true)['choice']['opportunity']['key']);
|
|
$this->assertSame('Manica', json_decode($p['context_opportunity_snapshot'], true)['choice']['territory']['title']);
|
|
$this->assertSame((int) substr($key, 12), $this->getJson('/investidor/manifestacoes/'.$submission)->json('submission.opportunity_id'));
|
|
$revision = $this->getJson('/investidor/manifestacoes/'.$submission.'/revisions/3')->assertOk()->json('snapshot.payload');
|
|
$this->assertSame($key, $revision['context_opportunity']);
|
|
$this->assertSame($first, $this->getJson('/investimentos/'.$this->id.'/contexto')->json('context.opportunity_choices.0'));
|
|
}
|
|
|
|
public function test_returned_request_keeps_its_locked_opportunity_snapshot_when_only_description_is_editable(): void
|
|
{
|
|
$desk = app(VirtualDesk::class);
|
|
$service = DB::table('desk_service_versions')->where('status', 'published')->firstOrFail();
|
|
$row = $desk->createRequest($this->actor, $this->id, $service->id, (string) Str::uuid(), $this->choices[1]['source']);
|
|
$payload = $desk->decode($row->payload);
|
|
$snapshot = $payload['context_opportunity_snapshot'];
|
|
DB::table('desk_requests')->where('id', $row->id)->update(['status' => 'returned', 'editable_fields' => json_encode(['description']), 'protocol' => 'SYNTHETIC-QA-'.Str::uuid(), 'submitted_at' => now(), 'submitted_snapshot' => $desk->encode(['payload' => $payload, 'investment_context' => ['revision' => 1]])]);
|
|
$this->choices[1] = [...$this->choices[1], 'corridor_key' => 'beira', 'territory' => 'territory:'.$this->parks[2]->primary_territory_id, 'park' => 'park:'.$this->parks[2]->id, 'confirm_location_change' => true, 'location_reason' => 'Mudança apenas da segunda oportunidade após devolução do pedido QA.'];
|
|
$this->confirm();
|
|
$saved = $desk->save($this->actor, $row->id, ['version' => $row->version, 'payload' => [...$payload, 'description' => 'Correcção deliberada apenas da descrição']]);
|
|
$after = $desk->decode($saved->payload);
|
|
$this->assertSame($snapshot, $after['context_opportunity_snapshot']);
|
|
$this->assertSame($payload['context_opportunity'], $after['context_opportunity']);
|
|
}
|
|
}
|