Receber documentos privados e locais de disponibilidade nas candidaturas
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -10,11 +10,15 @@ use App\Services\PeopleAccess;
|
||||
use App\Services\PeopleHiring;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
use App\Services\SubmissionForms;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
|
||||
beforeEach(function () {
|
||||
Storage::fake('candidate_documents');
|
||||
$this->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]);
|
||||
@@ -52,6 +56,17 @@ test('electronic submission returns a scoped receipt and preserves the actual pr
|
||||
$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();
|
||||
@@ -76,7 +91,7 @@ test('received submissions do not become internal evaluations and only the assig
|
||||
$this->postJson('/pessoas/'.$this->process->id.'/publicacao', [...$this->publication, 'version' => 1, 'active' => false])->assertForbidden();
|
||||
});
|
||||
|
||||
test('an opportunity requiring files cannot be published through the data only reception path', function () {
|
||||
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.']);
|
||||
@@ -84,8 +99,86 @@ test('an opportunity requiring files cannot be published through the data only r
|
||||
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)->assertUnprocessable();
|
||||
expect(DB::table('candidate_opportunity_publications')->where('people_process_id', $record->id)->count())->toBe(0);
|
||||
$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
|
||||
|
||||
Reference in New Issue
Block a user