172 lines
10 KiB
PHP
172 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use App\Services\PartnershipExchange;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
use Tests\TestCase;
|
|
|
|
final class PartnershipPublicationSummaryTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $owner;
|
|
|
|
private User $moderator;
|
|
|
|
private object $record;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
Http::fake();
|
|
Mail::fake();
|
|
Queue::fake();
|
|
$this->owner = User::factory()->create();
|
|
$this->moderator = User::factory()->create();
|
|
config(['portal.managers' => [$this->moderator->email]]);
|
|
$this->record = app(PartnershipExchange::class)->save($this->owner, ['client_key' => (string) Str::uuid(), 'title' => 'SYNTHETIC original history title', 'kind' => 'seeking', 'partnership_type' => 'joint_venture', 'offer' => 'SYNTHETIC original offer', 'payload' => ['consent' => true, 'confidentiality' => 'private', 'mandatory' => ['capacity']]]);
|
|
$this->actingAs($this->owner);
|
|
}
|
|
|
|
private function url(?int $id = null): string
|
|
{
|
|
return '/parcerias/'.($id ?? $this->record->id).'/historico';
|
|
}
|
|
|
|
private function edit(array $data = []): object
|
|
{
|
|
$current = app(PartnershipExchange::class)->find($this->record->id);
|
|
|
|
return app(PartnershipExchange::class)->save($this->owner, ['version' => $current->version, 'kind' => 'seeking', ...$data], $current->id);
|
|
}
|
|
|
|
private function organization(User $person, string $role = 'officer'): string
|
|
{
|
|
$id = (string) Str::uuid();
|
|
DB::table('organizations')->insert(['id' => $id, 'name' => 'SYNTHETIC versions organization', 'organization_type' => 'private']);
|
|
DB::table('memberships')->insert(['user_id' => $person->id, 'organization_id' => $id, 'role' => $role]);
|
|
|
|
return $id;
|
|
}
|
|
|
|
public function test_detail_exposes_a_bounded_summary_instead_of_raw_event_rows(): void
|
|
{
|
|
for ($i = 0; $i < 9; $i++) {
|
|
$this->edit(['title' => 'SYNTHETIC revision '.$i]);
|
|
}
|
|
$response = $this->getJson('/parcerias/'.$this->record->id)->assertOk()->assertJsonMissingPath('history')->assertJsonPath('historySummary.limit', 5)->assertJsonCount(5, 'historySummary.entries')->assertJsonPath('historySummary.has_more', true);
|
|
$entries = $response->json('historySummary.entries');
|
|
$current = app(PartnershipExchange::class)->find($this->record->id);
|
|
$this->assertSame([(int) $current->version, (int) $current->version - 1, (int) $current->version - 2, (int) $current->version - 3, (int) $current->version - 4], array_column($entries, 'version'));
|
|
$this->getJson($response->json('historySummary.href'))->assertOk()->assertJsonCount(10, 'entries');
|
|
}
|
|
|
|
public function test_summary_metadata_and_states_match_the_authorized_full_reader_without_snapshots(): void
|
|
{
|
|
$record = $this->edit(['title' => 'SYNTHETIC newer title']);
|
|
app(PartnershipExchange::class)->transition($this->owner, $record->id, $record->version, 'submit', 'SYNTHETIC reviewed reason');
|
|
$summary = $this->getJson('/parcerias/'.$this->record->id)->assertOk()->json('historySummary');
|
|
$full = $this->getJson($this->url())->assertOk()->json('entries');
|
|
foreach ($summary['entries'] as $index => $entry) {
|
|
foreach (['version', 'restricted', 'action', 'actor_name', 'occurred_at', 'reason'] as $field) {
|
|
$this->assertSame($full[$index][$field], $entry[$field]);
|
|
}
|
|
$this->assertSame(($full[$index]['previous']['availability'] === 'available' ? $full[$index]['previous']['status'] ?? null : null), $entry['status_before']);
|
|
$this->assertSame(($full[$index]['after']['availability'] === 'available' ? $full[$index]['after']['status'] ?? null : null), $entry['status_after']);
|
|
}
|
|
foreach (['event_type', 'actor_id', 'previous', 'after', 'payload', 'offer', 'client_key', 'operation_hash'] as $private) {
|
|
$this->assertStringNotContainsString('"'.$private.'"', json_encode($summary));
|
|
}
|
|
$this->assertSame('SYNTHETIC reviewed reason', $summary['entries'][0]['reason']);
|
|
$this->assertFalse($summary['has_more']);
|
|
}
|
|
|
|
public function test_a_new_organization_representative_does_not_receive_old_actor_or_decision_metadata(): void
|
|
{
|
|
$a = $this->organization($this->owner);
|
|
$this->edit(['organization_id' => $a]);
|
|
$oldActor = User::factory()->create(['name' => 'SYNTHETIC OLD PRIVATE ACTOR']);
|
|
DB::table('memberships')->insert(['user_id' => $oldActor->id, 'organization_id' => $a, 'role' => 'officer']);
|
|
$current = app(PartnershipExchange::class)->find($this->record->id);
|
|
app(PartnershipExchange::class)->save($oldActor, ['version' => $current->version, 'kind' => 'seeking', 'title' => 'SYNTHETIC A draft'], $current->id);
|
|
$b = $this->organization($this->owner);
|
|
$this->edit(['organization_id' => $b]);
|
|
$viewer = User::factory()->create();
|
|
DB::table('memberships')->insert(['user_id' => $viewer->id, 'organization_id' => $b, 'role' => 'viewer']);
|
|
$response = $this->actingAs($viewer)->getJson('/parcerias/'.$this->record->id)->assertOk()->assertJsonPath('canEdit', false)
|
|
->assertJsonPath('historySummary.entries.0.restricted', false)->assertJsonPath('historySummary.entries.0.status_before', null)
|
|
->assertJsonPath('historySummary.entries.1.restricted', true)->assertJsonPath('historySummary.entries.1.actor_name', null)
|
|
->assertJsonPath('historySummary.entries.1.occurred_at', null)->assertJsonPath('historySummary.entries.1.action', null)->assertJsonPath('historySummary.entries.1.reason', null);
|
|
$this->assertStringNotContainsString('OLD PRIVATE ACTOR', $response->getContent());
|
|
$this->postJson('/parcerias/'.$this->record->id.'/estado', ['version' => 99, 'action' => 'archive', 'reason' => 'SYNTHETIC viewer cannot decide'])->assertForbidden();
|
|
DB::table('memberships')->where('user_id', $viewer->id)->update(['deleted_at' => now()]);
|
|
$this->getJson('/parcerias/'.$this->record->id)->assertNotFound();
|
|
}
|
|
|
|
public function test_moderation_keeps_its_existing_historical_access(): void
|
|
{
|
|
$a = $this->organization($this->owner);
|
|
$this->edit(['organization_id' => $a]);
|
|
$b = $this->organization($this->owner);
|
|
$this->edit(['organization_id' => $b]);
|
|
$this->actingAs($this->moderator)->getJson('/parcerias/'.$this->record->id)->assertOk()->assertJsonPath('moderator', true)
|
|
->assertJsonPath('historySummary.entries.1.restricted', false)->assertJsonPath('historySummary.entries.1.actor_name', $this->owner->name);
|
|
}
|
|
|
|
public function test_public_and_candidate_reads_have_no_historical_summary_even_after_publication(): void
|
|
{
|
|
$exchange = app(PartnershipExchange::class);
|
|
$record = $exchange->transition($this->owner, $this->record->id, $this->record->version, 'submit', 'SYNTHETIC submit');
|
|
$record = $exchange->transition($this->moderator, $record->id, $record->version, 'publish', 'SYNTHETIC publish');
|
|
$candidate = User::factory()->create();
|
|
$exchange->interest($candidate, $record->id, ['proposal' => 'SYNTHETIC candidacy', 'payload' => ['consent' => true]]);
|
|
$this->actingAs($candidate)->getJson('/parcerias/'.$record->id)->assertOk()->assertJsonPath('historySummary', null)->assertJsonMissingPath('history');
|
|
$this->getJson($this->url())->assertForbidden();
|
|
auth()->forgetGuards();
|
|
$this->getJson('/parcerias/'.$record->id)->assertOk()->assertJsonPath('historySummary', null)->assertJsonMissingPath('history');
|
|
}
|
|
|
|
public function test_legacy_scope_unknown_does_not_become_current_authority_or_an_inferred_action(): void
|
|
{
|
|
$event = DB::table('partnership_events')->where('partnership_id', $this->record->id)->first();
|
|
$payload = json_decode($event->payload, true);
|
|
unset($payload['payload']['previous'],$payload['payload']['applied'],$payload['payload']['_notification_context']);
|
|
$payload['payload']['reason'] = 'SYNTHETIC RESERVED REASON';
|
|
DB::table('partnership_events')->where('id', $event->id)->update(['payload' => json_encode($payload)]);
|
|
$result = $this->getJson('/parcerias/'.$this->record->id)->assertOk()->assertJsonPath('historySummary.entries.0.restricted', true)
|
|
->assertJsonPath('historySummary.entries.0.action', null)->assertJsonPath('historySummary.entries.0.status_after', null)->assertJsonPath('historySummary.entries.0.actor_name', null);
|
|
$this->assertStringNotContainsString('RESERVED REASON', $result->getContent());
|
|
}
|
|
|
|
public function test_no_publication_events_is_an_explicit_empty_summary_with_a_valid_history_link(): void
|
|
{
|
|
DB::table('partnership_events')->where('partnership_id', $this->record->id)->update(['event_type' => 'synthetic_unrelated.v1']);
|
|
$response = $this->getJson('/parcerias/'.$this->record->id)->assertOk()->assertJsonCount(0, 'historySummary.entries')->assertJsonPath('historySummary.has_more', false)
|
|
->assertJsonPath('historySummary.href', '/parcerias/'.$this->record->id.'/historico');
|
|
$this->getJson($response->json('historySummary.href'))->assertOk()->assertJsonCount(0, 'entries');
|
|
}
|
|
|
|
public function test_native_and_json_summary_reads_do_not_change_versions_audit_or_notifications(): void
|
|
{
|
|
$before = DB::table('portal_partnerships')->where('id', $this->record->id)->first();
|
|
$events = DB::table('partnership_events')->count();
|
|
$audits = DB::table('audit_events')->count();
|
|
$notices = DB::table('partnership_notifications')->count();
|
|
$this->get('/parcerias/'.$this->record->id)->assertOk()->assertInertia(fn (Assert $p) => $p->component('partnerships/detail')->where('historySummary.limit', 5)->missing('history'));
|
|
$this->getJson('/parcerias/'.$this->record->id)->assertOk();
|
|
$this->assertEquals($before, DB::table('portal_partnerships')->where('id', $this->record->id)->first());
|
|
$this->assertSame($events, DB::table('partnership_events')->count());
|
|
$this->assertSame($audits, DB::table('audit_events')->count());
|
|
$this->assertSame($notices, DB::table('partnership_notifications')->count());
|
|
}
|
|
}
|