98 lines
4.4 KiB
PHP
98 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\ExistingBusiness;
|
|
use App\Models\User;
|
|
use App\Services\ExistingBusinessDrafts;
|
|
use Illuminate\Auth\Access\AuthorizationException;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
use Tests\TestCase;
|
|
|
|
class ExistingBusinessFoundationTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/** @return array<string,mixed> */
|
|
private function draft(): array
|
|
{
|
|
return ['modality' => 'company_sale', 'classification' => 'confidential', 'reason' => 'Synthetic draft created', 'payload' => ['title' => 'Private synthetic business']];
|
|
}
|
|
|
|
public function test_draft_is_encrypted_and_history_is_versioned_with_stale_writes_rejected(): void
|
|
{
|
|
$owner = User::factory()->create();
|
|
$service = app(ExistingBusinessDrafts::class);
|
|
$business = $service->save($owner, $this->draft());
|
|
$this->assertSame('draft', $business->status);
|
|
$this->assertArrayNotHasKey('payload', $business->toArray());
|
|
$raw = DB::table('existing_businesses')->where('id', $business->id)->value('payload');
|
|
$this->assertStringNotContainsString('Private synthetic', $raw);
|
|
$this->assertStringContainsString('Private synthetic', Crypt::decryptString($raw));
|
|
$service->save($owner, [...$this->draft(), 'version_no' => 1], $business->id);
|
|
$this->assertDatabaseCount('existing_business_revisions', 2);
|
|
try {
|
|
$service->save($owner, [...$this->draft(), 'version_no' => 1], $business->id);
|
|
$this->fail('Stale update accepted');
|
|
} catch (HttpException $e) {
|
|
$this->assertSame(409, $e->getStatusCode());
|
|
}
|
|
$this->assertDatabaseCount('existing_business_revisions', 2);
|
|
$service->archive($owner, $business->id, 2, 'Synthetic archive reason');
|
|
$this->assertNull(ExistingBusiness::find($business->id));
|
|
$this->assertSame(3, ExistingBusiness::withTrashed()->findOrFail($business->id)->version_no);
|
|
$this->assertDatabaseCount('existing_business_revisions', 3);
|
|
}
|
|
|
|
public function test_other_user_including_manager_cannot_edit_reserved_business(): void
|
|
{
|
|
$service = app(ExistingBusinessDrafts::class);
|
|
$business = $service->save(User::factory()->create(), $this->draft());
|
|
$other = User::factory()->create();
|
|
config(['portal.managers' => [$other->email]]);
|
|
$this->expectException(AuthorizationException::class);
|
|
$service->save($other, [...$this->draft(), 'version_no' => 1], $business->id);
|
|
}
|
|
|
|
public function test_unverified_account_cannot_create_business(): void
|
|
{
|
|
$this->expectException(AuthorizationException::class);
|
|
app(ExistingBusinessDrafts::class)->save(User::factory()->unverified()->create(), $this->draft());
|
|
}
|
|
|
|
public function test_database_prevents_publication_and_history_changes(): void
|
|
{
|
|
$business = app(ExistingBusinessDrafts::class)->save(User::factory()->create(), $this->draft());
|
|
foreach (['publish', 'history'] as $operation) {
|
|
DB::statement('SAVEPOINT business_constraint');
|
|
try {
|
|
if ($operation === 'publish') {
|
|
DB::table('existing_businesses')->where('id', $business->id)->update(['status' => 'published']);
|
|
} else {
|
|
DB::table('existing_business_revisions')->where('business_id', $business->id)->delete();
|
|
}
|
|
$this->fail('Database invariant was not enforced');
|
|
} catch (QueryException) {
|
|
DB::statement('ROLLBACK TO SAVEPOINT business_constraint');
|
|
$this->assertTrue(true);
|
|
}
|
|
}
|
|
$this->assertDatabaseCount('existing_business_revisions', 1);
|
|
}
|
|
|
|
public function test_empty_rollback_is_reversible_and_populated_rollback_is_refused(): void
|
|
{
|
|
$migration = require database_path('migrations/2026_09_28_130000_existing_business_foundation.php');
|
|
$migration->down();
|
|
$migration->up();
|
|
app(ExistingBusinessDrafts::class)->save(User::factory()->create(), $this->draft());
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('destructive rollback refused');
|
|
$migration->down();
|
|
}
|
|
}
|