TenantAtlas/tests/Unit/AssignmentBackupServiceTest.php
ahmido 8ee1174c8d feat: add resolved reference presentation layer (#161)
## Summary
- add the shared resolved-reference foundation with registry, resolvers, presenters, and badge semantics
- refactor related context, assignment evidence, and policy-version assignment rendering toward label-first reference presentation
- add Spec 132 artifacts and focused Pest coverage for reference resolution, degraded states, canonical linking, and tenant-context carryover

## Verification
- `vendor/bin/sail bin pint --dirty --format agent`
- focused Pest verification was marked complete in the task artifact

## Notes
- this PR is opened from the current session branch
- `specs/132-guid-context-resolver/tasks.md` reflects in-progress completion state for the implemented tasks

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #161
2026-03-10 18:52:52 +00:00

162 lines
5.5 KiB
PHP

<?php
use App\Models\BackupItem;
use App\Models\Tenant;
use App\Services\AssignmentBackupService;
use App\Services\Graph\AssignmentFetcher;
use App\Services\Graph\AssignmentFilterResolver;
use App\Services\Graph\GroupResolver;
use App\Services\Graph\ScopeTagResolver;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;
uses(RefreshDatabase::class);
it('enriches assignment filter names when filter data is stored at root', function () {
$tenant = Tenant::factory()->create([
'tenant_id' => 'tenant-123',
'external_id' => 'tenant-123',
]);
ensureDefaultProviderConnection($tenant, 'microsoft');
$backupItem = BackupItem::factory()->create([
'tenant_id' => $tenant->id,
'metadata' => [],
'assignments' => null,
]);
$policyPayload = [
'roleScopeTagIds' => ['0'],
];
$this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
$mock->shouldReceive('supportsStandardAssignments')
->once()
->with('settingsCatalogPolicy', null)
->andReturnTrue();
$mock->shouldReceive('fetch')
->once()
->andReturn([
[
'id' => 'assignment-1',
'intent' => 'apply',
'deviceAndAppManagementAssignmentFilterId' => 'filter-123',
'deviceAndAppManagementAssignmentFilterType' => 'include',
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-123',
],
],
]);
});
$this->mock(GroupResolver::class, function (MockInterface $mock) {
$mock->shouldReceive('resolveGroupIds')
->once()
->andReturn([
'group-123' => [
'id' => 'group-123',
'displayName' => 'Test Group',
'orphaned' => false,
],
]);
});
$this->mock(AssignmentFilterResolver::class, function (MockInterface $mock) use ($tenant) {
$mock->shouldReceive('resolve')
->once()
->with(['filter-123'], $tenant)
->andReturn([
['id' => 'filter-123', 'displayName' => 'Targeted Devices'],
]);
});
$this->mock(ScopeTagResolver::class, function (MockInterface $mock) use ($tenant) {
$mock->shouldReceive('resolve')
->once()
->with(['0'], $tenant)
->andReturn([
['id' => '0', 'displayName' => 'Default'],
]);
});
$service = app(AssignmentBackupService::class);
$updated = $service->enrichWithAssignments(
backupItem: $backupItem,
tenant: $tenant,
policyType: 'settingsCatalogPolicy',
policyId: 'policy-123',
policyPayload: $policyPayload,
includeAssignments: true
);
expect($updated->assignments)->toHaveCount(1)
->and($updated->assignments[0]['target']['deviceAndAppManagementAssignmentFilterId'])->toBe('filter-123')
->and($updated->assignments[0]['target']['deviceAndAppManagementAssignmentFilterType'])->toBe('include')
->and($updated->assignments[0]['target']['assignment_filter_name'])->toBe('Targeted Devices');
});
it('marks role definitions as not using standard policy assignments', function () {
$tenant = Tenant::factory()->create([
'tenant_id' => 'tenant-123',
'external_id' => 'tenant-123',
]);
ensureDefaultProviderConnection($tenant, 'microsoft');
$backupItem = BackupItem::factory()->create([
'tenant_id' => $tenant->id,
'metadata' => [
'assignments_fetch_failed' => true,
'assignments_fetch_error' => 'old error',
],
'assignments' => null,
]);
$this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
$mock->shouldReceive('supportsStandardAssignments')
->once()
->with('intuneRoleDefinition', '#microsoft.graph.roleDefinition')
->andReturnFalse();
$mock->shouldReceive('fetch')->never();
});
$this->mock(ScopeTagResolver::class, function (MockInterface $mock) use ($tenant) {
$mock->shouldReceive('resolve')
->once()
->with(['0'], $tenant)
->andReturn([
['id' => '0', 'displayName' => 'Default'],
]);
});
$this->mock(GroupResolver::class, function (MockInterface $mock) {
$mock->shouldReceive('resolveGroupIds')->never();
});
$this->mock(AssignmentFilterResolver::class, function (MockInterface $mock) {
$mock->shouldReceive('resolve')->never();
});
$service = app(AssignmentBackupService::class);
$updated = $service->enrichWithAssignments(
backupItem: $backupItem,
tenant: $tenant,
policyType: 'intuneRoleDefinition',
policyId: 'role-def-1',
policyPayload: [
'@odata.type' => '#microsoft.graph.roleDefinition',
'roleScopeTagIds' => ['0'],
],
includeAssignments: true,
);
expect($updated->assignments)->toBe([])
->and(data_get($updated->metadata, 'assignments_not_applicable'))->toBeTrue()
->and(data_get($updated->metadata, 'assignment_capture_reason'))->toBe('separate_role_assignments')
->and(data_get($updated->metadata, 'assignments_fetch_failed'))->toBeFalse()
->and(data_get($updated->metadata, 'assignments_fetch_error'))->toBeNull();
});