tenant = Tenant::query()->create(['name' => 'Empresa fictícia de submissões']); $this->admin = 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->admin); app(CandidateProfiles::class)->configure($this->admin, ['version' => 0, 'registration_open' => true, 'notice' => 'Aviso fictício para submissões sem dados reais.', 'reason' => 'Activação fictícia para testar submissões.']); $this->definition = ['available' => true, 'fields' => [['key' => 'full_name', 'label' => 'Nome completo', 'type' => 'text', 'required' => true], ['key' => 'availability_location', 'label' => 'Disponibilidade', 'type' => 'location', 'required' => true]], 'documents' => []]; app(SubmissionForms::class)->save($this->admin, ['audience' => 'service', 'modality' => 'competition', 'version' => 0, 'definition' => $this->definition, 'reason' => 'Formulário fictício para esta oportunidade.']); $this->process = app(PeopleHiring::class)->execute($this->admin, null, 'create', ['operation_key' => (string) Str::uuid(), 'kind' => 'service', 'title' => 'Oportunidade fictícia', 'purpose' => 'Finalidade interna que não deve ser publicada.', 'closes_on' => today()->addWeek()->toDateString(), 'minimum_reviews' => 1, 'criteria' => [['label' => 'Competências', 'weight' => 100]]]); app(PeopleAccess::class)->assign($this->admin, $this->process->id, ['version' => 1, 'user_id' => $this->reviewer->id, 'scopes' => ['review'], 'reason' => 'Conferência independente da abertura.']); $this->actingAs($this->reviewer); app(PeopleHiring::class)->execute($this->reviewer, $this->process->id, 'open', ['version' => 1, 'reason' => 'Abertura fictícia do processo.', 'no_conflict' => true]); $this->actingAs($this->admin); $this->publication = ['version' => 0, 'active' => true, 'description' => 'Descrição pública fictícia da oportunidade.', 'reason' => 'Publicação fictícia para candidaturas.']; app(CandidateSubmissions::class)->publish($this->admin, $this->process->id, $this->publication); $this->credentials = ['email' => 'submit@example.invalid', 'password' => 'Candidate-Test-123!', 'password_confirmation' => 'Candidate-Test-123!', 'portal_version' => 1, 'notice_acknowledged' => true]; $this->account = app(CandidateProfiles::class)->register($this->tenant->id, $this->credentials); $this->account->setAttribute('email_verified_at', now())->save(); $this->profile = ['version' => 0, 'name' => 'Pessoa fictícia', 'phone' => '000000', 'summary' => 'Perfil fictício de competências.', 'availability' => ['province' => 'Província fictícia', 'district' => 'Distrito fictício', 'locality' => 'Localidade fictícia'], 'reason' => 'Preencher perfil fictício para candidatura.']; app(CandidateProfiles::class)->save($this->account, $this->profile); $this->base = '/candidaturas/'.$this->tenant->id; $this->postJson($this->base.'/entrar', $this->credentials)->assertRedirect(); $this->body = ['operation_key' => (string) Str::uuid(), 'profile_version' => 1, 'publication_version' => 1, 'answers' => ['full_name' => 'Pessoa fictícia', 'availability_location' => $this->profile['availability']]]; }); test('guests see only explicitly published public opportunity fields while submission remains authenticated', function () { Auth::guard('candidate')->logout(); Auth::guard('web')->logout(); $this->get($this->base)->assertInertia(fn (Assert $p) => $p->has('opportunities.data', 1) ->where('opportunities.data.0', ['id' => $this->process->id, 'title' => 'Oportunidade fictícia', 'kind' => 'service', 'closes_on' => $this->process->closes_on, 'description' => $this->publication['description']]) ->where('auth.user', null)->where('organizationAccess', null)); $this->get($this->base.'/oportunidades/'.$this->process->id)->assertUnauthorized(); $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertUnauthorized(); expect(DB::table('candidate_submissions')->count())->toBe(0); $this->actingAs($this->admin); app(CandidateSubmissions::class)->publish($this->admin, $this->process->id, [...$this->publication, 'version' => 1, 'active' => false]); $this->get($this->base)->assertInertia(fn (Assert $p) => $p->has('opportunities.data', 0)); app(CandidateSubmissions::class)->publish($this->admin, $this->process->id, [...$this->publication, 'version' => 2]); $this->travel(8)->days(); $this->get($this->base)->assertInertia(fn (Assert $p) => $p->has('opportunities.data', 0)); }); test('public opportunity listing is paginated within the company and remains visible to existing users when registration closes', function () { $process = (array) DB::table('people_processes')->where('id', $this->process->id)->first(); $publication = (array) DB::table('candidate_opportunity_publications')->where('people_process_id', $this->process->id)->first(); unset($process['id'], $publication['id']); for ($index = 0; $index < 12; $index++) { $id = DB::table('people_processes')->insertGetId([...$process, 'operation_key' => (string) Str::uuid()]); DB::table('candidate_opportunity_publications')->insert([...$publication, 'people_process_id' => $id]); } app(CandidateProfiles::class)->configure($this->admin, ['version' => 1, 'registration_open' => false, 'notice' => 'Aviso fictício de registos temporariamente encerrados.', 'reason' => 'Encerrar novos registos no ensaio.']); $this->get($this->base)->assertInertia(fn (Assert $p) => $p->has('opportunities.data', 12)->where('opportunities.total', 13)->where('opportunities.next_page_url', $this->base.'?opportunities_page=2')->where('portal.registration_open', false)); $this->get($this->base.'?opportunities_page=2')->assertInertia(fn (Assert $p) => $p->has('opportunities.data', 1)); $this->get($this->base.'/perfil?opportunities_page=2')->assertInertia(fn (Assert $p) => $p->has('opportunities', 1)->where('opportunityPagination.prev', $this->base.'/perfil?opportunities_page=1')->where('opportunityPagination.next', null)); $other = Tenant::query()->create(['name' => 'Empresa sem oportunidades']); $otherAdmin = User::factory()->create(['tenant_id' => $other->id, 'purchasing_role' => PurchasingRole::Admin]); $this->actingAs($otherAdmin); app(CandidateProfiles::class)->configure($otherAdmin, ['version' => 0, 'registration_open' => true, 'notice' => 'Aviso fictício da outra empresa sem oportunidades.', 'reason' => 'Configurar portal separado no ensaio.']); $this->get('/candidaturas/'.$other->id)->assertInertia(fn (Assert $p) => $p->has('opportunities.data', 0)); $this->getJson($this->base.'?opportunities_page=invalid')->assertUnprocessable(); }); test('electronic submission returns a scoped receipt and preserves the actual profile and answers', function () { $this->get($this->base.'/oportunidades/'.$this->process->id)->assertInertia(fn (Assert $p) => $p->where('opportunity.description', 'Descrição pública fictícia da oportunidade.')->missing('opportunity.purpose')->missing('opportunity.selection')); $url = $this->base.'/oportunidades/'.$this->process->id; $this->postJson($url, $this->body)->assertRedirect(); $row = DB::table('candidate_submissions')->sole(); $this->get($this->base.'/submissoes/'.$row->id)->assertInertia(fn (Assert $p) => $p->where('receipt.snapshot.profile.version', 1)->where('receipt.snapshot.answers.availability_location.district', 'Distrito fictício')->where('receipt.snapshot.status', 'received')->missing('receipt.snapshot.reviews')); app(CandidateProfiles::class)->save($this->account, [...$this->profile, 'version' => 1, 'name' => 'Nome posterior']); $this->postJson($url, $this->body)->assertRedirect(); expect(DB::table('candidate_submissions')->count())->toBe(1)->and(json_decode($row->snapshot, true)['profile']['data']['name'])->toBe('Pessoa fictícia'); $this->postJson($url, [...$this->body, 'operation_key' => (string) Str::uuid(), 'profile_version' => 2])->assertConflict(); }); test('all availability locations travel with the submitted profile and remain unchanged after profile edits', function () { $extra = ['province' => 'Outra província', 'district' => 'Outro distrito', 'locality' => 'Outra localidade']; app(CandidateProfiles::class)->save($this->account, [...$this->profile, 'version' => 1, 'additional_availability' => [$extra]]); $this->postJson($this->base.'/oportunidades/'.$this->process->id, [...$this->body, 'profile_version' => 2])->assertRedirect(); $row = DB::table('candidate_submissions')->sole(); app(CandidateProfiles::class)->save($this->account, [...$this->profile, 'version' => 2, 'additional_availability' => []]); $this->get($this->base.'/submissoes/'.$row->id)->assertInertia(fn (Assert $p) => $p->where('receipt.snapshot.profile.data.additional_availability', [$extra])); $this->get('/pessoas/'.$this->process->id.'/recebidas')->assertInertia(fn (Assert $p) => $p->where('submissions.data.0.snapshot.profile.data.additional_availability', [$extra])); expect(DB::table('candidate_submissions')->sole()->snapshot)->toBe($row->snapshot); }); test('withdrawn opportunities stale profiles and unverified contacts cannot submit', function () { $url = $this->base.'/oportunidades/'.$this->process->id; $this->postJson($url, [...$this->body, 'profile_version' => 99])->assertUnprocessable(); $this->account->setAttribute('email_verified_at', null)->save(); $this->postJson($url, $this->body)->assertForbidden(); $this->account->setAttribute('email_verified_at', now())->save(); app(CandidateSubmissions::class)->publish($this->admin, $this->process->id, [...$this->publication, 'version' => 1, 'active' => false]); $this->postJson($url, $this->body)->assertNotFound(); expect(DB::table('candidate_submissions')->count())->toBe(0); }); test('received submissions do not become internal evaluations and only the assigned preparer reads the inbox', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); expect(DB::table('people_applications')->count())->toBe(0); $url = '/pessoas/'.$this->process->id.'/recebidas'; $this->get($url)->assertInertia(fn (Assert $p) => $p->has('submissions.data', 1)); $this->actingAs($this->reviewer)->get($url)->assertForbidden(); $other = Tenant::query()->create(['name' => 'Outra empresa fictícia']); $this->get('/candidaturas/'.$other->id.'/submissoes/1')->assertUnauthorized(); $this->admin->forceFill(['purchasing_role' => PurchasingRole::Requester])->save(); $this->actingAs($this->admin)->get($url)->assertForbidden(); $this->postJson('/pessoas/'.$this->process->id.'/publicacao', [...$this->publication, 'version' => 1, 'active' => false])->assertForbidden(); }); test('an opportunity requiring a CV can be published but cannot receive an incomplete submission', function () { $this->actingAs($this->admin); $definition = [...$this->definition, 'documents' => [['key' => 'cv', 'label' => 'CV', 'formats' => ['pdf'], 'max_kb' => 1024, 'required' => true]]]; app(SubmissionForms::class)->save($this->admin, ['audience' => 'service', 'modality' => 'competition', 'version' => 1, 'definition' => $definition, 'reason' => 'Exigir CV no novo formulário fictício.']); $record = app(PeopleHiring::class)->execute($this->admin, null, 'create', ['operation_key' => (string) Str::uuid(), 'kind' => 'service', 'title' => 'Oportunidade com CV', 'purpose' => 'Teste de documento obrigatório na candidatura.', 'closes_on' => today()->addWeek()->toDateString(), 'minimum_reviews' => 1, 'criteria' => [['label' => 'Competências', 'weight' => 100]]]); app(PeopleAccess::class)->assign($this->admin, $record->id, ['version' => 1, 'user_id' => $this->reviewer->id, 'scopes' => ['review'], 'reason' => 'Conferência independente do novo processo.']); $this->actingAs($this->reviewer); app(PeopleHiring::class)->execute($this->reviewer, $record->id, 'open', ['version' => 1, 'reason' => 'Abertura fictícia para testar os anexos.', 'no_conflict' => true]); $this->actingAs($this->admin)->postJson('/pessoas/'.$record->id.'/publicacao', $this->publication)->assertRedirect(); $this->postJson($this->base.'/oportunidades/'.$record->id, $this->body)->assertUnprocessable(); expect(DB::table('candidate_submissions')->where('people_process_id', $record->id)->count())->toBe(0); }); function requireElectronicCv(object $test): void { $test->actingAs($test->admin); $definition = [...$test->definition, 'documents' => [['key' => 'cv', 'label' => 'Currículo', 'formats' => ['pdf'], 'max_kb' => 1024, 'required' => true]]]; app(SubmissionForms::class)->save($test->admin, ['audience' => 'service', 'modality' => 'competition', 'version' => 1, 'definition' => $definition, 'reason' => 'Exigir currículo no formulário de teste.']); $test->process = app(PeopleHiring::class)->execute($test->admin, null, 'create', ['operation_key' => (string) Str::uuid(), 'kind' => 'service', 'title' => 'Processo com currículo', 'purpose' => 'Conferência fictícia da recepção de documentos.', 'closes_on' => today()->addWeek()->toDateString(), 'minimum_reviews' => 1, 'criteria' => [['label' => 'Competências', 'weight' => 100]]]); app(PeopleAccess::class)->assign($test->admin, $test->process->id, ['version' => 1, 'user_id' => $test->reviewer->id, 'scopes' => ['review'], 'reason' => 'Conferência independente do processo com CV.']); $test->actingAs($test->reviewer); app(PeopleHiring::class)->execute($test->reviewer, $test->process->id, 'open', ['version' => 1, 'reason' => 'Abrir processo fictício com documentos.', 'no_conflict' => true]); $test->actingAs($test->admin); app(CandidateSubmissions::class)->publish($test->admin, $test->process->id, $test->publication); } function electronicPdf(string $extra = ''): UploadedFile { return UploadedFile::fake()->createWithContent('curriculo.pdf', "%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n".str_repeat(' ', 1024).$extra."\n%%EOF\n"); } test('private CV receipt is immutable idempotent and downloadable only by its owner and authorised staff', function () { requireElectronicCv($this); $url = $this->base.'/oportunidades/'.$this->process->id; $payload = [...$this->body, 'documents' => ['cv' => electronicPdf()]]; $this->post($url, $payload)->assertRedirect()->assertSessionHasNoErrors(); $row = DB::table('candidate_submissions')->sole(); $document = DB::table('candidate_submission_documents')->sole(); Storage::disk('candidate_documents')->assertExists($document->path); $receipt = $this->base.'/submissoes/'.$row->id; $this->get($receipt)->assertInertia(fn (Assert $p) => $p->where('receipt.snapshot.documents.cv.sha256', $document->sha256)->missing('receipt.snapshot.documents.cv.path')); $this->get($receipt.'/documentos/cv')->assertOk()->assertHeader('X-Content-Type-Options', 'nosniff')->assertDownload('curriculo.pdf'); $foreign = Tenant::query()->create(['name' => 'Empresa estrangeira fictícia']); $this->get('/candidaturas/'.$foreign->id.'/submissoes/'.$row->id.'/documentos/cv')->assertUnauthorized(); $staffUrl = '/pessoas/'.$this->process->id.'/recebidas/'.$row->id.'/documentos/cv'; $this->get($staffUrl)->assertOk()->assertDownload('curriculo.pdf'); $this->post($url, [...$this->body, 'documents' => ['cv' => electronicPdf()]])->assertRedirect(); expect(DB::table('candidate_submission_documents')->count())->toBe(1)->and(Storage::disk('candidate_documents')->allFiles())->toHaveCount(1); $this->post($url, [...$this->body, 'documents' => ['cv' => electronicPdf('changed')]])->assertConflict(); $this->actingAs($this->reviewer)->get($staffUrl)->assertForbidden(); $this->post($this->base.'/sair')->assertRedirect(); $this->get($receipt.'/documentos/cv')->assertUnauthorized(); $this->postJson($this->base.'/registar', [...$this->credentials, 'email' => 'other@example.invalid'])->assertRedirect(); $this->get($receipt.'/documentos/cv')->assertNotFound(); expect(DB::table('candidate_submissions')->sole()->snapshot)->toBe($row->snapshot); }); test('missing forged unknown oversized and globally excessive files are rejected without stored files', function () { requireElectronicCv($this); $url = $this->base.'/oportunidades/'.$this->process->id; foreach ([[], ['cv' => UploadedFile::fake()->createWithContent('fake.pdf', 'not a pdf')], ['unknown' => electronicPdf()], ['cv' => UploadedFile::fake()->create('large.pdf', 1025, 'application/pdf')]] as $documents) { $this->postJson($url, [...$this->body, 'documents' => $documents])->assertUnprocessable(); } config(['candidate_documents.max_total_kb' => 1]); $this->postJson($url, [...$this->body, 'documents' => ['cv' => electronicPdf(str_repeat('x', 2048))]])->assertUnprocessable(); expect(DB::table('candidate_submissions')->count())->toBe(0)->and(Storage::disk('candidate_documents')->allFiles())->toBe([]); }); test('failed audit removes uploaded bytes and rolls back the submission and document metadata', function () { requireElectronicCv($this); $this->partialMock(CandidateProfiles::class, fn ($mock) => $mock->shouldReceive('event')->once()->andThrow(new RuntimeException('Injected upload audit failure'))); $this->withoutExceptionHandling(); try { $this->post($this->base.'/oportunidades/'.$this->process->id, [...$this->body, 'documents' => ['cv' => electronicPdf()]]); $this->fail('Expected audit failure.'); } catch (RuntimeException $error) { expect($error->getMessage())->toBe('Injected upload audit failure'); } expect(DB::table('candidate_submissions')->count())->toBe(0)->and(DB::table('candidate_submission_documents')->count())->toBe(0)->and(Storage::disk('candidate_documents')->allFiles())->toBe([]); }); test('download refuses changed stored bytes and document rows cannot be rewritten', function () { requireElectronicCv($this); $this->post($this->base.'/oportunidades/'.$this->process->id, [...$this->body, 'documents' => ['cv' => electronicPdf()]])->assertRedirect(); $document = DB::table('candidate_submission_documents')->sole(); Storage::disk('candidate_documents')->put($document->path, 'changed bytes'); $this->get($this->base.'/submissoes/'.$document->candidate_submission_id.'/documentos/cv')->assertConflict(); expect(fn () => DB::table('candidate_submission_documents')->where('id', $document->id)->update(['original_name' => 'other.pdf']))->toThrow(QueryException::class); }); function electronicTriagePayload(object $test, string $outcome = 'accepted_for_analysis'): array { return ['operation_key' => (string) Str::uuid(), 'process_version' => $test->process->fresh()->version, 'outcome' => $outcome, 'reason' => 'MOTIVO-INTERNO-RESERVADO da conferência fictícia.', 'public_message' => 'A empresa concluiu a triagem desta candidatura.', 'no_conflict' => true]; } test('triage reuses the submitted profile after the deadline without rewriting the receipt or admitting the triager as evaluator', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); $submission = DB::table('candidate_submissions')->sole(); $snapshot = $submission->snapshot; app(CandidateProfiles::class)->save($this->account, [...$this->profile, 'version' => 1, 'name' => 'Nome alterado depois de submeter']); $this->travel(8)->days(); $close = ['version' => $this->process->fresh()->version, 'reason' => 'Concluir recepção do processo fictício.']; $this->postJson('/pessoas/'.$this->process->id.'/close', $close)->assertUnprocessable(); $this->get('/pessoas')->assertInertia(fn (Assert $p) => $p->where('workdesk.tasks.0.action', 'triage')); $body = electronicTriagePayload($this); $url = '/pessoas/'.$this->process->id.'/recebidas/'.$submission->id.'/triagem'; $this->postJson($url, $body)->assertRedirect(); $this->postJson($url, $body)->assertRedirect(); $application = DB::table('people_applications')->sole(); expect($application->name)->toBe('Pessoa fictícia')->and($application->reference)->toBe($submission->reference)->and($application->created_by)->toBe($this->admin->id) ->and(DB::table('candidate_submissions')->sole()->snapshot)->toBe($snapshot)->and(DB::table('candidate_triage')->count())->toBe(1); $this->postJson('/pessoas/'.$this->process->id.'/close', $close)->assertRedirect(); $record = $this->process->fresh(); $evaluation = ['version' => $record->version, 'reason' => 'Avaliar candidatura fictícia com evidências.', 'no_conflict' => true, 'application_id' => $application->id, 'answers' => [['score' => '8', 'evidence' => 'Evidência fictícia das competências.']]]; $this->postJson('/pessoas/'.$record->id.'/evaluate', $evaluation)->assertForbidden(); $this->actingAs($this->reviewer)->postJson('/pessoas/'.$record->id.'/evaluate', $evaluation)->assertRedirect(); expect(app(PeopleHiring::class)->matrix($record)[0]['electronic_source']['profile_version'])->toBe(1); }); test('candidate sees only the approved triage communication and refusal creates no assessment row', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); $submission = DB::table('candidate_submissions')->sole(); $body = electronicTriagePayload($this, 'not_accepted'); $url = '/pessoas/'.$this->process->id.'/recebidas/'.$submission->id.'/triagem'; $this->postJson($url, $body)->assertRedirect(); expect(DB::table('people_applications')->count())->toBe(0); $this->get($this->base.'/submissoes/'.$submission->id)->assertInertia(fn (Assert $p) => $p->where('decision.outcome', 'not_accepted')->where('decision.message', $body['public_message'])->missing('decision.reason')->missing('decision.created_by')->missing('decision.people_application_id'))->assertDontSee('MOTIVO-INTERNO-RESERVADO'); $this->postJson($url, electronicTriagePayload($this))->assertConflict(); }); test('triage respects current assignment stale process versions and separates confidential source reading', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); $submission = DB::table('candidate_submissions')->sole(); $body = electronicTriagePayload($this); $url = '/pessoas/'.$this->process->id.'/recebidas/'.$submission->id.'/triagem'; $this->postJson($url, [...$body, 'process_version' => 99])->assertUnprocessable(); $this->actingAs($this->reviewer)->postJson($url, $body)->assertForbidden(); $this->get('/pessoas/'.$this->process->id.'/recebidas')->assertForbidden(); $this->actingAs($this->admin); $state = DB::table('people_access_states')->where('people_process_id', $this->process->id)->value('version'); app(PeopleAccess::class)->assign($this->admin, $this->process->id, ['version' => $state, 'user_id' => $this->reviewer->id, 'scopes' => ['review', 'interviews'], 'reason' => 'Autorizar consulta das evidências para análise.']); $this->actingAs($this->reviewer)->get('/pessoas/'.$this->process->id.'/recebidas')->assertInertia(fn (Assert $p) => $p->where('canPrepare', false)->where('canTriage', false)->has('submissions.data', 1)); $this->postJson($url, $body)->assertForbidden(); }); test('triage audit failure rolls back the linked internal candidature decision and candidate event', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); $submission = DB::table('candidate_submissions')->sole(); $events = DB::table('candidate_events')->count(); $this->mock(RequisitionWorkflow::class)->shouldReceive('audit')->once()->andThrow(new RuntimeException('Injected triage audit failure')); expect(fn () => app(CandidateTriage::class)->decide($this->admin, $this->process->id, $submission->id, electronicTriagePayload($this)))->toThrow(RuntimeException::class); expect(DB::table('candidate_triage')->count())->toBe(0)->and(DB::table('people_applications')->count())->toBe(0)->and(DB::table('candidate_events')->count())->toBe($events); }); test('a reference already entered manually requires reconciliation instead of automatic merging', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); $submission = DB::table('candidate_submissions')->sole(); DB::table('people_applications')->insert(['tenant_id' => $this->tenant->id, 'people_process_id' => $this->process->id, 'created_by' => $this->admin->id, 'operation_key' => (string) Str::uuid(), 'name' => 'Registo manual anterior', 'summary' => 'Não fundir este registo automaticamente.', 'reference' => $submission->reference, 'received_on' => today(), 'created_at' => now(), 'updated_at' => now()]); $this->postJson('/pessoas/'.$this->process->id.'/recebidas/'.$submission->id.'/triagem', electronicTriagePayload($this))->assertConflict(); expect(DB::table('candidate_triage')->count())->toBe(0)->and(DB::table('people_applications')->count())->toBe(1); }); test('legacy assessment already under review cannot receive a new admitted candidature through triage', function () { $this->postJson($this->base.'/oportunidades/'.$this->process->id, $this->body)->assertRedirect(); $submission = DB::table('candidate_submissions')->sole(); // Fixture for a process whose assessment began before triage was introduced. $application = DB::table('people_applications')->insertGetId(['tenant_id' => $this->tenant->id, 'people_process_id' => $this->process->id, 'created_by' => $this->admin->id, 'operation_key' => (string) Str::uuid(), 'name' => 'Candidatura anterior', 'summary' => 'Registo anterior à introdução da triagem.', 'reference' => 'LEGACY-TEST', 'received_on' => today(), 'created_at' => now(), 'updated_at' => now()]); DB::table('people_reviews')->insert(['tenant_id' => $this->tenant->id, 'people_application_id' => $application, 'reviewed_by' => $this->reviewer->id, 'answers' => '[]', 'reason' => 'Avaliação anterior da fixture.', 'score_minor' => 800, 'created_at' => now(), 'updated_at' => now()]); DB::table('people_processes')->where('id', $this->process->id)->update(['status' => 'assessment', 'version' => $this->process->fresh()->version + 1]); $url = '/pessoas/'.$this->process->id.'/recebidas/'.$submission->id.'/triagem'; $this->postJson($url, electronicTriagePayload($this))->assertConflict(); $this->postJson($url, electronicTriagePayload($this, 'not_accepted'))->assertRedirect(); expect(DB::table('people_applications')->count())->toBe(1); });