withoutVite(); config(['geography.enabled' => true]); $this->company = Tenant::query()->create(['name' => 'Geografia sintética']); $department = Department::query()->create(['tenant_id' => $this->company->id, 'name' => 'Operações']); $this->buyer = User::factory()->create(['tenant_id' => $this->company->id, 'department_id' => $department->id, 'purchasing_role' => PurchasingRole::Procurement]); $this->admin = User::factory()->create(['tenant_id' => $this->company->id, 'department_id' => $department->id, 'purchasing_role' => PurchasingRole::Admin]); $this->actingAs($this->buyer); $this->reference = ['country' => 'MZ', 'catalogue' => config('geography.source_sha256'), 'record_id' => '2']; $this->data = ['name' => 'Fornecedor geográfico', 'nuit' => '123456789', 'contact' => 'Contacto', 'email' => 'geo@example.test', 'phone' => '123456789', 'address' => 'Morada original estrangeira ou nacional', 'category' => 'Material', 'beneficiaries' => 'Beneficiário sintético', 'document_references' => 'Referências sintéticas', 'diligence_notes' => 'Diligência sintética sem dados reais', 'blocked' => false, 'next_review' => now()->addYear()->format('Y-m-d')]; }); test('catalogue preserves all source records and quarantines ambiguity without inventing nodes', function () { $catalogue = app(AdministrativeGeography::class)->catalogue(); expect($catalogue['manifest']['records'])->toBe(20407)->and($catalogue['manifest']['selectable'])->toBe(9678)->and($catalogue['manifest']['duplicate_code_groups'])->toBe(48)->and($catalogue['manifest']['edition'])->toBeNull(); expect(count($catalogue['records']))->toBe(20407); $ids = []; $preserved = true; $quarantined = true; $parentsValid = true; foreach ($catalogue['records'] as $record) { $ids[$record['id']] = $record; $preserved = $preserved && $record['name'] === $record['raw']['Designacao']; if ($record['issues']) { $quarantined = $quarantined && ! $record['selectable']; } } foreach ($ids as $record) { if ($record['parent_id'] !== null && $record['selectable']) { $parentsValid = $parentsValid && $ids[$record['parent_id']]['selectable']; } } expect($preserved)->toBeTrue()->and($quarantined)->toBeTrue()->and($parentsValid)->toBeTrue(); }); test('only authenticated company users can browse confirmed paginated locations', function () { $this->getJson('/api/divisao-administrativa')->assertOk()->assertJsonCount(11, 'data')->assertJsonPath('data.0.level', 1); $this->getJson('/api/divisao-administrativa?parent=2')->assertOk()->assertJsonPath('data.0.parent_id', '2'); $this->getJson('/api/divisao-administrativa?page=0')->assertUnprocessable(); $this->getJson('/api/divisao-administrativa?parent=999999')->assertUnprocessable(); auth()->logout(); $this->getJson('/api/divisao-administrativa')->assertUnauthorized(); }); test('inconsistent locations and stale catalogue versions cannot be selected', function () { $service = app(AdministrativeGeography::class); foreach (['duplicate_code_path', 'missing_parent', 'blank_designation', 'ambiguous_parent', 'ancestor_requires_review'] as $issue) { $record = collect($service->catalogue()['records'])->first(fn ($record) => in_array($issue, $record['issues'], true)); expect(fn () => $service->canonical([...$this->reference, 'record_id' => $record['id']]))->toThrow(ValidationException::class); } expect(fn () => $service->canonical([...$this->reference, 'catalogue' => str_repeat('0', 64)]))->toThrow(ValidationException::class); }); test('supplier registration reuses geographic reference and preserves address and independent review', function () { $this->post('/fornecedores', [...$this->data, 'geography_reference' => $this->reference])->assertSessionHasNoErrors(); $supplier = Supplier::query()->sole(); expect($supplier->geography_reference)->toBe($this->reference)->and($supplier->address)->toBe($this->data['address'])->and($supplier->status)->toBe('pending'); $this->get('/fornecedores/'.$supplier->id)->assertOk(); expect(AuditLog::query()->where('action', 'supplier.created')->sole()->after['geography_reference'])->toBe($this->reference); }); test('supplier geography changes follow the native version and revalidation circuit', function () { $supplier = Supplier::query()->create([...$this->data, 'tenant_id' => $this->company->id, 'created_by' => $this->buyer->id, 'status' => 'active']); $url = '/fornecedores/'.$supplier->id; $this->put($url, [...$this->data, 'version' => 1, 'geography_reference' => $this->reference])->assertSessionHasNoErrors(); expect($supplier->refresh()->status)->toBe('pending')->and($supplier->version)->toBe(2)->and($supplier->address)->toBe($this->data['address']); $this->put($url, [...$this->data, 'version' => 1, 'geography_reference' => null])->assertSessionHasErrors('version'); $this->put($url, [...$this->data, 'version' => 2, 'geography_reference' => $this->reference])->assertSessionHasNoErrors(); expect($supplier->refresh()->version)->toBe(2); $this->put($url, [...$this->data, 'version' => 2])->assertSessionHasNoErrors(); expect($supplier->refresh()->geography_reference)->toBe($this->reference); $this->put($url, [...$this->data, 'version' => 2, 'geography_reference' => null])->assertSessionHasNoErrors(); expect($supplier->refresh()->geography_reference)->toBeNull()->and($supplier->address)->toBe($this->data['address']); }); test('unclassified and foreign supplier addresses remain supported without guessing country', function () { $this->post('/fornecedores', $this->data)->assertSessionHasNoErrors(); expect(Supplier::query()->sole()->geography_reference)->toBeNull(); $this->post('/fornecedores', [...$this->data, 'nuit' => '987654321', 'geography_reference' => [...$this->reference, 'record_id' => '999999']])->assertSessionHasErrors('geography_reference'); expect(Supplier::query()->count())->toBe(1); }); test('geographic filter includes descendant locations and remains tenant scoped', function () { $service = app(AdministrativeGeography::class); $child = collect($service->catalogue()['records'])->first(fn ($record) => $record['selectable'] && $record['level'] > 1 && $record['codes'][0] === '01'); $supplier = Supplier::query()->create([...$this->data, 'geography_reference' => [...$this->reference, 'record_id' => $child['id']], 'tenant_id' => $this->company->id, 'created_by' => $this->buyer->id]); $other = Tenant::query()->create(['name' => 'Outra empresa']); Supplier::query()->create([...$this->data, 'tenant_id' => $other->id, 'created_by' => $this->buyer->id, 'geography_reference' => $this->reference]); $this->get('/fornecedores?location=2')->assertInertia(fn ($page) => $page->has('suppliers.data', 1)->where('suppliers.data.0.id', $supplier->id)); $this->get('/fornecedores?location=999999')->assertSessionHasErrors('geography_reference'); }); test('only current administrators can change company location without changing approval rules', function () { $payload = ['geography_reference' => $this->reference, 'expected_reference' => null]; $this->post('/empresa/localizacao', $payload)->assertForbidden(); $before = $this->company->refresh()->only(['first_limit_minor', 'second_limit_minor', 'approval_includes_tax']); $this->actingAs($this->admin)->post('/empresa/localizacao', $payload)->assertSessionHasNoErrors(); expect($this->company->refresh()->geography_reference)->toBe($this->reference)->and($this->company->only(array_keys($before)))->toBe($before); $this->post('/empresa/localizacao', ['geography_reference' => null, 'expected_reference' => null])->assertSessionHasErrors('geography_reference'); $this->post('/empresa/localizacao', ['geography_reference' => $this->reference, 'expected_reference' => $this->reference])->assertSessionHasNoErrors(); expect(AuditLog::query()->where('action', 'company.geography_updated')->count())->toBe(1); $this->admin->forceFill(['purchasing_role' => PurchasingRole::Finance])->save(); expect($this->admin->fresh()->purchasing_role)->toBe(PurchasingRole::Finance); $this->withoutMiddleware(EnsureWorkContext::class); $this->post('/empresa/localizacao', ['geography_reference' => null, 'expected_reference' => $this->reference])->assertForbidden(); }); test('disabled feature and missing schema do not accept geographic changes', function () { config(['geography.enabled' => false]); $this->getJson('/api/divisao-administrativa')->assertServiceUnavailable(); $this->post('/fornecedores', [...$this->data, 'geography_reference' => $this->reference])->assertSessionHasErrors('geography_reference'); $this->post('/fornecedores', $this->data)->assertSessionHasNoErrors(); config(['geography.enabled' => true]); Schema::table('suppliers', fn ($table) => $table->dropColumn('geography_reference')); $this->getJson('/api/divisao-administrativa')->assertServiceUnavailable(); }); test('catalogue integrity mismatch fails closed', function () { config(['geography.catalogue_sha256' => str_repeat('0', 64)]); $this->getJson('/api/divisao-administrativa')->assertServiceUnavailable(); }); test('geographic choices remain paginated without losing older locations', function () { $service = app(AdministrativeGeography::class); $group = collect($service->catalogue()['records'])->filter(fn ($record) => $record['selectable'] && $record['parent_id'] !== null)->groupBy('parent_id')->first(fn ($records) => $records->count() > 50); $parent = $group->first()['parent_id']; $first = $service->children($parent); $second = $service->children($parent, 2); expect($first['data'])->toHaveCount(50)->and($first['has_more'])->toBeTrue()->and($second['data'])->not->toBeEmpty(); expect(array_intersect(array_column($first['data'], 'id'), array_column($second['data'], 'id')))->toBeEmpty(); }); test('audit failure rolls back supplier geography and its validation state together', function () { $supplier = Supplier::query()->create([...$this->data, 'tenant_id' => $this->company->id, 'created_by' => $this->buyer->id, 'status' => 'active']); $this->partialMock(RequisitionWorkflow::class)->shouldReceive('audit')->andThrow(new RuntimeException('Synthetic audit failure')); $this->withoutExceptionHandling(); expect(fn () => $this->put('/fornecedores/'.$supplier->id, [...$this->data, 'version' => 1, 'geography_reference' => $this->reference]))->toThrow(RuntimeException::class, 'Synthetic audit failure'); expect($supplier->refresh()->geography_reference)->toBeNull()->and($supplier->status)->toBe('active')->and($supplier->version)->toBe(1); });