startCompare($tenant, $user); expect($result['ok'])->toBeFalse(); expect($result['reason_code'])->toBe(BaselineReasonCodes::COMPARE_NO_ASSIGNMENT); Queue::assertNotPushed(CompareBaselineToTenantJob::class); expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(0); }); it('rejects compare when assigned profile is in draft status', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $profile = BaselineProfile::factory()->create([ 'workspace_id' => $tenant->workspace_id, 'status' => BaselineProfile::STATUS_DRAFT, ]); BaselineTenantAssignment::create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); $service = app(BaselineCompareService::class); $result = $service->startCompare($tenant, $user); expect($result['ok'])->toBeFalse(); expect($result['reason_code'])->toBe(BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE); Queue::assertNotPushed(CompareBaselineToTenantJob::class); expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(0); }); it('rejects compare when assigned profile is archived [EC-001]', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $profile = BaselineProfile::factory()->archived()->create([ 'workspace_id' => $tenant->workspace_id, ]); BaselineTenantAssignment::create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); $service = app(BaselineCompareService::class); $result = $service->startCompare($tenant, $user); expect($result['ok'])->toBeFalse(); expect($result['reason_code'])->toBe(BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE); Queue::assertNotPushed(CompareBaselineToTenantJob::class); }); it('rejects compare when profile has no active snapshot', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $profile = BaselineProfile::factory()->active()->create([ 'workspace_id' => $tenant->workspace_id, 'active_snapshot_id' => null, ]); BaselineTenantAssignment::create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); $service = app(BaselineCompareService::class); $result = $service->startCompare($tenant, $user); expect($result['ok'])->toBeFalse(); expect($result['reason_code'])->toBe(BaselineReasonCodes::COMPARE_NO_ACTIVE_SNAPSHOT); Queue::assertNotPushed(CompareBaselineToTenantJob::class); }); it('enqueues compare successfully when all preconditions are met', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $profile = BaselineProfile::factory()->active()->create([ 'workspace_id' => $tenant->workspace_id, 'scope_jsonb' => ['policy_types' => ['deviceConfiguration']], ]); $snapshot = BaselineSnapshot::factory()->create([ 'workspace_id' => $tenant->workspace_id, 'baseline_profile_id' => $profile->getKey(), ]); $profile->update(['active_snapshot_id' => $snapshot->getKey()]); BaselineTenantAssignment::create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); $service = app(BaselineCompareService::class); $result = $service->startCompare($tenant, $user); expect($result['ok'])->toBeTrue(); expect($result)->toHaveKey('run'); /** @var OperationRun $run */ $run = $result['run']; expect($run->type)->toBe('baseline_compare'); expect($run->status)->toBe('queued'); $context = is_array($run->context) ? $run->context : []; expect($context['baseline_profile_id'])->toBe((int) $profile->getKey()); expect($context['baseline_snapshot_id'])->toBe((int) $snapshot->getKey()); Queue::assertPushed(CompareBaselineToTenantJob::class); }); // --- EC-004: Concurrent compare reuses active run --- it('reuses an existing active run for the same profile/tenant instead of creating a new one [EC-004]', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $profile = BaselineProfile::factory()->active()->create([ 'workspace_id' => $tenant->workspace_id, ]); $snapshot = BaselineSnapshot::factory()->create([ 'workspace_id' => $tenant->workspace_id, 'baseline_profile_id' => $profile->getKey(), ]); $profile->update(['active_snapshot_id' => $snapshot->getKey()]); BaselineTenantAssignment::create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); $service = app(BaselineCompareService::class); $result1 = $service->startCompare($tenant, $user); $result2 = $service->startCompare($tenant, $user); expect($result1['ok'])->toBeTrue(); expect($result2['ok'])->toBeTrue(); expect($result1['run']->getKey())->toBe($result2['run']->getKey()); expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(1); });