167 lines
11 KiB
PHP
167 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Testing\TestResponse;
|
|
use Tests\TestCase;
|
|
|
|
final class DeskDocumentReuseTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $owner;
|
|
|
|
private string $caseId;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
Storage::fake('local');
|
|
Queue::fake();
|
|
$this->owner = User::factory()->create();
|
|
$this->actingAs($this->owner);
|
|
$this->caseId = $this->makeCase('SYNTHETIC destination');
|
|
}
|
|
|
|
private function makeCase(string $title): string
|
|
{
|
|
return $this->postJson('/balcao-virtual/dossiers', ['title' => $title, 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
}
|
|
|
|
private function upload(string $case, string $name = 'synthetic-source.txt'): array
|
|
{
|
|
return $this->post('/balcao-virtual/dossiers/'.$case.'/documentos', ['version' => DB::table('desk_cases')->where('id', $case)->value('version'), 'file' => UploadedFile::fake()->createWithContent($name, 'SYNTHETIC '.$name), 'category' => 'project_evidence', 'source_reference' => 'SYNTHETIC source '.$name, 'consent' => true], ['Accept' => 'application/json'])->assertOk()->json();
|
|
}
|
|
|
|
private function catalogue(string $query = ''): TestResponse
|
|
{
|
|
return $this->getJson('/balcao-virtual/dossiers/'.$this->caseId.'/documentos-disponiveis'.$query);
|
|
}
|
|
|
|
public function test_personal_source_is_searchable_and_reused_without_reentering_reference_dates_or_copying_original(): void
|
|
{
|
|
$source = $this->makeCase('SYNTHETIC previous dossier');
|
|
$upload = $this->upload($source);
|
|
DB::table('desk_documents')->where('id', $upload['link_id'])->update(['stage' => 'E2', 'valid_until' => '2028-02-03']);
|
|
$this->catalogue('?q=previous')->assertOk()->assertJsonCount(1, 'documents')->assertJsonPath('documents.0.id', $upload['link_id'])->assertJsonPath('documents.0.valid_until', '2028-02-03')->assertHeader('Cache-Control', 'no-store, private');
|
|
$document = $upload['documents'][0]['document_id'];
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/documentos', ['version' => 1, 'document_id' => $document, 'reuse_link_id' => $upload['link_id'], 'category' => 'resource_evidence', 'consent' => true])->assertOk();
|
|
$this->assertDatabaseCount('documents', 1);
|
|
$this->assertDatabaseCount('desk_documents', 2);
|
|
$this->assertCount(1, Storage::disk('local')->allFiles());
|
|
$this->assertDatabaseHas('desk_documents', ['case_id' => $this->caseId, 'document_id' => $document, 'source_reference' => 'SYNTHETIC source synthetic-source.txt', 'stage' => 'E2', 'valid_until' => '2028-02-03']);
|
|
$this->assertDatabaseHas('desk_cases', ['id' => $source, 'version' => 2]);
|
|
}
|
|
|
|
public function test_catalogue_matches_existing_authority_and_hides_another_dossiers_private_source_from_its_uploader(): void
|
|
{
|
|
$source = $this->makeCase('SYNTHETIC secret organization context');
|
|
$upload = $this->upload($source);
|
|
$org = (string) Str::uuid();
|
|
DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC organization', 'organization_type' => 'private']);
|
|
DB::table('desk_cases')->where('id', $source)->update(['organization_id' => $org]);
|
|
$this->catalogue()->assertOk()->assertJsonCount(1, 'documents')->assertJsonPath('documents.0.id', null)->assertJsonPath('documents.0.source_reference', null)->assertJsonPath('documents.0.origin_label', null);
|
|
$this->catalogue('?q=secret')->assertOk()->assertJsonCount(0, 'documents');
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/documentos', ['version' => 1, 'document_id' => $upload['documents'][0]['document_id'], 'reuse_link_id' => $upload['link_id'], 'category' => 'project_evidence', 'consent' => true])->assertForbidden();
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/documentos', ['version' => 1, 'document_id' => $upload['documents'][0]['document_id'], 'category' => 'project_evidence', 'consent' => true])->assertUnprocessable()->assertJsonValidationErrors('source_reference');
|
|
$other = User::factory()->create();
|
|
config(['portal.managers' => [$other->email]]);
|
|
$this->actingAs($other);
|
|
$this->catalogue()->assertForbidden();
|
|
}
|
|
|
|
public function test_current_organization_files_are_reusable_but_revoked_members_cannot_keep_using_the_catalogue(): void
|
|
{
|
|
$upload = $this->upload($this->caseId);
|
|
$org = (string) Str::uuid();
|
|
DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC collaboration', 'organization_type' => 'private']);
|
|
DB::table('desk_cases')->where('id', $this->caseId)->update(['organization_id' => $org]);
|
|
$colleague = User::factory()->create();
|
|
$membership = (string) Str::uuid();
|
|
DB::table('memberships')->insert(['id' => $membership, 'organization_id' => $org, 'user_id' => $colleague->id, 'role' => 'officer']);
|
|
$this->actingAs($colleague);
|
|
$this->catalogue()->assertOk()->assertJsonPath('documents.0.id', $upload['link_id']);
|
|
DB::table('memberships')->where('id', $membership)->update(['role' => 'viewer']);
|
|
$this->catalogue()->assertForbidden();
|
|
DB::table('memberships')->where('id', $membership)->update(['role' => 'officer', 'deleted_at' => now()]);
|
|
$this->catalogue()->assertForbidden();
|
|
}
|
|
|
|
public function test_unlinked_uploads_are_available_without_invented_provenance_and_foreign_or_deleted_uploads_are_absent(): void
|
|
{
|
|
$upload = $this->upload($this->caseId);
|
|
DB::table('desk_documents')->where('id', $upload['link_id'])->delete();
|
|
$this->catalogue()->assertOk()->assertJsonCount(1, 'documents')->assertJsonPath('documents.0.id', null)->assertJsonPath('documents.0.source_reference', null);
|
|
$document = $upload['documents'][0]['document_id'];
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/documentos', ['version' => 2, 'document_id' => $document, 'category' => 'project_evidence', 'source_reference' => 'SYNTHETIC supplied missing reference', 'consent' => true])->assertOk();
|
|
DB::table('documents')->where('id', $document)->update(['deleted_at' => now()]);
|
|
$this->catalogue()->assertOk()->assertJsonCount(0, 'documents');
|
|
$this->actingAs(User::factory()->create());
|
|
$foreign = $this->makeCase('SYNTHETIC foreign');
|
|
$this->upload($foreign, 'private-foreign.txt');
|
|
$this->actingAs($this->owner);
|
|
$this->catalogue()->assertOk()->assertJsonCount(0, 'documents');
|
|
}
|
|
|
|
public function test_opportunity_source_authority_is_checked_in_catalogue_and_attachment_even_for_uploader(): void
|
|
{
|
|
$upload = $this->upload($this->caseId);
|
|
$document = $upload['documents'][0]['document_id'];
|
|
DB::table('documents')->where('id', $document)->update(['source_system' => 'opportunity-resources']);
|
|
$this->catalogue()->assertOk()->assertJsonCount(0, 'documents');
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/documentos', ['version' => 2, 'document_id' => $document, 'category' => 'project_evidence', 'consent' => true])->assertForbidden();
|
|
$this->assertDatabaseHas('desk_cases', ['id' => $this->caseId, 'version' => 2]);
|
|
}
|
|
|
|
public function test_literal_search_pagination_and_invalid_input_do_not_expand_scope(): void
|
|
{
|
|
$upload = $this->upload($this->caseId, 'SYNTHETIC %_ literal.txt');
|
|
$row = (array) DB::table('documents')->where('id', $upload['documents'][0]['document_id'])->first();
|
|
for ($i = 0; $i < 27; $i++) {
|
|
$copy = (string) Str::uuid();
|
|
$key = 'synthetic-catalogue/'.$copy;
|
|
Storage::disk('local')->put($key, Storage::disk('local')->get($row['storage_key']));
|
|
DB::table('documents')->insert([...$row, 'id' => $copy, 'storage_key' => $key, 'file_name' => sprintf('SYNTHETIC paged %02d.txt', $i)]);
|
|
}
|
|
$this->catalogue('?q='.urlencode('%_'))->assertOk()->assertJsonCount(1, 'documents')->assertJsonPath('documents.0.file_name', 'SYNTHETIC %_ literal.txt');
|
|
$first = $this->catalogue('?q=paged')->assertOk()->assertJsonCount(25, 'documents')->assertJsonPath('next_offset', 25)->json('documents');
|
|
$last = $this->catalogue('?q=paged&offset=25')->assertOk()->assertJsonCount(2, 'documents')->assertJsonPath('next_offset', null)->json('documents');
|
|
$this->assertCount(27, array_unique(array_column([...$first, ...$last], 'document_id')));
|
|
$this->catalogue('?offset=-1')->assertUnprocessable();
|
|
$this->catalogue('?q='.str_repeat('x', 121))->assertUnprocessable();
|
|
}
|
|
|
|
public function test_selected_stage_link_preserves_its_exact_provenance_and_legacy_document_id_still_works(): void
|
|
{
|
|
$upload = $this->upload($this->caseId);
|
|
$other = $this->makeCase('SYNTHETIC other source');
|
|
$foreign = $this->upload($other, 'foreign-link.txt');
|
|
$body = ['version' => 2, 'stage' => 'E0', 'status' => 'preparing', 'reason' => 'SYNTHETIC exact support', 'document_link_id' => $upload['link_id']];
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/etapas', [...$body, 'document_link_id' => $foreign['link_id']])->assertUnprocessable();
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/etapas', $body)->assertOk();
|
|
$case = DB::table('desk_cases')->where('id', $this->caseId)->first();
|
|
$snapshot = json_decode($case->workflow, true)['E0'];
|
|
$this->assertSame($upload['link_id'], $snapshot['document_link_id']);
|
|
$this->assertSame($upload['documents'][0]['document_id'], $snapshot['document_id']);
|
|
$this->assertSame('SYNTHETIC source synthetic-source.txt', $snapshot['document_source']['source_reference']);
|
|
$this->postJson('/balcao-virtual/dossiers/'.$this->caseId.'/etapas', ['version' => 3, 'stage' => 'E0', 'status' => 'preparing', 'reason' => 'SYNTHETIC legacy client', 'document_id' => $upload['documents'][0]['document_id']])->assertOk();
|
|
}
|
|
|
|
public function test_closed_dossiers_remain_readable_without_upload_or_catalogue_and_anonymous_access_is_refused(): void
|
|
{
|
|
DB::table('desk_cases')->where('id', $this->caseId)->update(['closed_at' => now()]);
|
|
$this->getJson('/balcao-virtual/dossiers/'.$this->caseId)->assertOk()->assertJsonPath('canWrite', false);
|
|
$this->catalogue()->assertConflict();
|
|
auth()->logout();
|
|
$this->catalogue()->assertUnauthorized();
|
|
}
|
|
}
|