- Fix update ring inventory filter using isof() - Hydrate + restore Windows Update Ring via derived-type endpoint - Add Windows Feature/Quality Update Profile support - Stabilize tenant selection in tests
254 lines
9.1 KiB
PHP
254 lines
9.1 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');
|
|
});
|
|
|
|
class WindowsUpdateRingSnapshotGraphClient 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];
|
|
|
|
return new GraphResponse(success: true, data: [
|
|
'payload' => [
|
|
'id' => $policyId,
|
|
'@odata.type' => '#microsoft.graph.windowsUpdateForBusinessConfiguration',
|
|
'displayName' => 'Ring A',
|
|
],
|
|
]);
|
|
}
|
|
|
|
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 ($method === 'GET' && $path === 'deviceManagement/deviceConfigurations/policy-wuring/microsoft.graph.windowsUpdateForBusinessConfiguration') {
|
|
return new GraphResponse(success: true, data: [
|
|
'automaticUpdateMode' => 'autoInstallAtMaintenanceTime',
|
|
'featureUpdatesDeferralPeriodInDays' => 14,
|
|
]);
|
|
}
|
|
|
|
return new GraphResponse(success: true, data: []);
|
|
}
|
|
}
|
|
|
|
it('hydrates windows update ring snapshots via derived type cast endpoint', function () {
|
|
$client = new WindowsUpdateRingSnapshotGraphClient;
|
|
app()->instance(GraphClientInterface::class, $client);
|
|
|
|
$tenant = Tenant::factory()->create([
|
|
'tenant_id' => 'tenant-wuring',
|
|
'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' => 'policy-wuring',
|
|
'policy_type' => 'windowsUpdateRing',
|
|
'display_name' => 'Ring A',
|
|
'platform' => 'windows',
|
|
]);
|
|
|
|
$service = app(PolicySnapshotService::class);
|
|
$result = $service->fetch($tenant, $policy);
|
|
|
|
expect($result)->toHaveKey('payload');
|
|
expect($result['payload']['@odata.type'])->toBe('#microsoft.graph.windowsUpdateForBusinessConfiguration');
|
|
expect($result['payload']['automaticUpdateMode'])->toBe('autoInstallAtMaintenanceTime');
|
|
expect($result['payload']['featureUpdatesDeferralPeriodInDays'])->toBe(14);
|
|
expect($result['metadata']['properties_hydration'] ?? null)->toBe('complete');
|
|
});
|