feat/018-driver-updates-wufb #27

Merged
ahmido merged 6 commits from feat/018-driver-updates-wufb into dev 2026-01-04 00:38:54 +00:00
4 changed files with 132 additions and 5 deletions
Showing only changes of commit b94377db8e - Show all commits

View File

@ -6,9 +6,9 @@ # Requirements Checklist (018)
- [x] `windowsDriverUpdateProfile` is added to `config/tenantpilot.php` (metadata, endpoint, backup/restore mode, risk).
- [x] Graph contract exists in `config/graph_contracts.php` (resource, type family, create/update methods, assignments paths).
- [x] Sync lists and stores driver update profiles in the Policies inventory.
- [ ] Snapshot capture stores a complete payload for backups and versions.
- [ ] Restore preview is available and respects the configured restore mode.
- [x] Snapshot capture stores a complete payload for backups and versions.
- [x] Restore preview is available and respects the configured restore mode.
- [x] Restore execution applies only patchable properties and records audit logs.
- [x] Normalized settings view is readable for admins (no raw-only UX).
- [ ] Pest tests cover sync + snapshot + restore + normalized display.
- [x] Pest tests cover sync + snapshot + restore + normalized display.
- [x] Pint run (`./vendor/bin/pint --dirty`) on touched files.

View File

@ -15,8 +15,8 @@ ## Phase 2: Research & Design
## Phase 3: Tests (TDD)
- [x] T006 Add sync test ensuring `windowsDriverUpdateProfile` policies are imported and typed correctly.
- [ ] T007 Add snapshot/version capture test asserting full payload is stored.
- [ ] T008 Add restore preview test for this type (entries + restore_mode shown).
- [x] T007 Add snapshot/version capture test asserting full payload is stored.
- [x] T008 Add restore preview test for this type (entries + restore_mode shown).
- [x] T009 Add restore execution test asserting only patchable properties are sent and failures are reported with Graph metadata.
- [x] T010 Add normalized display test for key fields.

View File

@ -104,6 +104,77 @@ public function request(string $method, string $path, array $options = []): Grap
expect($policyPreview['action'])->toBe('update');
});
test('restore preview shows enabled restore mode for windows driver update profiles', function () {
app()->bind(GraphClientInterface::class, fn () => new class implements GraphClientInterface
{
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
{
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, []);
}
});
$tenant = Tenant::create([
'tenant_id' => 'tenant-driver-preview',
'name' => 'Tenant Preview',
'metadata' => [],
]);
$backupSet = BackupSet::create([
'tenant_id' => $tenant->id,
'name' => 'Backup',
'status' => 'completed',
'item_count' => 1,
]);
$backupItem = BackupItem::create([
'tenant_id' => $tenant->id,
'backup_set_id' => $backupSet->id,
'policy_id' => null,
'policy_identifier' => 'wdp-1',
'policy_type' => 'windowsDriverUpdateProfile',
'platform' => 'windows',
'payload' => [
'@odata.type' => '#microsoft.graph.windowsDriverUpdateProfile',
'displayName' => 'Driver Updates A',
],
]);
$service = app(RestoreService::class);
$preview = $service->preview($tenant, $backupSet, [$backupItem->id]);
expect($preview)->toHaveCount(1);
$policyPreview = $preview[0] ?? [];
expect($policyPreview['policy_type'] ?? null)->toBe('windowsDriverUpdateProfile');
expect($policyPreview['action'] ?? null)->toBe('create');
expect($policyPreview['restore_mode'] ?? null)->toBe('enabled');
});
test('restore preview warns about missing compliance notification templates', function () {
app()->bind(GraphClientInterface::class, fn () => new class implements GraphClientInterface
{

View File

@ -41,6 +41,27 @@ public function getPolicy(string $policyType, string $policyId, array $options =
]);
}
if ($policyType === 'windowsDriverUpdateProfile') {
return new GraphResponse(success: true, data: [
'payload' => [
'id' => $policyId,
'displayName' => 'Driver Updates A',
'description' => 'Drivers rollout policy',
'@odata.type' => '#microsoft.graph.windowsDriverUpdateProfile',
'approvalType' => 'automatic',
'deploymentDeferralInDays' => 7,
'deviceReporting' => 12,
'newUpdates' => 3,
'roleScopeTagIds' => ['0'],
'inventorySyncStatus' => [
'@odata.type' => '#microsoft.graph.windowsDriverUpdateProfileInventorySyncStatus',
'driverInventorySyncState' => 'success',
'lastSuccessfulSyncDateTime' => '2026-01-01T00:00:00Z',
],
],
]);
}
return new GraphResponse(success: true, data: [
'payload' => [
'id' => $policyId,
@ -271,6 +292,41 @@ public function request(string $method, string $path, array $options = []): Grap
expect($client->requests[0][3]['select'])->not->toContain('@odata.type');
});
it('captures windows driver update profile snapshots with full payload', function () {
$client = new PolicySnapshotGraphClient;
app()->instance(GraphClientInterface::class, $client);
$tenant = Tenant::factory()->create([
'tenant_id' => 'tenant-driver',
'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' => 'wdp-123',
'policy_type' => 'windowsDriverUpdateProfile',
'display_name' => 'Driver Updates A',
'platform' => 'windows',
]);
$service = app(PolicySnapshotService::class);
$result = $service->fetch($tenant, $policy);
expect($result)->toHaveKey('payload');
expect($result['payload']['approvalType'] ?? null)->toBe('automatic');
expect($result['payload']['deploymentDeferralInDays'] ?? null)->toBe(7);
expect($result['payload']['deviceReporting'] ?? null)->toBe(12);
expect($result['payload']['newUpdates'] ?? null)->toBe(3);
expect($result['payload']['inventorySyncStatus']['driverInventorySyncState'] ?? null)->toBe('success');
expect($client->requests[0][0])->toBe('getPolicy');
expect($client->requests[0][1])->toBe('windowsDriverUpdateProfile');
expect($client->requests[0][2])->toBe('wdp-123');
});
test('falls back to metadata-only snapshot when mamAppConfiguration returns 500', function () {
$client = Mockery::mock(\App\Services\Graph\GraphClientInterface::class);
$client->shouldReceive('getPolicy')