f = receiptFixture(); $this->admin = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['department']->id, 'purchasing_role' => PurchasingRole::Admin]); $this->actingAs($this->admin); $this->base = '/candidaturas/'.$this->f['tenant']->id; $this->settings = ['version' => 0, 'registration_open' => true, 'notice' => 'Aviso fictício para testar o perfil, sem utilização de dados reais.', 'reason' => 'Configuração fictícia da área do candidato.']; $this->registration = ['email' => 'candidate@example.invalid', 'password' => 'Candidate-Test-123!', 'password_confirmation' => 'Candidate-Test-123!', 'notice_acknowledged' => true, 'portal_version' => 1]; $this->profile = ['version' => 0, 'name' => 'Pessoa fictícia', 'phone' => '000000', 'summary' => 'Resumo fictício de competências.', 'availability' => ['province' => 'Província fictícia', 'district' => 'Distrito fictício', 'locality' => 'Localidade fictícia'], 'reason' => 'Primeiro preenchimento fictício.']; }); test('portal starts unavailable and only company administrators configure it', function () { $this->get($this->base)->assertNotFound(); $this->actingAs($this->f['receiver'])->postJson('/administracao/candidaturas/portal', $this->settings)->assertForbidden(); $this->actingAs($this->admin)->postJson('/administracao/candidaturas/portal', $this->settings)->assertRedirect(); $this->postJson('/administracao/candidaturas/portal', $this->settings)->assertUnprocessable(); $this->get($this->base)->assertInertia(fn (Assert $p) => $p->where('portal.registration_open', true)->where('auth.user', null)->where('organizationAccess', null)); }); test('candidate account is separate from staff and profile versions preserve geography without granting internal access', function () { app(CandidateProfiles::class)->configure($this->admin, $this->settings); Auth::guard('web')->logout(); $this->postJson($this->base.'/registar', $this->registration)->assertRedirect($this->base.'/perfil'); $account = CandidateAccount::query()->sole(); expect(Hash::check($this->registration['password'], $account->password))->toBeTrue(); $this->assertAuthenticatedAs($account, 'candidate'); $this->assertGuest('web'); $this->get('/pessoas')->assertRedirect(); $this->postJson($this->base.'/perfil', $this->profile)->assertRedirect(); $this->postJson($this->base.'/perfil', $this->profile)->assertUnprocessable(); $this->postJson($this->base.'/perfil', [...$this->profile, 'version' => 1, 'name' => 'Pessoa fictícia actualizada'])->assertRedirect(); $this->get($this->base.'/perfil')->assertInertia(fn (Assert $p) => $p->where('profile.version', 2)->where('profile.data.name', 'Pessoa fictícia actualizada')->where('profile.data.availability.district', 'Distrito fictício')->where('auth.user', null)->missing('profile.data.password')->missing('reviews')); $first = DB::table('candidate_profile_versions')->orderBy('version')->first(); expect(json_decode($first->data, true)['name'])->toBe('Pessoa fictícia'); $this->post($this->base.'/sair')->assertRedirect($this->base); $this->assertGuest('candidate'); $this->get($this->base.'/perfil')->assertRedirect($this->base); }); test('closed registration preserves existing access and stale notices cannot create accounts', function () { app(CandidateProfiles::class)->configure($this->admin, $this->settings); $this->postJson($this->base.'/registar', [...$this->registration, 'portal_version' => 2])->assertUnprocessable(); $this->postJson($this->base.'/registar', $this->registration)->assertRedirect(); app(CandidateProfiles::class)->configure($this->admin, [...$this->settings, 'version' => 1, 'registration_open' => false]); $this->post($this->base.'/sair')->assertRedirect(); $this->postJson($this->base.'/registar', [...$this->registration, 'email' => 'other@example.invalid', 'portal_version' => 2])->assertForbidden(); $this->postJson($this->base.'/entrar', $this->registration)->assertRedirect(); $this->get($this->base.'/perfil')->assertOk(); $this->assertAuthenticatedAs($this->admin, 'web'); }); test('one candidate never reads another company profile or another account profile', function () { app(CandidateProfiles::class)->configure($this->admin, $this->settings); $this->postJson($this->base.'/registar', $this->registration)->assertRedirect(); $first = CandidateAccount::query()->sole(); $this->postJson($this->base.'/perfil', $this->profile)->assertRedirect(); $foreign = receiptFixture(); $this->postJson('/candidaturas/'.$foreign['tenant']->id.'/perfil', $this->profile)->assertUnauthorized(); $this->get('/candidaturas/'.$foreign['tenant']->id.'/perfil')->assertRedirect(); $this->post($this->base.'/sair')->assertRedirect(); $this->postJson($this->base.'/registar', [...$this->registration, 'email' => 'another@example.invalid'])->assertRedirect(); $this->get($this->base.'/perfil')->assertInertia(fn (Assert $p) => $p->where('profile.version', 0)->where('profile.data.name', '')); $this->postJson($this->base.'/perfil', [...$this->profile, 'candidate_account_id' => $first->id])->assertRedirect(); expect(DB::table('candidate_profile_versions')->where('candidate_account_id', $first->id)->count())->toBe(1); }); test('profile and event writes roll back together and missing geography is rejected', function () { app(CandidateProfiles::class)->configure($this->admin, $this->settings); $this->postJson($this->base.'/registar', $this->registration)->assertRedirect(); $this->postJson($this->base.'/perfil', [...$this->profile, 'availability' => ['province' => 'Parcial']])->assertUnprocessable(); $this->partialMock(CandidateProfiles::class, fn ($mock) => $mock->shouldReceive('event')->once()->andThrow(new RuntimeException('Injected candidate audit failure'))); $this->withoutExceptionHandling(); expect(fn () => $this->postJson($this->base.'/perfil', $this->profile))->toThrow(RuntimeException::class); expect(DB::table('candidate_profile_versions')->count())->toBe(0); });