TenantAtlas/tests/Unit/AssignmentRestoreServiceTest.php

256 lines
8.0 KiB
PHP

<?php
use App\Models\AuditLog;
use App\Models\Tenant;
use App\Services\AssignmentRestoreService;
use App\Services\Graph\AssignmentFilterResolver;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use App\Services\Graph\GraphLogger;
use App\Services\Graph\GraphResponse;
use App\Services\Intune\AuditLogger;
use Tests\TestCase;
uses(TestCase::class);
beforeEach(function () {
config()->set('graph_contracts.types.deviceManagementScript', [
'assignments_create_path' => '/deviceManagement/deviceManagementScripts/{id}/assign',
'assignments_create_method' => 'POST',
'assignments_payload_key' => 'deviceManagementScriptAssignments',
]);
config()->set('graph_contracts.types.settingsCatalogPolicy', [
'assignments_create_path' => '/deviceManagement/configurationPolicies/{id}/assign',
'assignments_create_method' => 'POST',
]);
config()->set('graph_contracts.types.appProtectionPolicy', [
'assignments_create_path' => '/deviceAppManagement/managedAppPolicies/{id}/assign',
'assignments_create_method' => 'POST',
'assignments_payload_key' => 'assignments',
]);
$this->graphClient = Mockery::mock(GraphClientInterface::class);
$this->auditLogger = Mockery::mock(AuditLogger::class);
$this->filterResolver = Mockery::mock(AssignmentFilterResolver::class);
$this->filterResolver->shouldReceive('resolve')->andReturn([])->byDefault();
$this->service = new AssignmentRestoreService(
$this->graphClient,
app(GraphContractRegistry::class),
app(GraphLogger::class),
$this->auditLogger,
$this->filterResolver,
);
});
it('uses the contract assignment payload key for assign actions', function () {
$tenant = Tenant::factory()->make([
'tenant_id' => 'tenant-123',
'app_client_id' => null,
'app_client_secret' => null,
]);
$policyId = 'policy-123';
$assignments = [
[
'id' => 'assignment-1',
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-1',
],
],
];
$expectedAssignments = [
[
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-1',
],
],
];
$this->graphClient
->shouldReceive('request')
->once()
->with('POST', "/deviceManagement/deviceManagementScripts/{$policyId}/assign", Mockery::on(
fn (array $options) => ($options['json']['deviceManagementScriptAssignments'] ?? null) === $expectedAssignments
))
->andReturn(new GraphResponse(success: true, data: []));
$this->auditLogger
->shouldReceive('log')
->once()
->andReturn(new AuditLog);
$result = $this->service->restore(
$tenant,
'deviceManagementScript',
$policyId,
$assignments,
[]
);
expect($result['summary']['success'])->toBe(1);
expect($result['summary']['failed'])->toBe(0);
expect($result['summary']['skipped'])->toBe(0);
});
it('uses derived assign endpoints for app protection policies', function () {
$tenant = Tenant::factory()->make([
'tenant_id' => 'tenant-123',
'app_client_id' => null,
'app_client_secret' => null,
]);
$policyId = 'policy-123';
$assignments = [
[
'id' => 'assignment-1',
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-1',
],
],
];
$this->graphClient
->shouldReceive('request')
->once()
->with('POST', "/deviceAppManagement/androidManagedAppProtections/{$policyId}/assign", Mockery::on(
fn (array $options) => isset($options['json']['assignments'])
))
->andReturn(new GraphResponse(success: true, data: []));
$this->auditLogger
->shouldReceive('log')
->once()
->andReturn(new AuditLog);
$result = $this->service->restore(
$tenant,
'appProtectionPolicy',
$policyId,
$assignments,
[],
[],
null,
null,
null,
'#microsoft.graph.androidManagedAppProtection',
);
expect($result['summary']['success'])->toBe(1);
expect($result['summary']['failed'])->toBe(0);
expect($result['summary']['skipped'])->toBe(0);
});
it('maps assignment filter ids stored at the root of assignments', function () {
$tenant = Tenant::factory()->make([
'tenant_id' => 'tenant-123',
'app_client_id' => null,
'app_client_secret' => null,
]);
$policyId = 'policy-789';
$assignments = [
[
'id' => 'assignment-1',
'deviceAndAppManagementAssignmentFilterId' => 'filter-source',
'deviceAndAppManagementAssignmentFilterType' => 'include',
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-1',
],
],
];
$expectedAssignments = [
[
'deviceAndAppManagementAssignmentFilterId' => 'filter-target',
'deviceAndAppManagementAssignmentFilterType' => 'include',
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-1',
],
],
];
$this->graphClient
->shouldReceive('request')
->once()
->with('POST', "/deviceManagement/configurationPolicies/{$policyId}/assign", Mockery::on(
fn (array $options) => ($options['json']['assignments'] ?? null) === $expectedAssignments
))
->andReturn(new GraphResponse(success: true, data: []));
$this->auditLogger
->shouldReceive('log')
->once()
->andReturn(new AuditLog);
$result = $this->service->restore(
$tenant,
'settingsCatalogPolicy',
$policyId,
$assignments,
[],
[
'assignmentFilter' => [
'filter-source' => 'filter-target',
],
]
);
expect($result['summary']['success'])->toBe(1);
expect($result['summary']['failed'])->toBe(0);
expect($result['summary']['skipped'])->toBe(0);
});
it('keeps assignment filters when mapping is missing but filter exists in target', function () {
$tenant = Tenant::factory()->make([
'tenant_id' => 'tenant-123',
'app_client_id' => null,
'app_client_secret' => null,
]);
$policyId = 'policy-999';
$assignments = [
[
'id' => 'assignment-1',
'deviceAndAppManagementAssignmentFilterId' => 'filter-1',
'deviceAndAppManagementAssignmentFilterType' => 'include',
'target' => [
'@odata.type' => '#microsoft.graph.groupAssignmentTarget',
'groupId' => 'group-1',
],
],
];
$this->filterResolver
->shouldReceive('resolve')
->once()
->with(['filter-1'], $tenant)
->andReturn([['id' => 'filter-1', 'displayName' => 'Test']]);
$this->graphClient
->shouldReceive('request')
->once()
->with('POST', "/deviceManagement/configurationPolicies/{$policyId}/assign", Mockery::on(
fn (array $options) => ($options['json']['assignments'][0]['deviceAndAppManagementAssignmentFilterId'] ?? null) === 'filter-1'
))
->andReturn(new GraphResponse(success: true, data: []));
$this->auditLogger
->shouldReceive('log')
->once()
->andReturn(new AuditLog);
$result = $this->service->restore(
$tenant,
'settingsCatalogPolicy',
$policyId,
$assignments,
[],
[]
);
expect($result['summary']['success'])->toBe(1);
expect($result['summary']['failed'])->toBe(0);
expect($result['summary']['skipped'])->toBe(0);
});