155 lines
6.8 KiB
PHP
155 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\TenantConfigurationResource;
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Services\TenantConfiguration\CoverageResourceUpserter;
|
|
use App\Services\TenantConfiguration\CoverageSourceContractResolver;
|
|
use App\Services\TenantConfiguration\ResourceTypeRegistry;
|
|
use App\Support\TenantConfiguration\ClaimState;
|
|
use App\Support\TenantConfiguration\IdentityState;
|
|
|
|
it('Spec417 preserves resource rows across stable identity renames', function (): void {
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = spec417UpsertConnection($tenant);
|
|
$resourceType = spec417UpsertResourceType('deviceAndAppManagementAssignmentFilter');
|
|
$decision = app(CoverageSourceContractResolver::class)->resolve($resourceType);
|
|
|
|
$first = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['id' => 'assignment-filter-1', 'displayName' => 'Old name'],
|
|
sourceMetadata: $decision->sourceMetadata,
|
|
);
|
|
$second = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['id' => 'assignment-filter-1', 'displayName' => 'New name'],
|
|
sourceMetadata: $decision->sourceMetadata,
|
|
);
|
|
|
|
expect($second->getKey())->toBe($first->getKey())
|
|
->and($second->source_display_name)->toBe('New name')
|
|
->and(TenantConfigurationResource::query()->count())->toBe(1);
|
|
});
|
|
|
|
it('Spec417 keeps duplicate display names with different stable ids as separate resources', function (): void {
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = spec417UpsertConnection($tenant);
|
|
$resourceType = spec417UpsertResourceType('deviceAndAppManagementAssignmentFilter');
|
|
$decision = app(CoverageSourceContractResolver::class)->resolve($resourceType);
|
|
|
|
foreach (['assignment-filter-1', 'assignment-filter-2'] as $sourceId) {
|
|
app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['id' => $sourceId, 'displayName' => 'Same visible name'],
|
|
sourceMetadata: $decision->sourceMetadata,
|
|
);
|
|
}
|
|
|
|
expect(TenantConfigurationResource::query()->count())->toBe(2)
|
|
->and(TenantConfigurationResource::query()->pluck('source_display_name')->unique()->values()->all())->toBe(['Same visible name'])
|
|
->and(TenantConfigurationResource::query()->pluck('canonical_resource_key')->unique())->toHaveCount(2);
|
|
});
|
|
|
|
it('Spec417 never marks display-name-only resources stable', function (): void {
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = spec417UpsertConnection($tenant);
|
|
$resourceType = spec417UpsertResourceType('deviceAndAppManagementAssignmentFilter');
|
|
$decision = app(CoverageSourceContractResolver::class)->resolve($resourceType);
|
|
|
|
$resource = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['displayName' => 'Only a label'],
|
|
sourceMetadata: $decision->sourceMetadata,
|
|
);
|
|
|
|
expect($resource->latest_identity_state)->toBe(IdentityState::MissingExternalId)
|
|
->and($resource->canonical_resource_key)->not->toContain('Only a label');
|
|
});
|
|
|
|
it('Spec417 does not merge repeated display-name-only resources as identity truth', function (): void {
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = spec417UpsertConnection($tenant);
|
|
$resourceType = spec417UpsertResourceType('deviceAndAppManagementAssignmentFilter');
|
|
$decision = app(CoverageSourceContractResolver::class)->resolve($resourceType);
|
|
|
|
$first = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['displayName' => 'Only a label'],
|
|
sourceMetadata: $decision->sourceMetadata,
|
|
);
|
|
$second = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['displayName' => 'Only a label'],
|
|
sourceMetadata: $decision->sourceMetadata,
|
|
);
|
|
|
|
$first->refresh();
|
|
|
|
expect($second->getKey())->not->toBe($first->getKey())
|
|
->and(TenantConfigurationResource::query()->count())->toBe(2)
|
|
->and(TenantConfigurationResource::query()->pluck('canonical_resource_key')->unique())->toHaveCount(2)
|
|
->and(TenantConfigurationResource::query()->pluck('latest_identity_state')->all())->each->toBe(IdentityState::IdentityConflict)
|
|
->and(TenantConfigurationResource::query()->pluck('latest_claim_state')->all())->each->toBe(ClaimState::ClaimBlocked);
|
|
});
|
|
|
|
it('Spec417 does not merge unsupported identity observations by fallback candidate key', function (): void {
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = spec417UpsertConnection($tenant);
|
|
$resourceType = TenantConfigurationResourceType::factory()->create([
|
|
'canonical_type' => 'unsupportedIdentityType',
|
|
'default_claim_state' => ClaimState::ClaimAllowed->value,
|
|
]);
|
|
|
|
$first = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['id' => 'unsupported-1', 'displayName' => 'Unsupported A'],
|
|
);
|
|
$second = app(CoverageResourceUpserter::class)->upsert(
|
|
tenant: $tenant,
|
|
providerConnection: $connection,
|
|
resourceType: $resourceType,
|
|
payload: ['id' => 'unsupported-2', 'displayName' => 'Unsupported B'],
|
|
);
|
|
|
|
$first->refresh();
|
|
|
|
expect($second->getKey())->not->toBe($first->getKey())
|
|
->and(TenantConfigurationResource::query()->count())->toBe(2)
|
|
->and(TenantConfigurationResource::query()->pluck('canonical_resource_key')->unique())->toHaveCount(2)
|
|
->and(TenantConfigurationResource::query()->pluck('latest_identity_state')->all())->each->toBe(IdentityState::IdentityConflict)
|
|
->and(TenantConfigurationResource::query()->pluck('latest_claim_state')->all())->each->toBe(ClaimState::ClaimBlocked);
|
|
});
|
|
|
|
function spec417UpsertResourceType(string $canonicalType): TenantConfigurationResourceType
|
|
{
|
|
app(ResourceTypeRegistry::class)->syncDefaults();
|
|
|
|
return TenantConfigurationResourceType::query()
|
|
->where('canonical_type', $canonicalType)
|
|
->firstOrFail();
|
|
}
|
|
|
|
function spec417UpsertConnection($tenant): ProviderConnection
|
|
{
|
|
return ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
}
|