175 lines
11 KiB
PHP
175 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use App\Services\InvestmentGuides;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
final class BvmAdmissionGuideTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $owner;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->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 BVM preparation', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
}
|
|
|
|
private function url(string $id, string $slug = 'bolsa'): string
|
|
{
|
|
return '/investimentos/'.$id.'/guias/'.$slug;
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
private function input(string $id, string $slug = 'bolsa', array $extra = []): array
|
|
{
|
|
return [...['version' => DB::table('desk_cases')->where('id', $id)->value('version'), 'operation_key' => (string) Str::uuid(), 'sha256' => app(InvestmentGuides::class)->guide($slug)['sha256'], 'instruments' => $slug === 'bolsa' ? ['third_shares'] : ['debt'], 'note' => 'SYNTHETIC private '.$slug], ...$extra];
|
|
}
|
|
|
|
public function test_public_guide_has_five_real_paths_with_article_page_and_redaction_per_requirement(): void
|
|
{
|
|
$r = $this->getJson('/guias-investimento/bolsa')->assertOk()->assertHeader('Cache-Control', 'no-store, private')
|
|
->assertJsonCount(5, 'guide.instruments')->assertJsonCount(7, 'guide.sources')->assertJsonCount(6, 'guide.preparation')->assertJsonPath('case', null)->json();
|
|
$guide = $r['guide'];
|
|
$this->assertSame(['official_shares', 'official_bonds', 'second_shares', 'second_bonds', 'third_shares'], array_keys($guide['instruments']));
|
|
foreach ([$guide['requirements'], ...array_column($guide['instruments'], 'requirements')] as $requirements) {
|
|
foreach ($requirements as $requirement) {
|
|
$this->assertNotEmpty($requirement['citations']);
|
|
foreach ($requirement['citations'] as $cite) {
|
|
$this->assertArrayHasKey($cite['source'], $guide['sources']);
|
|
$this->assertNotEmpty($guide['sources'][$cite['source']]['edition']);
|
|
$this->assertGreaterThan(0, $cite['page']);
|
|
$this->assertMatchesRegularExpression('/art|âmbito/iu', $cite['reference']);
|
|
}
|
|
}
|
|
}
|
|
$this->get('/guias-investimento/bolsa')->assertOk()->assertInertia(fn ($page) => $page->component('investment/guide'));
|
|
$this->assertDatabaseCount('desk_cases', 0);
|
|
$this->assertDatabaseCount('desk_requests', 0);
|
|
}
|
|
|
|
public function test_current_minimums_remain_unknown_instead_of_reusing_the_expired_amounts_or_zero(): void
|
|
{
|
|
$guide = $this->getJson('/guias-investimento/bolsa')->assertOk()->assertJsonPath('guide.sources.minimums_historical.valid_until', '2025-03-31')->json('guide');
|
|
foreach ($guide['instruments'] as $path) {
|
|
$gap = collect($path['requirements'])->firstWhere('kind', 'gap');
|
|
$this->assertNull($gap['value']);
|
|
$this->assertSame('MZN', $gap['unit']);
|
|
$this->assertStringContainsString('31/03/2025', $gap['text']);
|
|
$this->assertStringContainsString('regulamento anual', $gap['text']);
|
|
}
|
|
$serialized = json_encode($guide, JSON_THROW_ON_ERROR);
|
|
foreach (['16.000.000', '4.000.000', '1.000.000', '8.000.000', '2.000.000'] as $expired) {
|
|
$this->assertStringNotContainsString($expired, $serialized);
|
|
}
|
|
$this->assertStringContainsString('um quarto', $guide['instruments']['second_shares']['requirements'][0]['text']);
|
|
$this->assertStringContainsString('25%', $guide['instruments']['third_shares']['requirements'][0]['text']);
|
|
}
|
|
|
|
public function test_third_market_uses_2021_instead_of_the_old_nine_month_transition_and_keeps_initial_waivers_scoped(): void
|
|
{
|
|
$guide = app(InvestmentGuides::class)->guide('bolsa');
|
|
$third = collect($guide['instruments']['third_shares']['requirements'])->keyBy('id');
|
|
$this->assertStringContainsString('dois anos', $third['third_entry_dispersion']['text']);
|
|
$this->assertStringContainsString('mais um ano', $third['third_entry_dispersion']['text']);
|
|
$this->assertStringContainsString('sem extensão automática', $third['third_entry_dispersion']['text']);
|
|
$this->assertStringNotContainsString('nove meses', json_encode($third, JSON_THROW_ON_ERROR));
|
|
$this->assertSame('exception', $third['third_entry_audit']['kind']);
|
|
$this->assertStringContainsString('momento do pedido', $third['third_entry_audit']['text']);
|
|
$this->assertStringContainsString('OCAM', $third['third_finances']['text']);
|
|
$this->assertStringContainsString('semestrais', $third['third_reporting']['text']);
|
|
$docs = collect($guide['documents'])->keyBy('id');
|
|
$this->assertNotContains('third_shares', $docs['audit']['paths']);
|
|
$this->assertContains('third_shares', $docs['updated_information']['paths']);
|
|
$this->assertStringContainsString('não reproduz uma dispensa geral', $docs['updated_information']['text']);
|
|
$this->assertStringContainsString('um ano', $guide['instruments']['second_shares']['requirements'][1]['text']);
|
|
$this->assertStringContainsString('nove meses', $guide['instruments']['second_shares']['requirements'][1]['text']);
|
|
}
|
|
|
|
public function test_sources_distinguish_supervision_sponsorship_registry_and_the_2022_nominative_and_sas_rules(): void
|
|
{
|
|
$guide = app(InvestmentGuides::class)->guide('bolsa');
|
|
$roles = implode(' ', array_column($guide['eligibility'], 'text'));
|
|
foreach (['Banco de Moçambique', 'BVM', 'operador', 'Central de Valores Mobiliários', 'Não é um regulador separado'] as $expected) {
|
|
$this->assertStringContainsString($expected, $roles);
|
|
}
|
|
$rules = collect($guide['requirements'])->keyBy('id');
|
|
$this->assertStringContainsString('simplificada', $rules['legal_form']['text']);
|
|
$this->assertSame(63, $rules['legal_form']['citations'][1]['page']);
|
|
$this->assertStringContainsString('2022', $rules['nominative']['text']);
|
|
$this->assertStringContainsString('ao portador', $rules['nominative']['text']);
|
|
foreach ($guide['handoffs'] as $link) {
|
|
$this->assertStringStartsWith('https://www.bvm.co.mz/', $link['url']);
|
|
$this->assertStringNotContainsString('?', $link['url']);
|
|
}
|
|
}
|
|
|
|
public function test_dossier_choice_and_creation_pin_the_bvm_edition_without_saving_or_submitting_by_arrasto(): void
|
|
{
|
|
$guide = app(InvestmentGuides::class)->guide('bolsa');
|
|
$selection = ['slug' => 'bolsa', 'sha256' => $guide['sha256'], 'instruments' => ['second_bonds', 'third_shares']];
|
|
$target = '/investimentos?'.http_build_query(['investment_guide' => $selection]);
|
|
$this->get($target)->assertRedirect('/login');
|
|
$this->assertSame(url($target), session('url.intended'));
|
|
$id = $this->dossier();
|
|
$this->getJson($target)->assertOk()->assertJsonPath('guideSelection.slug', 'bolsa');
|
|
$record = $this->postJson('/investimentos', ['title' => 'SYNTHETIC selected destination', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true, 'investment_guide' => $selection])->assertCreated()->json();
|
|
$this->getJson($record['guide_href'])->assertOk()->assertJsonPath('guide.slug', 'bolsa')->assertJsonPath('instruments', ['second_bonds', 'third_shares'])->assertJsonPath('saved', null);
|
|
$this->assertSame(0, DB::table('desk_events')->where('action', InvestmentGuides::EVENT)->count());
|
|
$this->getJson($this->url($id).'?instruments[0]=third_bonds')->assertUnprocessable();
|
|
$this->getJson($this->url($id).'?instruments[0]=debt')->assertUnprocessable();
|
|
}
|
|
|
|
public function test_multiple_guides_reuse_one_dossier_and_cannot_replay_or_open_each_others_revisions(): void
|
|
{
|
|
$id = $this->dossier();
|
|
$before = DB::table('journey_steps')->orderBy('id')->get()->toJson();
|
|
$capital = $this->input($id, 'capitalizacao');
|
|
$this->postJson($this->url($id, 'capitalizacao'), $capital)->assertOk();
|
|
$first = $this->getJson($this->url($id, 'capitalizacao'))->json();
|
|
$bvm = $this->input($id);
|
|
$this->postJson($this->url($id), $bvm)->assertOk()->assertJsonPath('revision', 1);
|
|
$this->postJson($this->url($id), $bvm)->assertOk()->assertJsonPath('replayed', true);
|
|
$this->postJson($this->url($id), [...$bvm, 'operation_key' => $capital['operation_key']])->assertConflict();
|
|
$this->getJson($this->url($id).'?revision='.$first['history'][0]['id'])->assertNotFound();
|
|
$this->getJson($this->url($id, 'capitalizacao'))->assertOk()->assertJsonPath('saved', $first['saved'])->assertJsonCount(1, 'history');
|
|
$this->getJson($this->url($id))->assertOk()->assertJsonCount(1, 'history')->assertJsonPath('guide.slug', 'bolsa');
|
|
$this->assertSame($before, DB::table('journey_steps')->orderBy('id')->get()->toJson());
|
|
$stored = DB::table('desk_cases')->where('id', $id)->first();
|
|
$this->assertStringNotContainsString('SYNTHETIC private bolsa', $stored->payload);
|
|
$payload = app(VirtualDesk::class)->decode($stored->payload);
|
|
$this->assertCount(2, $payload['investment_guides']);
|
|
$this->assertDatabaseCount('desk_requests', 0);
|
|
$this->assertDatabaseCount('organizations', 0);
|
|
}
|
|
|
|
public function test_pagination_filters_the_requested_guide_when_the_shared_event_stream_is_interleaved(): void
|
|
{
|
|
$id = $this->dossier();
|
|
for ($i = 1; $i <= 26; $i++) {
|
|
$this->postJson($this->url($id), $this->input($id, 'bolsa', ['note' => 'SYNTHETIC BVM revision '.$i]))->assertOk();
|
|
$this->postJson($this->url($id, 'capitalizacao'), $this->input($id, 'capitalizacao', ['note' => 'SYNTHETIC capital revision '.$i]))->assertOk();
|
|
}
|
|
$page = $this->getJson($this->url($id))->assertOk()->assertJsonCount(25, 'history')->assertJsonPath('saved.revision', 26)->json();
|
|
$old = $this->getJson($this->url($id).'?history_before='.$page['historyNext'])->assertOk()->assertJsonCount(1, 'history')->assertJsonPath('history.0.revision', 1)->json();
|
|
$this->getJson($this->url($id).'?revision='.$old['history'][0]['id'])->assertOk()->assertJsonPath('guide.slug', 'bolsa')->assertJsonPath('note', 'SYNTHETIC BVM revision 1');
|
|
$this->getJson($this->url($id, 'capitalizacao').'?revision='.$old['history'][0]['id'])->assertNotFound();
|
|
}
|
|
}
|