create(); ensureDefaultProviderConnection($tenant, 'microsoft'); $policy = Policy::factory()->for($tenant)->create([ 'policy_type' => 'mamAppConfiguration', 'platform' => 'mobile', 'external_id' => 'A_meta_only', 'display_name' => 'MAM Config Meta', ]); $this->mock(PolicySnapshotService::class, function ($mock) { $mock->shouldReceive('fetch') ->once() ->andReturn([ 'payload' => [ 'id' => 'A_meta_only', 'displayName' => 'MAM Config Meta', '@odata.type' => '#microsoft.graph.targetedManagedAppConfiguration', ], 'metadata' => [ 'source' => 'metadata_only', 'original_status' => 500, 'original_failure' => 'InternalServerError: upstream', ], 'warnings' => [ 'Snapshot captured from local metadata only (Graph API returned 500).', ], ]); }); $this->mock(AssignmentFetcher::class, function ($mock) { $mock->shouldReceive('fetch')->never(); }); $this->mock(ScopeTagResolver::class, function ($mock) { $mock->shouldReceive('resolve')->never(); }); $service = app(VersionService::class); $version = $service->captureFromGraph( tenant: $tenant, policy: $policy, createdBy: 'tester@example.test', includeAssignments: false, includeScopeTags: false, ); expect($version->metadata['source'])->toBe('metadata_only'); expect($version->metadata['original_status'])->toBe(500); expect($version->metadata['original_failure'])->toContain('InternalServerError'); expect($version->metadata['capture_source'])->toBe('version_capture'); expect($version->metadata['warnings'])->toBeArray(); expect($version->metadata['warnings'][0])->toContain('metadata only'); }); it('captures and reuses immutable RBAC foundation versions from payload snapshots', function () { $tenant = Tenant::factory()->create(); $policy = Policy::factory()->for($tenant)->create([ 'policy_type' => 'intuneRoleDefinition', 'platform' => 'all', 'external_id' => 'role-def-1', 'display_name' => 'Policy and Profile Manager', 'last_synced_at' => null, ]); $service = app(VersionService::class); $basePayload = [ 'id' => 'role-def-1', 'displayName' => 'Policy and Profile Manager', 'isBuiltIn' => true, 'rolePermissions' => [ [ 'resourceActions' => [ [ 'allowedResourceActions' => [ 'Microsoft.Intune/deviceConfigurations/read', ], ], ], ], ], ]; $firstVersion = $service->captureFoundationVersion( policy: $policy, payload: $basePayload, createdBy: 'tester@example.test', metadata: [ 'kind' => 'intuneRoleDefinition', 'graph' => [ 'resource' => 'deviceManagement/roleDefinitions', 'apiVersion' => 'beta', ], ], ); $reusedVersion = $service->captureFoundationVersion( policy: $policy, payload: $basePayload, createdBy: 'tester@example.test', metadata: [ 'kind' => 'intuneRoleDefinition', ], ); $changedVersion = $service->captureFoundationVersion( policy: $policy, payload: array_merge($basePayload, [ 'description' => 'Updated role description', ]), createdBy: 'tester@example.test', metadata: [ 'kind' => 'intuneRoleDefinition', ], ); expect($firstVersion->id)->toBe($reusedVersion->id); expect($changedVersion->id)->not->toBe($firstVersion->id); expect($changedVersion->version_number)->toBe(2); expect($firstVersion->metadata['capture_source'] ?? null)->toBe('foundation_capture'); expect($firstVersion->metadata['kind'] ?? null)->toBe('intuneRoleDefinition'); expect(PolicyVersion::query()->where('policy_id', $policy->id)->count())->toBe(2); });