158 lines
11 KiB
PHP
158 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Controllers\InvestmentPortfolioController;
|
|
use App\Models\Opportunity;
|
|
use App\Models\SimulationFormDraft;
|
|
use App\Models\User;
|
|
use App\Services\CorridorAgribusinessOpportunities;
|
|
use App\Services\InvestmentCatalogue;
|
|
use App\Services\InvestmentCorridorContext;
|
|
use App\Services\InvestmentOpportunityChoices;
|
|
use App\Services\SimulationFormDrafts;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
use Tests\TestCase;
|
|
|
|
final class InvestmentPortfolioContinuityTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
public function test_draft_version_guard_still_rejects_a_real_stale_version(): void
|
|
{
|
|
$draft = new SimulationFormDraft;
|
|
$draft->forceFill(['id' => (string) Str::uuid(), 'version_no' => 2]);
|
|
$service = app(SimulationFormDrafts::class);
|
|
$guard = new \ReflectionMethod($service, 'requireMatch');
|
|
$tag = $service->etag($draft);
|
|
foreach (['W/'.$tag, str_replace('-v2', '-v1', $tag)] as $invalid) {
|
|
$request = Request::create('/', 'PUT');
|
|
$request->headers->set('If-Match', $invalid);
|
|
try {
|
|
$guard->invoke($service, $request, $draft);
|
|
$this->fail('A mismatching version must be rejected');
|
|
} catch (HttpException $error) {
|
|
$this->assertSame(412, $error->getStatusCode());
|
|
}
|
|
}
|
|
$request = Request::create('/', 'PUT');
|
|
$request->headers->set('If-Match', $tag);
|
|
$this->assertNull($guard->invoke($service, $request, $draft));
|
|
}
|
|
|
|
public function test_beira_documentary_membership_opens_the_same_published_opportunities_without_inferring_by_province(): void
|
|
{
|
|
$portfolio = InvestmentPortfolioController::visible()->where('content->code', 'COR-02')->firstOrFail();
|
|
$service = app(CorridorAgribusinessOpportunities::class);
|
|
$ids = $service->portfolioOpportunityIds($portfolio);
|
|
$this->assertCount(7, $ids);
|
|
$this->get('/carteira/'.$portfolio->id)->assertOk()->assertInertia(fn (Assert $page) => $page->has('opportunities', 7));
|
|
$catalogueIds = app(InvestmentCatalogue::class)->query(['corridor_id' => $portfolio->corridor_id])->pluck('opportunities.id')->all();
|
|
$this->assertSame([], array_values(array_diff($ids, $catalogueIds)));
|
|
$opportunity = Opportunity::findOrFail($ids[0]);
|
|
$this->get('/oportunidades/'.$opportunity->slug.'?from_dossier='.$portfolio->id)->assertOk();
|
|
$origins = app(InvestmentOpportunityChoices::class)->portfolioOrigins([$portfolio->id], $opportunity->id);
|
|
$this->assertSame($portfolio->id, $origins[0]['id']);
|
|
$this->assertSame('corridor', $origins[0]['kind']);
|
|
$other = Opportunity::published()->whereNotIn('id', $ids)->firstOrFail();
|
|
$this->get('/oportunidades/'.$other->slug.'?from_dossier='.$portfolio->id)->assertNotFound();
|
|
$this->assertSame([], $service->portfolioOpportunityIds(InvestmentPortfolioController::visible()->whereNotNull('project_id')->firstOrFail()));
|
|
}
|
|
|
|
private User $actor;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
$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, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
|
|
$this->actor = User::factory()->create();
|
|
$this->actingAs($this->actor);
|
|
}
|
|
|
|
private function records(): array
|
|
{
|
|
$entries = collect(app(CorridorAgribusinessOpportunities::class)->entries())->where('corridor', config('investment_corridors.beira.name'))->take(2);
|
|
$opportunities = Opportunity::whereIn('management_creation_key', $entries->pluck('key'))->orderBy('id')->get();
|
|
$project = InvestmentPortfolioController::visible()->whereNotNull('project_id')->firstOrFail();
|
|
$cluster = InvestmentPortfolioController::visible()->whereNotNull('cluster_id')->firstOrFail();
|
|
foreach ($opportunities as $opportunity) {
|
|
// Synthetic links exist only inside this rolled-back QA transaction.
|
|
DB::table('opportunity_projects')->insertOrIgnore(['opportunity_id' => $opportunity->id, 'project_id' => $project->project_id]);
|
|
DB::table('opportunity_clusters')->insertOrIgnore(['opportunity_id' => $opportunity->id, 'cluster_id' => $cluster->cluster_id]);
|
|
}
|
|
|
|
return [$opportunities, $project, $cluster];
|
|
}
|
|
|
|
public function test_origin_and_location_survive_entry_draft_confirmation_and_simulator_without_resetting_deliberate_values(): void
|
|
{
|
|
[$records,$origin,$cluster] = $this->records();
|
|
$opportunity = $records[0];
|
|
$province = collect(app(InvestmentCorridorContext::class)->provinces('beira'))->firstWhere('source_code', '07')->id;
|
|
$query = '?source=opportunity:'.$opportunity->id.'&from_dossier='.$origin->id.'&province_id='.$province;
|
|
auth()->logout();
|
|
$this->get('/investimentos/iniciar'.$query)->assertRedirect(route('login'));
|
|
parse_str(parse_url(session('url.intended'), PHP_URL_QUERY), $intended);
|
|
$this->assertSame($origin->id, $intended['from_dossier']);
|
|
$this->assertSame($province, $intended['province_id']);
|
|
$this->actingAs($this->actor);
|
|
$this->get('/investimentos/iniciar'.$query)->assertOk()->assertInertia(fn (Assert $p) => $p->where('opportunityChoices.0.portfolio_origins.0.id', $origin->id)->where('opportunityChoices.0.territory.id', $province)->where('entryQuery.from_dossier', $origin->id));
|
|
$id = $this->postJson('/investimentos', ['title' => 'QA título deliberado', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$body = ['version' => $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'), 'selection' => [], 'opportunity_choices' => [
|
|
['source' => 'opportunity:'.$opportunity->id, 'territory' => 'territory:'.$province, 'portfolio_origins' => [$origin->id], 'activities' => ['trade']],
|
|
['source' => 'opportunity:'.$records[1]->id, 'territory' => 'territory:'.$province, 'portfolio_origins' => [$origin->id, $cluster->id]],
|
|
]];
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho', [...$body, 'draft_sequence' => 1])->assertOk();
|
|
$this->flushSession();
|
|
$this->actingAs($this->actor);
|
|
$this->get('/investimentos/'.$id.'/contexto/editar')->assertOk()->assertInertia(fn (Assert $p) => $p->where('requestedChoices.0.portfolio_origins.0.id', $origin->id)->where('requestedChoices.0.territory.id', $province));
|
|
$preview = $this->postJson('/investimentos/'.$id.'/contexto/previa', $body)->assertOk()->json();
|
|
$this->assertGreaterThan(0, $preview['opportunity_choices'][0]['portfolio_origins'][0]['version']);
|
|
$this->assertSame('QA título deliberado', $preview['fields']['project_title']);
|
|
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$body, 'preview_hash' => $preview['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
|
$view = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
|
|
$this->assertSame($view['context']['opportunity_choices'], json_decode($view['prefill']['simulator']['context_applicability_snapshot'], true)['opportunity_choices']);
|
|
$this->assertArrayNotHasKey('province_id', $view['prefill']['simulator']);
|
|
$this->getJson('/investimentos/'.$id.'/contexto?opportunity=opportunity:'.$opportunity->id)->assertOk()->assertJsonPath('prefill.simulator.province_id', $province);
|
|
$this->assertSame('QA título deliberado', $view['prefill']['simulator']['project_title']);
|
|
$created = $this->postJson('/api/v1/simulation-drafts', ['context' => $id, 'personal_snapshot' => [], 'economic_snapshot' => ['purpose' => 'simulate_only', 'project_title' => 'QA edição deliberada no simulador', 'context_applicability_snapshot' => $view['prefill']['simulator']['context_applicability_snapshot']]], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated();
|
|
$this->flushSession();
|
|
$this->actingAs($this->actor);
|
|
$simulation = $this->getJson('/api/v1/simulation-drafts/'.$created->json('data.id').'?context='.$id)->assertOk()->assertJsonPath('data.economic_snapshot.project_title', 'QA edição deliberada no simulador')->json('data.economic_snapshot');
|
|
$this->assertSame($view['context']['opportunity_choices'], json_decode($simulation['context_applicability_snapshot'], true)['opportunity_choices']);
|
|
$manica = collect(app(InvestmentCorridorContext::class)->provinces('beira'))->firstWhere('source_code', '06')->id;
|
|
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$opportunity->id.'&from_dossier='.$cluster->id.'&province_id='.$manica)
|
|
->assertOk()->assertInertia(fn (Assert $p) => $p->where('requestedChoices.0.territory.id', $province)->where('requestedChoices.0.activities.0', 'trade')->has('requestedChoices', 2)->has('requestedChoices.0.portfolio_origins', 2)->has('entryLocationDifferences', 1));
|
|
$body['version'] = $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
|
|
$body['opportunity_choices'][0]['portfolio_origins'][] = $cluster->id;
|
|
$associations = $this->postJson('/investimentos/'.$id.'/contexto/previa', $body)->assertOk()->json();
|
|
$this->assertFalse($associations['calculation_inputs_changed']);
|
|
$this->assertSame([], $associations['review_steps']);
|
|
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$body, 'preview_hash' => $associations['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
|
$this->getJson('/investimentos/'.$id.'/contexto')->assertJsonPath('context.dependency_hash', $view['context']['dependency_hash'])->assertJsonCount(2, 'context.opportunity_choices.0.portfolio_origins');
|
|
}
|
|
|
|
public function test_unrelated_or_private_origins_are_rejected_in_entry_draft_and_preview(): void
|
|
{
|
|
[$records,$origin] = $this->records();
|
|
DB::table('opportunity_projects')->where('opportunity_id', $records[0]->id)->where('project_id', $origin->project_id)->delete();
|
|
$this->get('/investimentos/iniciar?source=opportunity:'.$records[0]->id.'&from_dossier='.$origin->id)->assertUnprocessable();
|
|
$id = $this->postJson('/investimentos', ['title' => 'QA origem inválida', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->json('id');
|
|
$body = ['version' => $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'), 'selection' => [], 'opportunity_choices' => [['source' => 'opportunity:'.$records[0]->id, 'portfolio_origins' => [$origin->id]]]];
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho', [...$body, 'draft_sequence' => 1])->assertUnprocessable();
|
|
$this->postJson('/investimentos/'.$id.'/contexto/previa', $body)->assertUnprocessable();
|
|
$origin->publication_status = 'draft';
|
|
$origin->save();
|
|
$this->get('/investimentos/iniciar?source=opportunity:'.$records[1]->id.'&from_dossier='.$origin->id)->assertNotFound();
|
|
}
|
|
}
|