168 lines
11 KiB
PHP
168 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
class ExistingBusinessWizardTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function publicLocation(string $type, ?string $parent = null): string
|
|
{
|
|
$id = (string) Str::uuid();
|
|
DB::table('geo_locations')->insert([
|
|
'id' => $id, 'source_system' => 'cartografia-workbook',
|
|
'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id,
|
|
'version_no' => 1, 'data_classification' => 'public', 'name' => 'Synthetic '.$type.' '.$id,
|
|
'location_type' => $type, 'parent_id' => $parent, 'code' => $id,
|
|
]);
|
|
|
|
return $id;
|
|
}
|
|
|
|
public function test_sector_migration_reverses_empty_and_refuses_data_loss(): void
|
|
{
|
|
$migration = require database_path('migrations/2026_09_29_200000_existing_business_form_sector.php');
|
|
$migration->down();
|
|
$this->assertFalse(Schema::hasColumn('existing_businesses', 'sector_id'));
|
|
$migration->up();
|
|
$this->assertTrue(Schema::hasColumn('existing_businesses', 'sector_id'));
|
|
config(['existing-business.enabled' => true]);
|
|
$sector = DB::table('sectors')->insertGetId(['name' => 'Synthetic', 'slug' => 'rollback-synthetic', 'is_active' => true]);
|
|
$this->actingAs(User::factory()->create())->postJson('/api/v1/existing-business/listings', [
|
|
'modality' => 'company_sale', 'classification' => 'confidential', 'reason' => 'Synthetic rollback check',
|
|
'sector_id' => $sector, 'payload' => ['title' => 'Synthetic rollback'],
|
|
])->assertCreated();
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('Sector associations exist');
|
|
$migration->down();
|
|
}
|
|
|
|
public function test_draft_resume_validation_concurrency_confirmation_and_private_submission(): void
|
|
{
|
|
config(['existing-business.enabled' => true]);
|
|
$owner = User::factory()->create();
|
|
$sector = DB::table('sectors')->insertGetId(['name' => 'Synthetic', 'slug' => 'wizard-synthetic', 'is_active' => true]);
|
|
$this->actingAs($owner);
|
|
$draft = ['modality' => 'company_sale', 'classification' => 'confidential', 'reason' => 'Synthetic form draft', 'payload' => ['title' => 'Synthetic wizard', 'wizard_version' => '1', 'wizard_step' => '1']];
|
|
$record = $this->postJson('/api/v1/existing-business/listings', $draft)->assertCreated()->json();
|
|
$url = '/api/v1/existing-business/listings/'.$record['id'];
|
|
$transition = ['action' => 'submitted', 'version_no' => 1, 'note' => 'Synthetic submission request', 'review_consent' => true, 'confirmation_code' => $record['code']];
|
|
$this->postJson($url.'/transitions', $transition)->assertUnprocessable();
|
|
$payload = [...$draft['payload'], 'wizard_step' => '8', 'proponent_type' => 'person', 'proponent_name' => 'Synthetic owner',
|
|
'tax_identifier' => 'SYNTHETIC', 'contact_email' => 'synthetic@example.test', 'country' => 'MZ', 'acting_capacity' => 'representative',
|
|
'declared_holder' => 'Synthetic holder', 'legitimacy_declaration' => 'yes', 'location_notes' => 'Synthetic private location',
|
|
'description' => 'Synthetic description long enough for review.', 'source_reference' => 'Synthetic source reference',
|
|
'valid_until' => today()->addMonth()->toDateString(), 'asking_value' => '1234.50', 'currency' => 'MZN', 'value_nature' => 'asking'];
|
|
$save = [...$draft, 'version_no' => 1, 'sector_id' => $sector, 'payload' => $payload];
|
|
$this->putJson($url, [...$save, 'payload' => [...$payload, 'equity_percentage' => '101']])->assertUnprocessable();
|
|
$this->putJson($url, $save)->assertOk()->assertJsonPath('version_no', 2);
|
|
$this->getJson($url)->assertOk()->assertJsonPath('record.payload.wizard_step', '8')->assertJsonPath('record.sector_id', $sector);
|
|
$this->putJson($url, $save)->assertConflict();
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 2])->assertUnprocessable()->assertJsonValidationErrors('mandate_reference');
|
|
$payload['mandate_reference'] = 'Synthetic mandate reference';
|
|
$payload['mandate_valid_until'] = today()->addMonth()->toDateString();
|
|
$this->putJson($url, [...$save, 'version_no' => 2, 'payload' => $payload])->assertOk();
|
|
$this->putJson($url, [...$save, 'version_no' => 3, 'payload' => $payload, 'modality' => 'real_estate'])->assertOk();
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 4])->assertUnprocessable()->assertJsonValidationErrors('objects');
|
|
$this->putJson($url, [...$save, 'version_no' => 4, 'payload' => $payload])->assertOk();
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 5, 'confirmation_code' => 'wrong'])
|
|
->assertUnprocessable()->assertJsonValidationErrors('confirmation_code');
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 5])->assertOk()->assertJsonPath('status', 'submitted');
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 5])->assertConflict();
|
|
$this->actingAs(User::factory()->create())->getJson($url)->assertForbidden();
|
|
$this->assertDatabaseCount('existing_business_reviews', 1);
|
|
$raw = DB::table('existing_businesses')->where('id', $record['id'])->value('payload');
|
|
$this->assertStringNotContainsString('synthetic@example.test', $raw);
|
|
}
|
|
|
|
public function test_multiple_locations_require_distinct_public_paths_from_the_same_hierarchy(): void
|
|
{
|
|
config(['existing-business.enabled' => true]);
|
|
$owner = User::factory()->create();
|
|
$sector = DB::table('sectors')->insertGetId(['name' => 'Synthetic locations', 'slug' => 'synthetic-locations', 'is_active' => true]);
|
|
$country = $this->publicLocation('country');
|
|
$provinceA = $this->publicLocation('province', $country);
|
|
$provinceB = $this->publicLocation('province', $country);
|
|
$districtA = $this->publicLocation('district', $provinceA);
|
|
$districtB = $this->publicLocation('district', $provinceB);
|
|
$this->actingAs($owner);
|
|
|
|
$payload = [
|
|
'title' => 'Synthetic multiple-location opportunity', 'wizard_version' => '2', 'wizard_step' => '8',
|
|
'proponent_type' => 'organization', 'proponent_name' => 'Synthetic proponent', 'country' => 'MZ',
|
|
'contact_name' => 'Synthetic contact', 'contact_email' => 'locations@example.test',
|
|
'acting_capacity' => 'holder', 'legitimacy_declaration' => 'yes',
|
|
'opportunity_type' => 'company_sale',
|
|
'description' => 'Synthetic description with sufficient detail for review.',
|
|
'project_stage' => 'study', 'availability' => 'available', 'value_status' => 'unknown',
|
|
'geography_scope' => 'multiple', 'source_reference' => 'Synthetic source reference',
|
|
'source_provider' => 'Synthetic information provider', 'source_date' => today()->toDateString(),
|
|
'asset_deal_kind' => 'whole_business', 'confirm_information' => 'yes',
|
|
'confirm_disclosure_authority' => 'yes', 'confirm_no_publication_guarantee' => 'yes',
|
|
'locations_json' => json_encode([['province_id' => $provinceA]], JSON_THROW_ON_ERROR),
|
|
];
|
|
$id = $this->postJson('/api/v1/existing-business/listings', [
|
|
'modality' => 'company_sale', 'classification' => 'confidential', 'reason' => 'Synthetic multiple-location draft',
|
|
'sector_id' => $sector, 'payload' => $payload,
|
|
])->assertCreated()->json('id');
|
|
$url = '/api/v1/existing-business/listings/'.$id;
|
|
$transition = ['action' => 'submitted', 'version_no' => 1, 'note' => 'Synthetic multiple-location submission',
|
|
'review_consent' => true, 'confirmation_code' => DB::table('existing_businesses')->where('id', $id)->value('code')];
|
|
|
|
$this->postJson($url.'/transitions', $transition)->assertUnprocessable()->assertJsonValidationErrors('locations');
|
|
$overlapping = [
|
|
['province_id' => $provinceA],
|
|
['province_id' => $provinceA, 'district_id' => $districtA],
|
|
];
|
|
$this->putJson($url, ['modality' => 'company_sale', 'classification' => 'confidential',
|
|
'reason' => 'Synthetic overlapping location update', 'version_no' => 1, 'sector_id' => $sector,
|
|
'payload' => [...$payload, 'locations_json' => json_encode($overlapping, JSON_THROW_ON_ERROR)]])->assertOk();
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 2])
|
|
->assertUnprocessable()->assertJsonValidationErrors('locations_json');
|
|
|
|
$wrongParent = [
|
|
['province_id' => $provinceA, 'district_id' => $districtA],
|
|
['province_id' => $provinceA, 'district_id' => $districtB],
|
|
];
|
|
$this->putJson($url, ['modality' => 'company_sale', 'classification' => 'confidential',
|
|
'reason' => 'Synthetic invalid hierarchy update', 'version_no' => 2, 'sector_id' => $sector,
|
|
'payload' => [...$payload, 'locations_json' => json_encode($wrongParent, JSON_THROW_ON_ERROR)]])->assertOk();
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 3])
|
|
->assertUnprocessable()->assertJsonValidationErrors('district_id');
|
|
|
|
$valid = [
|
|
['province_id' => $provinceA, 'district_id' => $districtA],
|
|
['province_id' => $provinceB, 'district_id' => $districtB],
|
|
];
|
|
$this->putJson($url, ['modality' => 'company_sale', 'classification' => 'confidential',
|
|
'reason' => 'Synthetic distinct location update', 'version_no' => 3, 'sector_id' => $sector,
|
|
'payload' => [...$payload, 'locations_json' => json_encode($valid, JSON_THROW_ON_ERROR)]])->assertOk();
|
|
$this->postJson($url.'/transitions', [...$transition, 'version_no' => 4])
|
|
->assertOk()->assertJsonPath('status', 'submitted');
|
|
$this->getJson($url)->assertOk()->assertJsonPath('record.payload.locations_json', json_encode($valid, JSON_THROW_ON_ERROR));
|
|
}
|
|
|
|
public function test_public_territory_search_matches_names_with_or_without_accents(): void
|
|
{
|
|
$country = $this->publicLocation('country');
|
|
$province = $this->publicLocation('province', $country);
|
|
DB::table('geo_locations')->where('id', $province)->update([
|
|
'name' => 'Província de Mapúto', 'code' => 'MPT-ACCENT',
|
|
]);
|
|
|
|
$this->getJson('/api/v1/investment/territory-options?type=province&q=maputo')
|
|
->assertOk()->assertJsonPath('data.0.id', $province);
|
|
$this->getJson('/api/v1/investment/territory-options?type=province&q=provincia')
|
|
->assertOk()->assertJsonPath('data.0.id', $province);
|
|
$this->getJson('/api/v1/investment/territory-options?type=province&q=Mapúto')
|
|
->assertOk()->assertJsonPath('data.0.id', $province);
|
|
}
|
|
}
|