withoutVite(); config(['investor-path.enabled' => true, 'investmoz_ai.enabled' => false, 'suitecrm.enabled' => false, 'portal.notifications_enabled' => false]); Http::preventStrayRequests(); $this->owner = User::factory()->create(); } private function dossier(): string { return $this->actingAs($this->owner)->postJson('/investimentos', ['title' => 'SYNTHETIC company preparation', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id'); } public function test_public_incomplete_comparison_uses_exact_mozambican_source_without_writing_or_requiring_ia(): void { $this->getJson('/formas-juridicas')->assertOk()->assertHeader('Cache-Control', 'no-store, private') ->assertJsonPath('result.source.sha256', '8009ef95a352dc02283e671cd717449ec8ad747a8995153cd3122ea16aa22a0f') ->assertJsonCount(5, 'result.forms')->assertJsonPath('case', null)->assertJsonCount(8, 'result.pending'); $this->get('/formas-juridicas')->assertOk()->assertInertia(fn ($page) => $page->component('investment/company-comparison')); $this->assertDatabaseCount('desk_cases', 0); $this->assertDatabaseCount('organizations', 0); } public function test_answers_explain_single_participant_and_listing_conflicts_without_an_automatic_winner(): void { $r = $this->getJson('/formas-juridicas?'.http_build_query(['answers' => ['purpose' => 'new', 'participants' => 'one', 'listing' => 'yes', 'participation' => 'shares', 'preferred' => 'simplified']]))->assertOk(); $forms = collect($r->json('result.forms'))->keyBy('code'); $this->assertNotEmpty($forms['collective']['constraints']); $this->assertNotEmpty($forms['simplified']['constraints']); $this->assertTrue($forms['simplified']['selected']); foreach (['quotas', 'anonymous', 'simplified'] as $code) { $this->assertStringContainsString('unipessoal', implode(' ', $forms[$code]['notes'])); } $this->assertArrayNotHasKey('score', $forms['simplified']); $this->assertStringContainsString('preferência', implode(' ', $forms['quotas']['notes'])); $this->getJson('/formas-juridicas?'.http_build_query(['answers' => ['participants' => 'several']]))->assertOk()->assertJsonCount(1, 'result.forms.0.constraints'); $this->getJson('/formas-juridicas?'.http_build_query(['answers' => ['unexpected' => 'field']]))->assertUnprocessable(); $this->getJson('/formas-juridicas?'.http_build_query(['answers' => ['preferred' => 'invented']]))->assertUnprocessable(); } public function test_representation_and_cooperation_keep_inactive_answers_without_treating_them_as_new_societies(): void { foreach (['representation', 'cooperation'] as $purpose) { $this->getJson('/formas-juridicas?'.http_build_query(['answers' => ['purpose' => $purpose, 'participants' => 'one', 'preferred' => 'quotas']]))->assertOk() ->assertJsonCount(0, 'result.forms')->assertJsonPath('result.active', ['purpose', 'regulated']) ->assertJsonPath('answers.participants', 'one')->assertJsonPath('answers.preferred', 'quotas'); } } public function test_questions_produce_applicable_preparation_without_a_false_choice_between_partners_and_administration(): void { $answers = ['purpose' => 'new', 'participants' => 'several', 'participation' => 'work', 'capital' => 'defined', 'management' => 'both', 'responsibility' => 'limited', 'listing' => 'yes', 'regulated' => 'yes']; $response = $this->getJson('/formas-juridicas?'.http_build_query(['answers' => $answers]))->assertOk()->assertJsonCount(0, 'result.pending'); $preparation = implode(' ', $response->json('result.preparation')); foreach (['contribuições definidas', 'poderes de gestão', 'nomeação', 'regime de responsabilidade', 'mercado pretendido', 'habilitações específicas'] as $expected) { $this->assertStringContainsString($expected, $preparation); } $this->assertStringContainsString('trabalho', implode(' ', $response->json('result.forms.1.notes'))); } public function test_private_comparison_saves_recovers_and_replays_without_changing_master_identity_or_steps(): void { $id = $this->dossier(); $url = '/investimentos/'.$id.'/forma-juridica'; $before = DB::table('journey_steps')->orderBy('id')->get()->toJson(); $case = DB::table('desk_cases')->where('id', $id)->first(); $payloadBefore = app(VirtualDesk::class)->decode($case->payload); $input = ['version' => $case->version, 'operation_key' => (string) Str::uuid(), 'answers' => ['participants' => 'one', 'preferred' => 'quotas']]; $this->postJson($url, $input)->assertOk()->assertJsonPath('replayed', false)->assertJsonPath('revision', 1); $this->postJson($url, $input)->assertOk()->assertJsonPath('replayed', true); $this->postJson($url, [...$input, 'answers' => ['participants' => 'several']])->assertConflict(); $this->getJson($url)->assertOk()->assertHeader('Cache-Control', 'no-store, private')->assertJsonPath('saved.revision', 1)->assertJsonPath('answers.participants', 'one'); $this->assertSame(1, DB::table('desk_events')->where('case_id', $id)->where('action', CompanyComparison::EVENT)->count()); $stored = DB::table('desk_cases')->where('id', $id)->first(); $payload = app(VirtualDesk::class)->decode($stored->payload); unset($payload['company_comparison']); $this->assertSame($payloadBefore, $payload); $this->assertStringNotContainsString('company_comparison', $stored->payload); $this->assertSame($before, DB::table('journey_steps')->orderBy('id')->get()->toJson()); $this->assertDatabaseCount('organizations', 0); $this->assertDatabaseCount('investment_company_declarations', 0); $this->assertDatabaseCount('desk_requests', 0); $currentVersion = $stored->version; $this->postJson($url, [...$input, 'version' => $currentVersion, 'operation_key' => (string) Str::uuid()])->assertOk()->assertJsonPath('unchanged', true); $this->assertSame($currentVersion, DB::table('desk_cases')->where('id', $id)->value('version')); $this->assertSame(1, DB::table('desk_events')->where('case_id', $id)->where('action', CompanyComparison::EVENT)->count()); $this->getJson('/investimentos/'.$id)->assertOk()->assertJsonPath('tools.company', $url); $this->postJson($url, [...$input, 'operation_key' => (string) Str::uuid()])->assertConflict(); } public function test_historical_answers_sources_and_results_do_not_follow_future_engine_changes(): void { $id = $this->dossier(); $url = '/investimentos/'.$id.'/forma-juridica'; $version = DB::table('desk_cases')->where('id', $id)->value('version'); $original = ['version' => $version, 'operation_key' => (string) Str::uuid(), 'answers' => ['participants' => 'one']]; $this->postJson($url, $original)->assertOk(); $first = $this->getJson($url)->assertOk()->json(); config(['company-comparison.version' => 'SYNTHETIC-future-version', 'company-comparison.source.edition' => 'SYNTHETIC-future-edition']); config(['company-comparison.questions.synthetic_future' => ['label' => 'SYNTHETIC future question', 'options' => ['unknown' => 'Unknown']]]); $this->postJson($url, [...$original, 'version' => (string) $original['version']])->assertOk()->assertJsonPath('replayed', true)->assertJsonPath('revision', 1); $this->getJson($url)->assertOk()->assertJsonPath('result', $first['result']); $this->postJson($url, ['version' => $first['case']['version'], 'operation_key' => (string) Str::uuid(), 'answers' => ['participants' => 'several']])->assertOk()->assertJsonPath('revision', 2); $revision = $first['history'][0]['id']; $this->getJson($url.'?revision='.$revision)->assertOk()->assertJsonPath('historical', true)->assertJsonPath('result', $first['result'])->assertJsonPath('answers.participants', 'one'); $this->getJson($url.'?'.http_build_query(['revision' => $revision, 'answers' => ['participants' => 'several']]))->assertUnprocessable(); $this->getJson($url)->assertOk()->assertJsonPath('result.engine_version', 'SYNTHETIC-future-version')->assertJsonPath('answers.participants', 'several'); } public function test_scope_is_rechecked_before_validation_and_revocation_or_closure_does_not_allow_writes(): void { $id = $this->dossier(); $url = '/investimentos/'.$id.'/forma-juridica'; $input = ['version' => DB::table('desk_cases')->where('id', $id)->value('version'), 'operation_key' => (string) Str::uuid(), 'answers' => []]; $this->actingAs(User::factory()->create())->getJson($url)->assertForbidden(); $this->postJson($url, [])->assertForbidden(); $this->actingAs($this->owner); $this->postJson($url, $input)->assertOk(); DB::table('desk_cases')->where('id', $id)->update(['closed_at' => now()]); $this->getJson($url)->assertOk()->assertJsonPath('canWrite', false); $this->postJson($url, $input)->assertOk()->assertJsonPath('replayed', true); $this->postJson($url, [...$input, 'operation_key' => (string) Str::uuid()])->assertConflict(); } public function test_public_answers_continue_through_dossier_choice_and_explicit_creation_without_automatic_save(): void { $answers = ['participants' => 'one', 'preferred' => 'anonymous']; $target = '/investimentos?'.http_build_query(['company_comparison' => $answers]); $this->get($target)->assertRedirect('/login'); $this->assertSame(url($target), session('url.intended')); $id = $this->dossier(); $this->getJson($target)->assertOk()->assertJsonPath('companyComparisonAnswers.participants', 'one'); $record = $this->postJson('/investimentos', ['title' => 'SYNTHETIC comparison destination', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true, 'company_comparison' => $answers])->assertCreated()->json(); $this->assertStringContainsString('/forma-juridica?', $record['comparison_href']); $this->getJson($record['comparison_href'])->assertOk()->assertJsonPath('workingCopy', true)->assertJsonPath('answers.preferred', 'anonymous')->assertJsonPath('saved', null); $this->assertSame(0, DB::table('desk_events')->where('action', CompanyComparison::EVENT)->count()); $this->getJson('/investimentos/'.$id.'/forma-juridica?'.http_build_query(['answers' => $answers]))->assertOk()->assertJsonPath('saved', null); } public function test_organization_coeditor_revocation_is_effective_before_replay_or_validation(): void { $id = $this->dossier(); $org = (string) Str::uuid(); DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC comparison organization', 'organization_type' => 'private']); DB::table('desk_cases')->where('id', $id)->update(['organization_id' => $org]); $coeditor = User::factory()->create(); $viewer = User::factory()->create(); foreach ([[$this->owner, 'officer'], [$coeditor, 'officer'], [$viewer, 'viewer']] as [$actor, $role]) { DB::table('memberships')->insert(['id' => (string) Str::uuid(), 'organization_id' => $org, 'user_id' => $actor->id, 'role' => $role]); } $url = '/investimentos/'.$id.'/forma-juridica'; $input = ['version' => DB::table('desk_cases')->where('id', $id)->value('version'), 'operation_key' => (string) Str::uuid(), 'answers' => ['participants' => 'several']]; $this->actingAs($coeditor)->postJson($url, $input)->assertOk(); $this->actingAs($viewer)->getJson($url)->assertOk()->assertJsonPath('canWrite', false); $this->postJson($url, $input)->assertForbidden(); $this->actingAs($coeditor); DB::table('memberships')->where('organization_id', $org)->where('user_id', $coeditor->id)->update(['deleted_at' => now()]); $this->getJson($url)->assertForbidden(); $this->postJson($url, $input)->assertForbidden(); $this->postJson($url, [])->assertForbidden(); $this->actingAs($this->owner)->getJson($url)->assertOk()->assertJsonPath('answers.participants', 'several'); $this->assertSame(1, DB::table('desk_events')->where('case_id', $id)->where('action', CompanyComparison::EVENT)->count()); } public function test_all_history_pages_recover_old_editions_without_overwriting_the_latest_snapshot(): void { $id = $this->dossier(); $url = '/investimentos/'.$id.'/forma-juridica'; $version = DB::table('desk_cases')->where('id', $id)->value('version'); for ($revision = 1; $revision <= 26; $revision++) { $this->postJson($url, ['version' => $version++, 'operation_key' => (string) Str::uuid(), 'answers' => ['participants' => $revision % 2 === 1 ? 'one' : 'several']])->assertOk()->assertJsonPath('revision', $revision); } $page = $this->getJson($url)->assertOk()->assertJsonCount(25, 'history')->assertJsonPath('saved.revision', 26)->json(); $this->assertNotNull($page['historyNext']); $older = $this->getJson($url.'?history_before='.$page['historyNext'])->assertOk()->assertJsonCount(1, 'history')->assertJsonPath('history.0.revision', 1)->assertJsonPath('saved.revision', 26)->json(); $this->getJson($url.'?revision='.$older['history'][0]['id'])->assertOk()->assertJsonPath('saved.revision', 1); $this->getJson($url)->assertOk()->assertJsonPath('saved.revision', 26); } }