TenantAtlas/tests/Unit/PolicySnapshotServiceTest.php
ahmido 47db966a19 feat: add metadata-only mobile app coverage with scope tag restore (#10)
Summary
add mobileApp contract details (assignments, expanded type family, scope tag select) and spec/test coverage so App snapshots stay metadata-only yet still capture roleScopeTagIds.
guard restores so scope tags are written back whenever a snapshot carries them, even without explicit foundation mappings, and document it via a new Filament restore test.
keep existing restore/sync behaviors in place while ensuring mobileApp assignments and metadata continue to flow through the backup/restore pipeline.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #10
2025-12-29 14:01:37 +00:00

172 lines
6.2 KiB
PHP

<?php
use App\Models\Policy;
use App\Models\Tenant;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\Intune\PolicySnapshotService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class);
uses(RefreshDatabase::class);
class PolicySnapshotGraphClient implements GraphClientInterface
{
public array $requests = [];
public function listPolicies(string $policyType, array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
{
$this->requests[] = ['getPolicy', $policyType, $policyId, $options];
if ($policyType === 'mobileApp') {
return new GraphResponse(success: true, data: [
'payload' => [
'id' => $policyId,
'displayName' => 'Contoso Portal',
'publisher' => 'Contoso',
'description' => 'Company Portal',
'@odata.type' => '#microsoft.graph.win32LobApp',
'createdDateTime' => '2025-01-01T00:00:00Z',
'lastModifiedDateTime' => '2025-01-02T00:00:00Z',
'roleScopeTagIds' => ['0', 'tag-1', 'tag-2'],
'installCommandLine' => 'setup.exe /quiet',
'largeIcon' => ['type' => 'image/png', 'value' => '...'],
],
]);
}
return new GraphResponse(success: true, data: [
'payload' => [
'id' => $policyId,
'displayName' => 'Compliance Alpha',
'@odata.type' => '#microsoft.graph.windows10CompliancePolicy',
],
]);
}
public function getOrganization(array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function getServicePrincipalPermissions(array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function request(string $method, string $path, array $options = []): GraphResponse
{
$this->requests[] = [$method, $path];
if (str_contains($path, 'scheduledActionsForRule')) {
return new GraphResponse(success: true, data: [
'value' => [
[
'ruleName' => 'Default rule',
'scheduledActionConfigurations' => [
[
'actionType' => 'notification',
'notificationTemplateId' => 'template-123',
],
],
],
],
]);
}
return new GraphResponse(success: true, data: []);
}
}
it('hydrates compliance policy scheduled actions into snapshots', function () {
$client = new PolicySnapshotGraphClient;
app()->instance(GraphClientInterface::class, $client);
$tenant = Tenant::factory()->create([
'tenant_id' => 'tenant-compliance',
'app_client_id' => 'client-123',
'app_client_secret' => 'secret-123',
'is_current' => true,
]);
$tenant->makeCurrent();
$policy = Policy::factory()->create([
'tenant_id' => $tenant->id,
'external_id' => 'compliance-123',
'policy_type' => 'deviceCompliancePolicy',
'display_name' => 'Compliance Alpha',
'platform' => 'windows',
]);
$service = app(PolicySnapshotService::class);
$result = $service->fetch($tenant, $policy);
expect($result)->toHaveKey('payload');
expect($result['payload'])->toHaveKey('scheduledActionsForRule');
expect($result['payload']['scheduledActionsForRule'])->toHaveCount(1);
expect($result['payload']['scheduledActionsForRule'][0]['scheduledActionConfigurations'][0]['notificationTemplateId'])
->toBe('template-123');
expect($result['metadata']['compliance_actions_hydration'])->toBe('complete');
expect($client->requests[0][0])->toBe('getPolicy');
expect($client->requests[0][1])->toBe('deviceCompliancePolicy');
expect($client->requests[0][2])->toBe('compliance-123');
expect($client->requests[0][3]['expand'] ?? null)
->toBe('scheduledActionsForRule($expand=scheduledActionConfigurations)');
});
it('filters mobile app snapshots to metadata-only keys', function () {
$client = new PolicySnapshotGraphClient;
app()->instance(GraphClientInterface::class, $client);
$tenant = Tenant::factory()->create([
'tenant_id' => 'tenant-apps',
'app_client_id' => 'client-123',
'app_client_secret' => 'secret-123',
'is_current' => true,
]);
$tenant->makeCurrent();
$policy = Policy::factory()->create([
'tenant_id' => $tenant->id,
'external_id' => 'app-123',
'policy_type' => 'mobileApp',
'display_name' => 'Contoso Portal',
'platform' => 'all',
]);
$service = app(PolicySnapshotService::class);
$result = $service->fetch($tenant, $policy);
expect($result['payload'])->toHaveKeys([
'id',
'displayName',
'publisher',
'description',
'@odata.type',
'createdDateTime',
'lastModifiedDateTime',
'roleScopeTagIds',
]);
expect($result['payload']['roleScopeTagIds'])->toBe(['0', 'tag-1', 'tag-2']);
expect($result['payload'])->not->toHaveKey('installCommandLine');
expect($result['payload'])->not->toHaveKey('largeIcon');
expect($client->requests[0][0])->toBe('getPolicy');
expect($client->requests[0][1])->toBe('mobileApp');
expect($client->requests[0][2])->toBe('app-123');
expect($client->requests[0][3]['select'] ?? null)->toBeArray();
expect($client->requests[0][3]['select'])->toContain('displayName');
expect($client->requests[0][3]['select'])->toContain('roleScopeTagIds');
expect($client->requests[0][3]['select'])->not->toContain('@odata.type');
});