tenant = Tenant::query()->create(['name' => 'Empresa fictícia de cadastro']); $this->author = User::factory()->create(['tenant_id' => $this->tenant->id, 'purchasing_role' => PurchasingRole::Admin]); $this->reviewer = User::factory()->create(['tenant_id' => $this->tenant->id, 'purchasing_role' => PurchasingRole::Admin]); $this->actingAs($this->author); $this->personBody = ['operation_key' => (string) Str::uuid(), 'name' => 'Pessoa original fictícia', 'reference' => 'CAD-001', 'profile_summary' => 'Perfil profissional fictício registado uma vez.', 'reason' => 'Registo inicial fictício autorizado.']; }); function registryOpenProcess(object $test, string $kind = 'service'): PeopleProcess { $key = (string) Str::uuid(); $test->actingAs($test->author)->postJson('/pessoas', ['operation_key' => $key, 'kind' => $kind, 'title' => 'Processo de reutilização fictício', 'purpose' => 'Necessidade fictícia independente do cadastro.', 'closes_on' => today()->toDateString(), 'minimum_reviews' => 1, 'criteria' => [['label' => 'Competências', 'weight' => 100]]])->assertRedirect(); $record = PeopleProcess::query()->where('operation_key', $key)->firstOrFail(); app(PeopleAccess::class)->assign($test->author, $record->id, ['version' => 1, 'user_id' => $test->reviewer->id, 'scopes' => ['review', 'interviews'], 'reason' => 'Conferente fictício do processo.']); $test->actingAs($test->reviewer)->postJson('/pessoas/'.$record->id.'/open', ['version' => 1, 'reason' => 'Abertura conferida de forma independente.', 'no_conflict' => true])->assertRedirect(); return $record->fresh(); } function registryApply(object $test, PeopleProcess $record, array $input): TestResponse { return $test->actingAs($test->author)->postJson('/pessoas/'.$record->id.'/apply', ['version' => $record->fresh()->version, 'operation_key' => (string) Str::uuid(), 'reference' => 'DOC-'.Str::uuid(), 'received_on' => today()->toDateString(), 'reason' => 'Registo fictício de candidatura.', ...$input]); } test('one person is reused across both hiring paths with frozen source versions and no duplicate candidature', function (string $kind) { $this->postJson('/pessoas/cadastro', $this->personBody)->assertRedirect(); $person = DB::table('people_registry')->sole(); $first = registryOpenProcess($this, $kind); $this->actingAs($this->author)->get('/pessoas/'.$first->id.'?cadastro=CAD-001')->assertInertia(fn (Assert $p) => $p->has('registry.items', 1)->where('registry.items.0.id', $person->id)); registryApply($this, $first, ['person_id' => $person->id, 'person_version' => 1, 'name' => 'NOME FORJADO IGNORADO'])->assertRedirect(); $original = DB::table('people_applications')->sole(); expect($original->name)->toBe($this->personBody['name'])->and(json_decode($original->person_snapshot, true)['version'])->toBe(1); $this->postJson('/pessoas/cadastro/'.$person->id, [...$this->personBody, 'version' => 1, 'active' => true, 'name' => 'Nome actualizado fictício', 'profile_summary' => 'Perfil actualizado para novos processos.'])->assertRedirect(); expect(app(PeopleHiring::class)->matrix($first)[0]['name'])->toBe($original->name) ->and(app(PeopleHiring::class)->matrix($first)[0]['registry_source']['version'])->toBe(1); registryApply($this, $first, ['person_id' => $person->id, 'person_version' => 2])->assertConflict(); $second = registryOpenProcess($this, $kind === 'service' ? 'staff' : 'service'); registryApply($this, $second, ['person_id' => $person->id, 'person_version' => 1])->assertUnprocessable(); registryApply($this, $second, ['person_id' => $person->id, 'person_version' => 2, 'summary' => 'Complemento específico desta candidatura.'])->assertRedirect(); $new = DB::table('people_applications')->where('people_process_id', $second->id)->sole(); expect($new->name)->toBe('Nome actualizado fictício')->and($new->summary)->toBe('Complemento específico desta candidatura.') ->and(json_decode($new->person_snapshot, true)['version'])->toBe(2) ->and(DB::table('people_applications')->where('id', $original->id)->first()->person_snapshot)->toBe($original->person_snapshot); $this->postJson('/pessoas/cadastro/'.$person->id, [...$this->personBody, 'version' => 2, 'active' => false])->assertRedirect(); $third = registryOpenProcess($this); registryApply($this, $third, ['person_id' => $person->id, 'person_version' => 3])->assertUnprocessable(); expect(fn () => DB::transaction(fn () => DB::table('people_applications')->where('id', $original->id)->update(['person_snapshot' => '{}'])))->toThrow(QueryException::class); })->with(['service', 'staff']); test('first registration can save a private reusable profile without entering name and profile again', function () { $record = registryOpenProcess($this); registryApply($this, $record, ['name' => 'Pessoa registada no fluxo', 'summary' => 'Experiência profissional fictícia reutilizável.', 'save_person' => true])->assertRedirect(); $person = DB::table('people_registry')->sole(); expect($person->name)->toBe('Pessoa registada no fluxo')->and($person->profile_summary)->toBe('Experiência profissional fictícia reutilizável.') ->and(DB::table('people_applications')->sole()->person_id)->toBe($person->id); $this->actingAs($this->reviewer)->get('/pessoas/cadastro')->assertInertia(fn (Assert $p) => $p->where('records.total', 0)); $this->postJson('/pessoas/cadastro/'.$person->id, [...$this->personBody, 'version' => 1, 'active' => true])->assertNotFound(); }); test('shared directory permissions are explicit and never grant access to a persons processes', function () { $this->postJson('/pessoas/cadastro', $this->personBody)->assertRedirect(); $record = registryOpenProcess($this); $reader = User::factory()->create(['tenant_id' => $this->tenant->id, 'purchasing_role' => PurchasingRole::Configured]); $profile = DB::table('organization_profiles')->insertGetId(['tenant_id' => $this->tenant->id, 'name' => 'Consulta de cadastro', 'permissions' => json_encode(['people_registry_read']), 'version' => 1]); DB::table('organization_memberships')->insert(['tenant_id' => $this->tenant->id, 'user_id' => $reader->id, 'profile_id' => $profile, 'maintenance' => false]); DB::table('organization_access')->insert(['tenant_id' => $this->tenant->id, 'active' => true, 'version' => 1]); $this->actingAs($reader)->get('/pessoas/cadastro')->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('records.data.0.canEdit', false)); $this->get('/pessoas/'.$record->id)->assertForbidden(); $this->postJson('/pessoas/cadastro', [...$this->personBody, 'operation_key' => (string) Str::uuid()])->assertForbidden(); DB::table('organization_memberships')->where('user_id', $reader->id)->update(['expires_at' => now()->subMinute()]); $this->get('/pessoas/cadastro')->assertForbidden(); }); test('registry references are unique per company without merging names and foreign company records cannot be reused', function () { $this->postJson('/pessoas/cadastro', $this->personBody)->assertRedirect(); $this->postJson('/pessoas/cadastro', $this->personBody)->assertRedirect(); expect(DB::table('people_registry')->count())->toBe(1); $this->postJson('/pessoas/cadastro', [...$this->personBody, 'operation_key' => (string) Str::uuid(), 'reference' => ' cad-001 '])->assertUnprocessable(); $foreign = Tenant::query()->create(['name' => 'Outra empresa fictícia']); $user = User::factory()->create(['tenant_id' => $foreign->id, 'purchasing_role' => PurchasingRole::Admin]); $this->actingAs($user)->postJson('/pessoas/cadastro', [...$this->personBody, 'operation_key' => (string) Str::uuid()])->assertRedirect(); $other = DB::table('people_registry')->where('tenant_id', $foreign->id)->sole(); $record = registryOpenProcess($this); registryApply($this, $record, ['person_id' => $other->id, 'person_version' => 1])->assertNotFound(); }); test('audit failure rolls back the first registry record candidature and process version together', function () { $record = registryOpenProcess($this); $version = $record->version; $this->mock(RequisitionWorkflow::class)->shouldReceive('audit')->once()->andThrow(new RuntimeException('Injected registry audit failure')); $this->withoutExceptionHandling(); expect(fn () => registryApply($this, $record, ['save_person' => true, 'name' => 'Pessoa fictícia', 'summary' => 'Perfil fictício que não deve persistir.']))->toThrow(RuntimeException::class); expect(DB::table('people_registry')->count())->toBe(0)->and(DB::table('people_applications')->count())->toBe(0)->and($record->fresh()->version)->toBe($version); });