Added a resolver/validation flow that fetches endpoint security template definitions and enforces them before CREATE/PATCH so we don’t call Graph with invalid settings. Hardened restore endpoint resolution (built-in fallback to deviceManagement/configurationPolicies, clearer error metadata, preview-only fallback when metadata is missing) and exposed Graph path/method in restore UI details. Stripped read-only fields when PATCHing endpointSecurityIntent so the request no longer fails with “properties not patchable”. Added regression tests covering endpoint security restore, intent sanitization, unknown type safety, Graph error metadata, and endpoint resolution behavior. Testing GraphClientEndpointResolutionTest.php ./vendor/bin/pint --dirty Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #25
103 lines
3.3 KiB
PHP
103 lines
3.3 KiB
PHP
<?php
|
|
|
|
use App\Models\BackupItem;
|
|
use App\Models\BackupSet;
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use App\Services\Graph\GraphResponse;
|
|
use App\Services\Intune\RestoreService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
class EndpointSecurityIntentRestoreGraphClient implements GraphClientInterface
|
|
{
|
|
/** @var array<int, array{policyType:string,policyId:string,payload:array,options:array<string,mixed>}> */
|
|
public array $applyPolicyCalls = [];
|
|
|
|
public function listPolicies(string $policyType, array $options = []): GraphResponse
|
|
{
|
|
return new GraphResponse(true, []);
|
|
}
|
|
|
|
public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
|
|
{
|
|
return new GraphResponse(true, ['payload' => []]);
|
|
}
|
|
|
|
public function getOrganization(array $options = []): GraphResponse
|
|
{
|
|
return new GraphResponse(true, []);
|
|
}
|
|
|
|
public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
|
|
{
|
|
$this->applyPolicyCalls[] = [
|
|
'policyType' => $policyType,
|
|
'policyId' => $policyId,
|
|
'payload' => $payload,
|
|
'options' => $options,
|
|
];
|
|
|
|
return new GraphResponse(true, []);
|
|
}
|
|
|
|
public function getServicePrincipalPermissions(array $options = []): GraphResponse
|
|
{
|
|
return new GraphResponse(true, []);
|
|
}
|
|
|
|
public function request(string $method, string $path, array $options = []): GraphResponse
|
|
{
|
|
return new GraphResponse(true, []);
|
|
}
|
|
}
|
|
|
|
test('restore strips non-patchable fields from endpoint security intent updates', function () {
|
|
$client = new EndpointSecurityIntentRestoreGraphClient;
|
|
app()->instance(GraphClientInterface::class, $client);
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
|
'status' => 'completed',
|
|
'item_count' => 1,
|
|
]);
|
|
|
|
$backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([
|
|
'policy_id' => null,
|
|
'policy_identifier' => 'intent-1',
|
|
'policy_type' => 'endpointSecurityIntent',
|
|
'platform' => 'windows',
|
|
'payload' => [
|
|
'id' => 'intent-1',
|
|
'@odata.type' => '#microsoft.graph.deviceManagementIntent',
|
|
'displayName' => 'SPO Account Protection',
|
|
'description' => 'Demo',
|
|
'isAssigned' => false,
|
|
'templateId' => '0f2b5d70-d4e9-4156-8c16-1397eb6c54a5',
|
|
'isMigratingToConfigurationPolicy' => false,
|
|
],
|
|
'assignments' => null,
|
|
]);
|
|
|
|
$service = app(RestoreService::class);
|
|
$run = $service->execute(
|
|
tenant: $tenant,
|
|
backupSet: $backupSet,
|
|
selectedItemIds: [$backupItem->id],
|
|
dryRun: false,
|
|
);
|
|
|
|
expect($run->status)->toBe('completed');
|
|
expect($client->applyPolicyCalls)->toHaveCount(1);
|
|
|
|
$payload = $client->applyPolicyCalls[0]['payload'] ?? [];
|
|
expect($payload)->toBeArray();
|
|
expect($payload)->toHaveKey('displayName');
|
|
expect($payload)->toHaveKey('description');
|
|
expect($payload)->not->toHaveKey('id');
|
|
expect($payload)->not->toHaveKey('isAssigned');
|
|
expect($payload)->not->toHaveKey('templateId');
|
|
expect($payload)->not->toHaveKey('isMigratingToConfigurationPolicy');
|
|
});
|