TenantAtlas/apps/platform/tests/Feature/TenantConfiguration/Spec435ExchangeNoEvidencePromotionTest.php
Ahmed Darrazi 73d84c2706
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m14s
feat: add Exchange structured output readiness
2026-07-08 16:24:20 +02:00

132 lines
6.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\OperationRun;
use App\Models\TenantConfigurationResource;
use App\Models\TenantConfigurationResourceEvidence;
use App\Services\TenantConfiguration\ExchangePowerShellCommandContract;
use App\Services\TenantConfiguration\ExchangePowerShellCommandContracts;
use App\Services\TenantConfiguration\ExchangePowerShellEvidenceNormalizer;
use App\Services\TenantConfiguration\ExchangePowerShellInvocationResult;
use App\Services\TenantConfiguration\ExchangePowerShellStructuredOutputEnvelope;
use App\Services\TenantConfiguration\ResourceTypeRegistry;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
beforeEach(function (): void {
app(ResourceTypeRegistry::class)->syncDefaults();
});
it('Spec435 readiness evaluation remains in-memory and creates no resources evidence or operation runs', function (): void {
$beforeRuns = OperationRun::query()->count();
$readiness = app(ExchangePowerShellEvidenceNormalizer::class)->evaluate(
spec435FeatureEnvelope('transportRule', [[
'Guid' => 'rule-guid-1',
'DisplayName' => 'Rule 1',
'Enabled' => true,
]]),
);
expect($readiness->ready)->toBeTrue()
->and(OperationRun::query()->count())->toBe($beforeRuns)
->and(TenantConfigurationResource::query()->count())->toBe(0)
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0);
});
it('Spec435 empty collections create no durable collection proof resource or evidence row', function (): void {
$readiness = app(ExchangePowerShellEvidenceNormalizer::class)->evaluate(
spec435FeatureEnvelope('transportRule', [], 'empty_collection'),
);
expect($readiness->ready)->toBeTrue()
->and($readiness->emptyCollection)->toBeTrue()
->and($readiness->normalizedPreviews)->toBe([])
->and($readiness->hashPreviews)->toBe([])
->and(TenantConfigurationResource::query()->count())->toBe(0)
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0);
});
it('Spec435 readiness code stays separated from capture upsert and evidence append paths', function (): void {
$runtimeSource = collect(spec435RuntimeFiles())
->map(fn (string $file): string => file_get_contents($file) ?: '')
->implode("\n");
expect($runtimeSource)
->not->toContain('ExchangePowerShellEvidenceCaptureAdapter::capture')
->not->toContain('CoverageResourceUpserter')
->not->toContain('CoverageEvidenceWriter')
->not->toContain('TenantConfigurationResourceEvidence::')
->not->toContain('content_backed')
->not->toContain('comparable')
->not->toContain('renderable')
->not->toContain('certified')
->not->toContain('restore_ready')
->not->toContain('customer_ready');
});
it('Spec435 introduces no product surface triggers migrations tenant-id ownership or mini-platform', function (): void {
$runtimeSource = collect(spec435RuntimeFiles())
->map(fn (string $file): string => file_get_contents($file) ?: '')
->implode("\n");
expect(preg_match('/\btenant_id\b/', $runtimeSource))->toBe(0)
->and(Schema::hasColumn('tenant_configuration_resources', 'tenant_id'))->toBeFalse()
->and(Schema::hasColumn('tenant_configuration_resource_evidence', 'tenant_id'))->toBeFalse()
->and(glob(app_path('Filament/**/*ExchangePowerShell*')) ?: [])->toBe([])
->and(glob(app_path('Livewire/**/*ExchangePowerShell*')) ?: [])->toBe([])
->and(glob(base_path('routes/*exchange*')) ?: [])->toBe([])
->and(glob(resource_path('views/**/*ExchangePowerShell*')) ?: [])->toBe([])
->and(glob(database_path('migrations/*exchange*evidence*')) ?: [])->toBe([])
->and(glob(database_path('migrations/*tenant*configuration*exchange*')) ?: [])->toBe([])
->and(File::exists(app_path('Jobs/TenantConfiguration/CaptureExchangePowerShellEvidenceJob.php')))->toBeFalse()
->and(File::exists(app_path('Jobs/TenantConfiguration/NormalizeExchangePowerShellEvidenceJob.php')))->toBeFalse()
->and(File::exists(app_path('Console/Commands/CaptureExchangePowerShellEvidence.php')))->toBeFalse();
});
function spec435FeatureEnvelope(
string $canonicalType,
array $items,
string $outputState = 'structured_collection',
): ExchangePowerShellStructuredOutputEnvelope {
return ExchangePowerShellStructuredOutputEnvelope::fromInvocationResult(
resourceType: $canonicalType,
contract: spec435FeatureVerifiedContract($canonicalType),
runnerMode: 'fake',
result: ExchangePowerShellInvocationResult::succeeded($items, context: [
'output_state' => $outputState,
'item_count' => count($items),
]),
);
}
function spec435FeatureVerifiedContract(string $canonicalType): ExchangePowerShellCommandContract
{
$contracts = new ExchangePowerShellCommandContracts;
$contract = $contracts->contractForCanonicalType($canonicalType);
if (! is_array($contract)) {
throw new RuntimeException('Missing Spec435 feature command contract.');
}
return ExchangePowerShellCommandContract::fromVerifiedArray($contract, $contracts, []);
}
/**
* @return list<string>
*/
function spec435RuntimeFiles(): array
{
return [
app_path('Services/TenantConfiguration/ExchangePowerShellStructuredOutputEnvelope.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellEvidenceNormalizer.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellTargetEvidenceNormalizer.php'),
app_path('Services/TenantConfiguration/ExchangeTransportRuleEvidenceNormalizer.php'),
app_path('Services/TenantConfiguration/ExchangeRemoteDomainEvidenceNormalizer.php'),
app_path('Services/TenantConfiguration/ExchangeInboundConnectorEvidenceNormalizer.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellNormalizerReadiness.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellHashInputBuilder.php'),
];
}