Compare commits
2 Commits
dev
...
014-enroll
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5bc7c09a | ||
|
|
4a953dc4ee |
@ -8,6 +8,7 @@
|
||||
use App\Services\Intune\AppProtectionPolicyNormalizer;
|
||||
use App\Services\Intune\CompliancePolicyNormalizer;
|
||||
use App\Services\Intune\DeviceConfigurationPolicyNormalizer;
|
||||
use App\Services\Intune\EnrollmentAutopilotPolicyNormalizer;
|
||||
use App\Services\Intune\GroupPolicyConfigurationNormalizer;
|
||||
use App\Services\Intune\SettingsCatalogPolicyNormalizer;
|
||||
use App\Services\Intune\WindowsFeatureUpdateProfileNormalizer;
|
||||
@ -41,6 +42,7 @@ public function register(): void
|
||||
AppProtectionPolicyNormalizer::class,
|
||||
CompliancePolicyNormalizer::class,
|
||||
DeviceConfigurationPolicyNormalizer::class,
|
||||
EnrollmentAutopilotPolicyNormalizer::class,
|
||||
GroupPolicyConfigurationNormalizer::class,
|
||||
SettingsCatalogPolicyNormalizer::class,
|
||||
WindowsFeatureUpdateProfileNormalizer::class,
|
||||
|
||||
230
app/Services/Intune/EnrollmentAutopilotPolicyNormalizer.php
Normal file
230
app/Services/Intune/EnrollmentAutopilotPolicyNormalizer.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Intune;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class EnrollmentAutopilotPolicyNormalizer implements PolicyTypeNormalizer
|
||||
{
|
||||
public function __construct(private readonly DefaultPolicyNormalizer $defaultNormalizer) {}
|
||||
|
||||
public function supports(string $policyType): bool
|
||||
{
|
||||
return in_array($policyType, [
|
||||
'windowsAutopilotDeploymentProfile',
|
||||
'windowsEnrollmentStatusPage',
|
||||
'enrollmentRestriction',
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{status: string, settings: array<int, array<string, mixed>>, settings_table?: array<string, mixed>, warnings: array<int, string>}
|
||||
*/
|
||||
public function normalize(?array $snapshot, string $policyType, ?string $platform = null): array
|
||||
{
|
||||
$snapshot = is_array($snapshot) ? $snapshot : [];
|
||||
|
||||
$displayName = Arr::get($snapshot, 'displayName') ?? Arr::get($snapshot, 'name');
|
||||
$description = Arr::get($snapshot, 'description');
|
||||
|
||||
$warnings = [];
|
||||
|
||||
if ($policyType === 'enrollmentRestriction') {
|
||||
$warnings[] = 'Restore is preview-only for Enrollment Restrictions.';
|
||||
}
|
||||
|
||||
$generalEntries = [
|
||||
['key' => 'Type', 'value' => $policyType],
|
||||
];
|
||||
|
||||
if (is_string($displayName) && $displayName !== '') {
|
||||
$generalEntries[] = ['key' => 'Display name', 'value' => $displayName];
|
||||
}
|
||||
|
||||
if (is_string($description) && $description !== '') {
|
||||
$generalEntries[] = ['key' => 'Description', 'value' => $description];
|
||||
}
|
||||
|
||||
$odataType = Arr::get($snapshot, '@odata.type');
|
||||
if (is_string($odataType) && $odataType !== '') {
|
||||
$generalEntries[] = ['key' => '@odata.type', 'value' => $odataType];
|
||||
}
|
||||
|
||||
$roleScopeTagIds = Arr::get($snapshot, 'roleScopeTagIds');
|
||||
if (is_array($roleScopeTagIds) && $roleScopeTagIds !== []) {
|
||||
$generalEntries[] = ['key' => 'Scope tag IDs', 'value' => array_values($roleScopeTagIds)];
|
||||
}
|
||||
|
||||
$settings = [
|
||||
[
|
||||
'type' => 'keyValue',
|
||||
'title' => 'General',
|
||||
'entries' => $generalEntries,
|
||||
],
|
||||
];
|
||||
|
||||
$typeBlock = match ($policyType) {
|
||||
'windowsAutopilotDeploymentProfile' => $this->buildAutopilotBlock($snapshot),
|
||||
'windowsEnrollmentStatusPage' => $this->buildEnrollmentStatusPageBlock($snapshot),
|
||||
'enrollmentRestriction' => $this->buildEnrollmentRestrictionBlock($snapshot),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($typeBlock !== null) {
|
||||
$settings[] = $typeBlock;
|
||||
}
|
||||
|
||||
$settings = array_values(array_filter($settings));
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'settings' => $settings,
|
||||
'warnings' => $warnings,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, title: string, entries: array<int, array{key: string, value: mixed}>}|null
|
||||
*/
|
||||
private function buildAutopilotBlock(array $snapshot): ?array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ([
|
||||
'deviceNameTemplate' => 'Device name template',
|
||||
'language' => 'Language',
|
||||
'locale' => 'Locale',
|
||||
'deploymentMode' => 'Deployment mode',
|
||||
'deviceType' => 'Device type',
|
||||
'enableWhiteGlove' => 'Pre-provisioning (White Glove)',
|
||||
'hybridAzureADJoinSkipConnectivityCheck' => 'Skip Hybrid AAD connectivity check',
|
||||
] as $key => $label) {
|
||||
$value = Arr::get($snapshot, $key);
|
||||
|
||||
if (is_string($value) && $value !== '') {
|
||||
$entries[] = ['key' => $label, 'value' => $value];
|
||||
} elseif (is_bool($value)) {
|
||||
$entries[] = ['key' => $label, 'value' => $value ? 'Enabled' : 'Disabled'];
|
||||
}
|
||||
}
|
||||
|
||||
$oobe = Arr::get($snapshot, 'outOfBoxExperienceSettings');
|
||||
if (is_array($oobe) && $oobe !== []) {
|
||||
$entries[] = ['key' => 'Out-of-box experience', 'value' => Arr::except($oobe, ['@odata.type'])];
|
||||
}
|
||||
|
||||
$assignments = Arr::get($snapshot, 'assignments');
|
||||
if (is_array($assignments) && $assignments !== []) {
|
||||
$entries[] = ['key' => 'Assignments (snapshot)', 'value' => '[present]'];
|
||||
}
|
||||
|
||||
if ($entries === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'keyValue',
|
||||
'title' => 'Autopilot profile',
|
||||
'entries' => $entries,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, title: string, entries: array<int, array{key: string, value: mixed}>}|null
|
||||
*/
|
||||
private function buildEnrollmentStatusPageBlock(array $snapshot): ?array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ([
|
||||
'priority' => 'Priority',
|
||||
'showInstallationProgress' => 'Show installation progress',
|
||||
'blockDeviceSetupRetryByUser' => 'Block retry by user',
|
||||
'allowDeviceResetOnInstallFailure' => 'Allow device reset on install failure',
|
||||
'installProgressTimeoutInMinutes' => 'Install progress timeout (minutes)',
|
||||
'allowLogCollectionOnInstallFailure' => 'Allow log collection on failure',
|
||||
] as $key => $label) {
|
||||
$value = Arr::get($snapshot, $key);
|
||||
|
||||
if (is_int($value) || is_float($value)) {
|
||||
$entries[] = ['key' => $label, 'value' => $value];
|
||||
} elseif (is_string($value) && $value !== '') {
|
||||
$entries[] = ['key' => $label, 'value' => $value];
|
||||
} elseif (is_bool($value)) {
|
||||
$entries[] = ['key' => $label, 'value' => $value ? 'Enabled' : 'Disabled'];
|
||||
}
|
||||
}
|
||||
|
||||
$selected = Arr::get($snapshot, 'selectedMobileAppIds');
|
||||
if (is_array($selected) && $selected !== []) {
|
||||
$entries[] = ['key' => 'Selected mobile app IDs', 'value' => array_values($selected)];
|
||||
}
|
||||
|
||||
$assigned = Arr::get($snapshot, 'assignments');
|
||||
if (is_array($assigned) && $assigned !== []) {
|
||||
$entries[] = ['key' => 'Assignments (snapshot)', 'value' => '[present]'];
|
||||
}
|
||||
|
||||
if ($entries === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'keyValue',
|
||||
'title' => 'Enrollment Status Page (ESP)',
|
||||
'entries' => $entries,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, title: string, entries: array<int, array{key: string, value: mixed}>}|null
|
||||
*/
|
||||
private function buildEnrollmentRestrictionBlock(array $snapshot): ?array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ([
|
||||
'priority' => 'Priority',
|
||||
'version' => 'Version',
|
||||
'deviceEnrollmentConfigurationType' => 'Configuration type',
|
||||
] as $key => $label) {
|
||||
$value = Arr::get($snapshot, $key);
|
||||
|
||||
if (is_int($value) || is_float($value)) {
|
||||
$entries[] = ['key' => $label, 'value' => $value];
|
||||
} elseif (is_string($value) && $value !== '') {
|
||||
$entries[] = ['key' => $label, 'value' => $value];
|
||||
}
|
||||
}
|
||||
|
||||
$platforms = Arr::get($snapshot, 'platformRestrictions');
|
||||
if (is_array($platforms) && $platforms !== []) {
|
||||
$entries[] = ['key' => 'Platform restrictions', 'value' => Arr::except($platforms, ['@odata.type'])];
|
||||
}
|
||||
|
||||
$assigned = Arr::get($snapshot, 'assignments');
|
||||
if (is_array($assigned) && $assigned !== []) {
|
||||
$entries[] = ['key' => 'Assignments (snapshot)', 'value' => '[present]'];
|
||||
}
|
||||
|
||||
if ($entries === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'keyValue',
|
||||
'title' => 'Enrollment restrictions',
|
||||
'entries' => $entries,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function flattenForDiff(?array $snapshot, string $policyType, ?string $platform = null): array
|
||||
{
|
||||
$normalized = $this->normalize($snapshot ?? [], $policyType, $platform);
|
||||
|
||||
return $this->defaultNormalizer->flattenNormalizedForDiff($normalized);
|
||||
}
|
||||
}
|
||||
34
specs/014-enrollment-autopilot/checklists/requirements.md
Normal file
34
specs/014-enrollment-autopilot/checklists/requirements.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: Enrollment & Autopilot
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-01-01
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Assumptions: Restore behavior for enrollment restrictions remains preview-only until a separate product decision explicitly enables it.
|
||||
48
specs/014-enrollment-autopilot/plan.md
Normal file
48
specs/014-enrollment-autopilot/plan.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Plan: Enrollment & Autopilot (014)
|
||||
|
||||
**Branch**: `014-enrollment-autopilot`
|
||||
**Date**: 2026-01-01
|
||||
**Input**: [spec.md](./spec.md)
|
||||
|
||||
## Goal
|
||||
Provide end-to-end support for enrollment & Autopilot configuration items with readable normalized settings and safe restore behavior.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
- Policy types:
|
||||
- `windowsAutopilotDeploymentProfile` (restore enabled)
|
||||
- `windowsEnrollmentStatusPage` (restore enabled)
|
||||
- `enrollmentRestriction` (restore preview-only)
|
||||
- Readable “Normalized settings” for the above types.
|
||||
- Restore behavior:
|
||||
- Autopilot/ESP: apply via existing restore mechanisms (create-if-missing allowed)
|
||||
- Enrollment restrictions: must be skipped on execution by default (preview-only)
|
||||
- Tests for normalization + UI rendering + preview-only enforcement.
|
||||
|
||||
### Out of scope
|
||||
- New restore wizard flows/pages.
|
||||
- Enabling execution for enrollment restrictions (requires product decision).
|
||||
- New external services.
|
||||
|
||||
## Approach
|
||||
1. Verify `config/graph_contracts.php` and `config/tenantpilot.php` entries for the three policy types.
|
||||
2. Implement a new policy type normalizer to provide stable, enrollment-relevant blocks for:
|
||||
- Autopilot deployment profiles
|
||||
- Enrollment Status Page
|
||||
- Enrollment restrictions
|
||||
3. Register the normalizer with the `policy-type-normalizers` tag.
|
||||
4. Add tests:
|
||||
- Unit tests for normalized output stability/shape.
|
||||
- Filament feature tests verifying “Normalized settings” renders for each type.
|
||||
- Feature test verifying `enrollmentRestriction` restore is preview-only and skipped on execution.
|
||||
5. Run targeted tests and Pint.
|
||||
|
||||
## Risks & Mitigations
|
||||
- Payload shape variance across tenants: normalizer must handle missing keys safely.
|
||||
- Enrollment restrictions are high impact: execution must remain disabled by default (preview-only).
|
||||
|
||||
## Success Criteria
|
||||
- Normalized settings are stable and readable for all in-scope types.
|
||||
- Restore execution skips preview-only types and reports clear result reasons.
|
||||
- Tests cover normalization and preview-only enforcement.
|
||||
111
specs/014-enrollment-autopilot/spec.md
Normal file
111
specs/014-enrollment-autopilot/spec.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Feature Specification: Enrollment & Autopilot
|
||||
|
||||
**Feature Branch**: `014-enrollment-autopilot`
|
||||
**Created**: 2026-01-01
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Improve enrollment and Autopilot configuration safety by adding readable normalized settings, reliable snapshot capture, and safe restore behavior for enrollment restrictions, enrollment status page, and Autopilot deployment profiles."
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
<!--
|
||||
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
||||
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
||||
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
||||
|
||||
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
||||
Think of each story as a standalone slice of functionality that can be:
|
||||
- Developed independently
|
||||
- Tested independently
|
||||
- Deployed independently
|
||||
- Demonstrated to users independently
|
||||
-->
|
||||
|
||||
### User Story 1 - Restore Autopilot/ESP safely (Priority: P1)
|
||||
|
||||
As an admin, I want to restore Autopilot deployment profiles and the Enrollment Status Page configuration from saved snapshots so I can recover enrollment readiness after changes.
|
||||
|
||||
**Why this priority**: Enrollment misconfiguration blocks device onboarding; fast recovery is critical.
|
||||
|
||||
**Independent Test**: Can be tested by restoring one Autopilot profile and one Enrollment Status Page item from snapshots into a target tenant and verifying they match the snapshot.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a saved Autopilot deployment profile snapshot and a target tenant where the profile is missing, **When** I restore it, **Then** a new profile is created and restore reports success.
|
||||
2. **Given** a saved Enrollment Status Page snapshot and a target tenant where the item exists with differences, **When** I restore it, **Then** the configuration is updated to match the snapshot and restore reports success.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Restore behavior is explicit for high-risk enrollment restrictions (Priority: P2)
|
||||
|
||||
As an admin, I want high-risk enrollment restrictions to be handled explicitly (preview-only unless intentionally enabled) so I do not accidentally break enrollment flows.
|
||||
|
||||
**Why this priority**: Enrollment restrictions can lock out device onboarding; accidental changes are high impact.
|
||||
|
||||
**Independent Test**: Can be tested by attempting restore of an enrollment restriction item and verifying the system does not apply changes when it is configured as preview-only.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an enrollment restriction snapshot and the feature is allowed for preview-only, **When** I run restore execution, **Then** the system skips applying changes and records a result indicating preview-only behavior.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Readable normalized settings (Priority: P3)
|
||||
|
||||
As an admin, I want to view readable normalized settings for Autopilot and Enrollment configurations so I can understand what will happen during device onboarding.
|
||||
|
||||
**Why this priority**: Enrollment troubleshooting is faster when key settings are visible and consistent.
|
||||
|
||||
**Independent Test**: Can be tested by opening a version details page and confirming a stable normalized settings view is present and readable.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a saved Autopilot/ESP snapshot, **When** I view the policy version, **Then** I see a normalized settings view that highlights key enrollment-relevant fields.
|
||||
|
||||
---
|
||||
|
||||
[Add more user stories as needed, each with an assigned priority]
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- Autopilot or ESP configuration in the target tenant is missing: system must create or clearly fail with an actionable reason.
|
||||
- Restoring Enrollment Status Page items must not silently drop settings; failures must be explicit.
|
||||
- Enrollment restrictions remain preview-only unless explicitly enabled by product decision; execution must not apply them by default.
|
||||
- Assignments (if present for these types) that cannot be mapped must be reported as manual-required.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: The content in this section represents placeholders.
|
||||
Fill them out with the right functional requirements.
|
||||
-->
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: System MUST support listing and viewing enrollment and Autopilot configuration items for the supported types.
|
||||
- **FR-002**: System MUST capture snapshots for these configuration items that are sufficient for later restore.
|
||||
- **FR-003**: System MUST support restore for Autopilot deployment profiles and Enrollment Status Page configuration.
|
||||
- **FR-004**: System MUST treat enrollment restrictions as high risk and default them to preview-only behavior unless explicitly enabled.
|
||||
- **FR-005**: System MUST present a readable normalized settings view for these configuration items and their versions.
|
||||
- **FR-006**: System MUST prevent restore execution if the snapshot type does not match the target item type.
|
||||
- **FR-007**: System MUST record audit entries for restore preview and restore execution attempts.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Autopilot Deployment Profile**: A configuration object that defines device provisioning behavior during Autopilot.
|
||||
- **Enrollment Status Page Configuration**: A configuration object that defines the onboarding status experience during enrollment.
|
||||
- **Enrollment Restriction**: A high-risk configuration object that can block or constrain enrollment.
|
||||
- **Snapshot**: An immutable capture of a configuration object at a point in time.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Define measurable success criteria.
|
||||
These must be technology-agnostic and measurable.
|
||||
-->
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: An admin can complete a restore preview for a single Autopilot/ESP item in under 1 minute.
|
||||
- **SC-002**: In a test tenant, restoring Autopilot deployment profiles and Enrollment Status Page results in configurations matching the snapshot for 100% of supported items.
|
||||
- **SC-003**: Enrollment restrictions remain non-executable by default (preview-only) with clear status reporting in 100% of attempts.
|
||||
- **SC-004**: Normalized settings views for these items are stable and readable (same snapshot yields identical normalized output).
|
||||
30
specs/014-enrollment-autopilot/tasks.md
Normal file
30
specs/014-enrollment-autopilot/tasks.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Tasks: Enrollment & Autopilot (014)
|
||||
|
||||
**Branch**: `014-enrollment-autopilot` | **Date**: 2026-01-01
|
||||
**Input**: [spec.md](./spec.md), [plan.md](./plan.md)
|
||||
|
||||
## Phase 1: Contracts Review
|
||||
- [x] T001 Verify `config/graph_contracts.php` entries for:
|
||||
- `windowsAutopilotDeploymentProfile`
|
||||
- `windowsEnrollmentStatusPage`
|
||||
- `enrollmentRestriction`
|
||||
(resource, type_family, create/update methods, assignment paths/payload keys)
|
||||
- [x] T002 Verify `config/tenantpilot.php` entries and restore modes:
|
||||
- Autopilot/ESP = `enabled`
|
||||
- Enrollment restrictions = `preview-only`
|
||||
|
||||
## Phase 2: UI Normalization
|
||||
- [x] T003 Add an `EnrollmentAutopilotPolicyNormalizer` (or equivalent) that produces readable normalized settings for the three policy types.
|
||||
- [x] T004 Register the normalizer in the app container/provider (tag `policy-type-normalizers`).
|
||||
|
||||
## Phase 3: Restore Safety
|
||||
- [x] T005 Add a feature test verifying `enrollmentRestriction` restore is preview-only and skipped on execution (no Graph apply calls).
|
||||
|
||||
## Phase 4: Tests + Verification
|
||||
- [ ] T006 Add unit tests for normalized output (shape + stability) for the three policy types.
|
||||
- [ ] T007 Add Filament render tests for “Normalized settings” tab for the three policy types.
|
||||
- [ ] T008 Run targeted tests.
|
||||
- [ ] T009 Run Pint (`./vendor/bin/pint --dirty`).
|
||||
|
||||
## Open TODOs (Follow-up)
|
||||
- None.
|
||||
111
tests/Feature/Filament/EnrollmentRestrictionsPreviewOnlyTest.php
Normal file
111
tests/Feature/Filament/EnrollmentRestrictionsPreviewOnlyTest.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
use App\Models\BackupItem;
|
||||
use App\Models\BackupSet;
|
||||
use App\Models\Policy;
|
||||
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);
|
||||
|
||||
test('enrollment restriction restores are preview-only and skipped on execution', function () {
|
||||
$client = new class implements GraphClientInterface
|
||||
{
|
||||
public int $applyCalls = 0;
|
||||
|
||||
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->applyCalls++;
|
||||
|
||||
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, []);
|
||||
}
|
||||
};
|
||||
|
||||
app()->instance(GraphClientInterface::class, $client);
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'tenant_id' => 'tenant-enrollment-restriction',
|
||||
'name' => 'Tenant Enrollment Restriction',
|
||||
'metadata' => [],
|
||||
]);
|
||||
|
||||
$policy = Policy::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'external_id' => 'enrollment-restriction-1',
|
||||
'policy_type' => 'enrollmentRestriction',
|
||||
'display_name' => 'Enrollment Restriction',
|
||||
'platform' => 'all',
|
||||
]);
|
||||
|
||||
$backupSet = BackupSet::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'name' => 'Enrollment Restriction Backup',
|
||||
'status' => 'completed',
|
||||
'item_count' => 1,
|
||||
]);
|
||||
|
||||
$backupItem = BackupItem::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'backup_set_id' => $backupSet->id,
|
||||
'policy_id' => $policy->id,
|
||||
'policy_identifier' => $policy->external_id,
|
||||
'policy_type' => $policy->policy_type,
|
||||
'platform' => $policy->platform,
|
||||
'payload' => [
|
||||
'@odata.type' => '#microsoft.graph.deviceEnrollmentConfiguration',
|
||||
'id' => $policy->external_id,
|
||||
'displayName' => $policy->display_name,
|
||||
],
|
||||
]);
|
||||
|
||||
$service = app(RestoreService::class);
|
||||
$preview = $service->preview($tenant, $backupSet, [$backupItem->id]);
|
||||
|
||||
$previewItem = collect($preview)->first(fn (array $item) => ($item['policy_type'] ?? null) === 'enrollmentRestriction');
|
||||
|
||||
expect($previewItem)->not->toBeNull()
|
||||
->and($previewItem['restore_mode'] ?? null)->toBe('preview-only');
|
||||
|
||||
$run = $service->execute(
|
||||
tenant: $tenant,
|
||||
backupSet: $backupSet,
|
||||
selectedItemIds: [$backupItem->id],
|
||||
dryRun: false,
|
||||
actorEmail: 'tester@example.com',
|
||||
actorName: 'Tester',
|
||||
);
|
||||
|
||||
expect($run->results)->toHaveCount(1);
|
||||
expect($run->results[0]['status'])->toBe('skipped');
|
||||
expect($run->results[0]['reason'])->toBe('preview_only');
|
||||
|
||||
expect($client->applyCalls)->toBe(0);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user