Automated PR provided by Codex via Gitea API. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #486
259 lines
11 KiB
PHP
259 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\TenantConfigurationResource;
|
|
use App\Models\TenantConfigurationResourceEvidence;
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Models\TenantConfigurationSupportedScope;
|
|
use App\Services\TenantConfiguration\ResourceTypeRegistry;
|
|
use App\Services\TenantConfiguration\SupportedScopeResolver;
|
|
use App\Support\TenantConfiguration\ClaimState;
|
|
use App\Support\TenantConfiguration\CoverageLevel;
|
|
use App\Support\TenantConfiguration\EvidenceState;
|
|
use App\Support\TenantConfiguration\IdentityState;
|
|
use App\Support\TenantConfiguration\RestoreTier;
|
|
use App\Support\TenantConfiguration\SupportState;
|
|
use App\Support\TenantConfiguration\Workload;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
it('Spec419 persists representative M365 registry rows without runtime tenant data', function (): void {
|
|
$expectedByWorkload = [
|
|
Workload::Intune->value => 8,
|
|
Workload::Entra->value => 6,
|
|
Workload::Exchange->value => 6,
|
|
Workload::Teams->value => 6,
|
|
Workload::SecurityCompliance->value => 6,
|
|
];
|
|
$counts = TenantConfigurationResourceType::query()
|
|
->selectRaw('workload, count(*) as aggregate')
|
|
->groupBy('workload')
|
|
->pluck('aggregate', 'workload')
|
|
->map(fn (int|string $count): int => (int) $count);
|
|
|
|
foreach ($expectedByWorkload as $workload => $expectedCount) {
|
|
expect($counts[$workload] ?? null)->toBe($expectedCount);
|
|
}
|
|
|
|
expect(TenantConfigurationResourceType::query()->count())->toBe(32)
|
|
->and(TenantConfigurationResourceType::query()->whereIn('workload', [
|
|
Workload::Defender->value,
|
|
Workload::Purview->value,
|
|
Workload::Tenantpilot->value,
|
|
Workload::Unknown->value,
|
|
])->exists())->toBeFalse()
|
|
->and(TenantConfigurationResource::query()->count())->toBe(0)
|
|
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0);
|
|
|
|
$m365Rows = TenantConfigurationResourceType::query()
|
|
->where('workload', '!=', Workload::Intune->value)
|
|
->orderBy('canonical_type')
|
|
->get();
|
|
|
|
foreach ($m365Rows as $row) {
|
|
expect($row->support_state)->toBe(SupportState::OutOfScope)
|
|
->and($row->default_coverage_level)->toBe(CoverageLevel::Detected)
|
|
->and($row->default_evidence_state)->toBe(EvidenceState::NotCaptured)
|
|
->and($row->default_identity_state)->toBe(IdentityState::Derived)
|
|
->and($row->default_claim_state)->toBe(ClaimState::InternalOnly)
|
|
->and($row->allows_beta_claims)->toBeFalse()
|
|
->and($row->allows_graph_fallback_claims)->toBeFalse()
|
|
->and($row->allows_certified_claims)->toBeFalse()
|
|
->and($row->metadata['registry_only'])->toBeTrue()
|
|
->and($row->metadata['is_full_catalog'])->toBeFalse()
|
|
->and($row->metadata['customer_claims_allowed'])->toBeFalse();
|
|
|
|
expect([
|
|
RestoreTier::NotRestorable,
|
|
RestoreTier::PreviewOnly,
|
|
])->toContain($row->restore_tier);
|
|
}
|
|
});
|
|
|
|
it('Spec419 persists inactive M365 planning scopes and leaves active Intune scopes unchanged', function (): void {
|
|
$requiredScopeKeys = [
|
|
'm365_tcm_registry_detected',
|
|
'entra_tcm_registry_detected',
|
|
'exchange_tcm_registry_detected',
|
|
'teams_tcm_registry_detected',
|
|
'security_compliance_tcm_registry_detected',
|
|
'm365_tcm_generic_future',
|
|
'm365_tcm_certified_none',
|
|
];
|
|
|
|
$scopes = TenantConfigurationSupportedScope::query()
|
|
->whereIn('scope_key', $requiredScopeKeys)
|
|
->get()
|
|
->keyBy('scope_key');
|
|
|
|
expect($scopes->keys()->sort()->values()->all())->toBe(collect($requiredScopeKeys)->sort()->values()->all());
|
|
|
|
foreach ($scopes as $scope) {
|
|
expect($scope->minimum_coverage_level)->toBe(CoverageLevel::Detected)
|
|
->and($scope->customer_claims_allowed)->toBeFalse()
|
|
->and($scope->is_active)->toBeFalse()
|
|
->and($scope->metadata['registry_only'])->toBeTrue()
|
|
->and($scope->metadata['is_full_catalog'])->toBeFalse();
|
|
}
|
|
|
|
expect(TenantConfigurationSupportedScope::query()->active()->orderBy('scope_key')->pluck('scope_key')->all())
|
|
->toBe([
|
|
'intune_tcm_core',
|
|
'intune_tcm_core_with_graph_fallback',
|
|
])
|
|
->and(TenantConfigurationSupportedScope::query()
|
|
->whereIn('scope_key', [
|
|
'm365_full_coverage',
|
|
'm365_certified',
|
|
'all_microsoft_365_supported',
|
|
'full_tenant_coverage',
|
|
'full_m365_restore_ready',
|
|
])
|
|
->exists())->toBeFalse();
|
|
|
|
$aggregate = $scopes['m365_tcm_registry_detected'];
|
|
|
|
expect($aggregate->included_resource_types)->toHaveCount(24)
|
|
->and($aggregate->included_resource_types)->toContain('conditionalAccessPolicy')
|
|
->toContain('transportRule')
|
|
->toContain('meetingPolicy')
|
|
->toContain('dlpCompliancePolicy');
|
|
});
|
|
|
|
it('Spec419 keeps registry sync idempotent and non-capturing', function (): void {
|
|
(new ResourceTypeRegistry)->syncDefaults();
|
|
(new ResourceTypeRegistry)->syncDefaults();
|
|
(new SupportedScopeResolver)->syncDefaults();
|
|
(new SupportedScopeResolver)->syncDefaults();
|
|
|
|
expect(TenantConfigurationResourceType::query()->count())->toBe(32)
|
|
->and(TenantConfigurationSupportedScope::query()->count())->toBe(9)
|
|
->and(TenantConfigurationResource::query()->count())->toBe(0)
|
|
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0);
|
|
});
|
|
|
|
it('Spec419 rollback deletes only exact Spec419 rows and preserves later non-Intune promotions', function (): void {
|
|
$futureResourceType = TenantConfigurationResourceType::query()
|
|
->where('canonical_type', 'conditionalAccessPolicy')
|
|
->where('source_class', 'tcm')
|
|
->firstOrFail();
|
|
$futureResourceType->update([
|
|
'metadata' => [
|
|
...$futureResourceType->metadata,
|
|
'catalog_import_batch' => 'future_entra_promotion',
|
|
'registry_only' => false,
|
|
'future_spec' => 'preserve_non_intune_after_spec_419',
|
|
],
|
|
]);
|
|
|
|
$futureScope = TenantConfigurationSupportedScope::query()
|
|
->where('scope_key', 'm365_tcm_registry_detected')
|
|
->firstOrFail();
|
|
$futureScope->update([
|
|
'metadata' => [
|
|
...$futureScope->metadata,
|
|
'catalog_import_batch' => 'future_m365_scope_promotion',
|
|
'future_spec' => 'preserve_non_intune_after_spec_419',
|
|
],
|
|
]);
|
|
|
|
$migration = require database_path('migrations/2026_06_26_000419_expand_tenant_configuration_workloads.php');
|
|
$migration->down();
|
|
|
|
expect(TenantConfigurationResourceType::query()
|
|
->where('canonical_type', 'conditionalAccessPolicy')
|
|
->where('source_class', 'tcm')
|
|
->exists())->toBeTrue()
|
|
->and(TenantConfigurationResourceType::query()
|
|
->where('canonical_type', 'securityDefaults')
|
|
->where('source_class', 'tcm')
|
|
->exists())->toBeFalse()
|
|
->and(TenantConfigurationSupportedScope::query()
|
|
->where('scope_key', 'm365_tcm_registry_detected')
|
|
->exists())->toBeTrue()
|
|
->and(TenantConfigurationSupportedScope::query()
|
|
->where('scope_key', 'entra_tcm_registry_detected')
|
|
->exists())->toBeFalse();
|
|
|
|
if (DB::getDriverName() === 'pgsql') {
|
|
$definition = DB::scalar(<<<'SQL'
|
|
SELECT pg_get_constraintdef(oid)
|
|
FROM pg_constraint
|
|
WHERE conrelid = 'tenant_configuration_resource_types'::regclass
|
|
AND conname = 'tenant_config_resource_types_workload_check'
|
|
SQL);
|
|
|
|
expect((string) $definition)->toContain('intune')
|
|
->toContain('entra');
|
|
}
|
|
});
|
|
|
|
it('Spec419 does not add tenant ownership columns to registry definition tables', function (): void {
|
|
foreach (['tenant_configuration_resource_types', 'tenant_configuration_supported_scopes'] as $table) {
|
|
expect(Schema::getColumnListing($table))
|
|
->not->toContain('tenant_id')
|
|
->not->toContain('provider_tenant_id')
|
|
->not->toContain('entra_tenant_id')
|
|
->not->toContain('workspace_id')
|
|
->not->toContain('managed_environment_id')
|
|
->not->toContain('provider_connection_id');
|
|
}
|
|
});
|
|
|
|
it('Spec419 stays out of capture clients, v1 adapters, and workload mini-platforms', function (): void {
|
|
$files = [
|
|
app_path('Services/TenantConfiguration/ResourceTypeRegistry.php'),
|
|
app_path('Services/TenantConfiguration/SupportedScopeResolver.php'),
|
|
app_path('Services/TenantConfiguration/ClaimGuard.php'),
|
|
app_path('Support/TenantConfiguration/Workload.php'),
|
|
database_path('migrations/2026_06_26_000419_expand_tenant_configuration_workloads.php'),
|
|
];
|
|
|
|
$content = collect($files)
|
|
->map(fn (string $file): string => file_get_contents($file) ?: '')
|
|
->implode("\n");
|
|
|
|
expect($content)
|
|
->not->toContain('GraphClientInterface')
|
|
->not->toContain('ProviderGateway')
|
|
->not->toContain('StartTenantConfigurationCapture')
|
|
->not->toContain('CaptureTenantConfigurationEvidenceJob')
|
|
->not->toContain('TenantConfigurationResource::query()->create')
|
|
->not->toContain('TenantConfigurationResourceEvidence::query()->create')
|
|
->not->toContain('Http::')
|
|
->not->toContain('tenant_id')
|
|
->not->toContain('ProviderV1')
|
|
->not->toContain('LegacyAdapter')
|
|
->not->toContain('dual_write')
|
|
->not->toContain('namespace App\\Services\\TenantConfiguration\\Entra')
|
|
->not->toContain('namespace App\\Services\\TenantConfiguration\\Exchange')
|
|
->not->toContain('namespace App\\Services\\TenantConfiguration\\Teams')
|
|
->not->toContain('namespace App\\Services\\TenantConfiguration\\Defender')
|
|
->not->toContain('namespace App\\Services\\TenantConfiguration\\Purview')
|
|
->not->toContain('namespace App\\Services\\TenantConfiguration\\SecurityCompliance')
|
|
->not->toContain('create_entra_')
|
|
->not->toContain('create_exchange_')
|
|
->not->toContain('create_teams_')
|
|
->not->toContain('create_defender_')
|
|
->not->toContain('create_purview_')
|
|
->not->toContain('create_security_compliance_');
|
|
});
|
|
|
|
it('Spec419 updates the PostgreSQL workload check constraint', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
$this->markTestSkipped('PostgreSQL-specific workload check proof runs in the pgsql lane.');
|
|
}
|
|
|
|
$definition = DB::scalar(<<<'SQL'
|
|
SELECT pg_get_constraintdef(oid)
|
|
FROM pg_constraint
|
|
WHERE conrelid = 'tenant_configuration_resource_types'::regclass
|
|
AND conname = 'tenant_config_resource_types_workload_check'
|
|
SQL);
|
|
|
|
foreach (Workload::values() as $workload) {
|
|
expect((string) $definition)->toContain($workload);
|
|
}
|
|
});
|