TenantAtlas/tests/Unit/Tenants/TenantOperabilityServiceTest.php
2026-03-17 12:47:16 +01:00

161 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Tenant;
use App\Services\Tenants\TenantOperabilityService;
use App\Support\Tenants\TenantInteractionLane;
use App\Support\Tenants\TenantLifecycle;
use App\Support\Tenants\TenantOperabilityQuestion;
use App\Support\Tenants\TenantOperabilityReasonCode;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('returns lifecycle-aware operability decisions for every canonical tenant state', function (
\Closure $tenantFactory,
TenantLifecycle $expectedLifecycle,
bool $canSelectAsContext,
bool $canOperate,
bool $canArchive,
bool $canRestore,
bool $canResumeOnboarding,
): void {
$tenant = $tenantFactory();
$decision = app(TenantOperabilityService::class)->decisionFor($tenant);
expect($decision->lifecycle)->toBe($expectedLifecycle)
->and($decision->canViewTenantSurface)->toBeTrue()
->and($decision->canSelectAsContext)->toBe($canSelectAsContext)
->and($decision->canOperate)->toBe($canOperate)
->and($decision->canArchive)->toBe($canArchive)
->and($decision->canRestore)->toBe($canRestore)
->and($decision->canResumeOnboarding)->toBe($canResumeOnboarding)
->and($decision->canReferenceInWorkspaceMonitoring)->toBeTrue();
})->with([
'draft' => [
fn (): Tenant => Tenant::factory()->draft()->create(),
TenantLifecycle::Draft,
false,
false,
false,
false,
true,
],
'onboarding' => [
fn (): Tenant => Tenant::factory()->onboarding()->create(),
TenantLifecycle::Onboarding,
false,
false,
false,
false,
true,
],
'active' => [
fn (): Tenant => Tenant::factory()->active()->create(),
TenantLifecycle::Active,
true,
true,
true,
false,
false,
],
'archived' => [
fn (): Tenant => Tenant::factory()->archived()->create(),
TenantLifecycle::Archived,
false,
false,
false,
true,
false,
],
]);
it('returns structured selector outcomes for active and non-active tenants', function (
\Closure $tenantFactory,
bool $expectedAllowed,
?TenantOperabilityReasonCode $expectedReason,
): void {
$tenant = $tenantFactory();
$outcome = app(TenantOperabilityService::class)->outcomeFor(
tenant: $tenant,
question: TenantOperabilityQuestion::SelectorEligibility,
lane: TenantInteractionLane::StandardActiveOperating,
);
expect($outcome->allowed)->toBe($expectedAllowed)
->and($outcome->reasonCode)->toBe($expectedReason);
})->with([
'active-selector-eligible' => [fn (): Tenant => Tenant::factory()->active()->create(), true, null],
'draft-selector-ineligible' => [fn (): Tenant => Tenant::factory()->draft()->create(), false, TenantOperabilityReasonCode::SelectorIneligibleLifecycle],
'onboarding-selector-ineligible' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), false, TenantOperabilityReasonCode::SelectorIneligibleLifecycle],
'archived-selector-ineligible' => [fn (): Tenant => Tenant::factory()->archived()->create(), false, TenantOperabilityReasonCode::SelectorIneligibleLifecycle],
]);
it('keeps tenant-bound and canonical lanes viewable across all tenant lifecycles', function (
\Closure $tenantFactory,
TenantInteractionLane $lane,
): void {
$tenant = $tenantFactory();
$question = $lane === TenantInteractionLane::AdministrativeManagement
? TenantOperabilityQuestion::TenantBoundViewability
: TenantOperabilityQuestion::CanonicalLinkedRecordViewability;
$outcome = app(TenantOperabilityService::class)->outcomeFor(
tenant: $tenant,
question: $question,
lane: $lane,
);
expect($outcome->allowed)->toBeTrue()
->and($outcome->discoverable)->toBeTrue();
})->with([
'draft-admin' => [fn (): Tenant => Tenant::factory()->draft()->create(), TenantInteractionLane::AdministrativeManagement],
'onboarding-admin' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), TenantInteractionLane::AdministrativeManagement],
'archived-admin' => [fn (): Tenant => Tenant::factory()->archived()->create(), TenantInteractionLane::AdministrativeManagement],
'onboarding-canonical' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), TenantInteractionLane::CanonicalWorkspaceRecord],
'archived-canonical' => [fn (): Tenant => Tenant::factory()->archived()->create(), TenantInteractionLane::CanonicalWorkspaceRecord],
]);
it('returns wrong-lane reasons for questions asked in the wrong lane', function (): void {
$tenant = Tenant::factory()->active()->create();
$outcome = app(TenantOperabilityService::class)->outcomeFor(
tenant: $tenant,
question: TenantOperabilityQuestion::ArchiveEligibility,
lane: TenantInteractionLane::CanonicalWorkspaceRecord,
);
expect($outcome->allowed)->toBeFalse()
->and($outcome->reasonCode)->toBe(TenantOperabilityReasonCode::WrongLane);
});
it('returns lifecycle-safe primary management action keys', function (
\Closure $tenantFactory,
?string $expectedActionKey,
): void {
$tenant = $tenantFactory();
expect(app(TenantOperabilityService::class)->primaryManagementActionKey($tenant))
->toBe($expectedActionKey);
})->with([
'draft-primary' => [fn (): Tenant => Tenant::factory()->draft()->create(), null],
'onboarding-primary' => [fn (): Tenant => Tenant::factory()->onboarding()->create(), null],
'active-primary' => [fn (): Tenant => Tenant::factory()->active()->create(), 'archive'],
'archived-primary' => [fn (): Tenant => Tenant::factory()->archived()->create(), 'restore'],
]);
it('can prefer onboarding as the primary management action for draft-like tenants', function (
\Closure $tenantFactory,
): void {
$tenant = $tenantFactory();
expect(app(TenantOperabilityService::class)->primaryManagementActionKey($tenant, preferOnboarding: true))
->toBe('resume_onboarding');
})->with([
'draft-prefers-onboarding' => [fn (): Tenant => Tenant::factory()->draft()->create()],
'onboarding-prefers-onboarding' => [fn (): Tenant => Tenant::factory()->onboarding()->create()],
]);