set('tenantpilot.hardening.intune_write_gate.enabled', true); config()->set('tenantpilot.hardening.intune_write_gate.freshness_threshold_hours', 24); }); test('gate blocks when rbac_status is ok but rbac_last_checked_at is stale', function () { $tenant = Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now()->subHours(25), ]); $gate = app(WriteGateInterface::class); try { $gate->evaluate($tenant, 'restore.execute'); $this->fail('Expected ProviderAccessHardeningRequired to be thrown'); } catch (ProviderAccessHardeningRequired $e) { expect($e->reasonCode)->toBe('intune_rbac.stale') ->and($e->tenantId)->toBe((int) $tenant->getKey()); } }); test('gate blocks when rbac_status is ok but rbac_last_checked_at is null', function () { $tenant = Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => null, ]); $gate = app(WriteGateInterface::class); try { $gate->evaluate($tenant, 'restore.execute'); $this->fail('Expected ProviderAccessHardeningRequired to be thrown'); } catch (ProviderAccessHardeningRequired $e) { expect($e->reasonCode)->toBe('intune_rbac.stale'); } }); test('gate blocks at exact threshold boundary', function () { $tenant = Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now()->subHours(24), ]); $gate = app(WriteGateInterface::class); try { $gate->evaluate($tenant, 'restore.execute'); $this->fail('Expected ProviderAccessHardeningRequired to be thrown'); } catch (ProviderAccessHardeningRequired $e) { expect($e->reasonCode)->toBe('intune_rbac.stale'); } }); test('gate respects custom freshness threshold', function () { config()->set('tenantpilot.hardening.intune_write_gate.freshness_threshold_hours', 1); $tenant = Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now()->subHours(2), ]); $gate = app(WriteGateInterface::class); try { $gate->evaluate($tenant, 'restore.execute'); $this->fail('Expected ProviderAccessHardeningRequired to be thrown'); } catch (ProviderAccessHardeningRequired $e) { expect($e->reasonCode)->toBe('intune_rbac.stale'); } });