From 5a8b8d9c8a79872e93e5b6ba0f614b0dffcb8426 Mon Sep 17 00:00:00 2001 From: intuneascode Date: Thu, 13 Nov 2025 16:32:15 +0000 Subject: [PATCH] fix(windows_esp): resolve app validation pagination limit by querying apps individually --- .../mocks/responders.go | 86 +++++++-------- .../validate.go | 101 ++++++++++-------- 2 files changed, 96 insertions(+), 91 deletions(-) diff --git a/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/mocks/responders.go b/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/mocks/responders.go index 9bd5e83c..3f8f3974 100644 --- a/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/mocks/responders.go +++ b/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/mocks/responders.go @@ -32,56 +32,34 @@ func (m *WindowsEnrollmentStatusPageMock) RegisterMocks() { mockState.enrollmentStatusPages = make(map[string]map[string]any) mockState.Unlock() - // Mock the mobile apps endpoint for validation - httpmock.RegisterResponder("GET", `=~^https://graph\.microsoft\.com/beta/deviceAppManagement/mobileApps.*`, func(req *http.Request) (*http.Response, error) { - // Return mock mobile apps that include the test app IDs used in unit tests - mockApps := map[string]any{ - "@odata.context": "https://graph.microsoft.com/beta/$metadata#deviceAppManagement/mobileApps", - "@odata.count": 5, - "value": []any{ - map[string]any{ - "@odata.type": "#microsoft.graph.win32LobApp", - "id": "12345678-1234-1234-1234-123456789012", - "displayName": "Test App 1", - "description": "Test application 1 for unit testing", - "publisher": "Test Publisher", - "publishingState": "published", - }, - map[string]any{ - "@odata.type": "#microsoft.graph.winGetApp", - "id": "87654321-4321-4321-4321-210987654321", - "displayName": "Test App 2", - "description": "Test application 2 for unit testing", - "publisher": "Test Publisher", - "publishingState": "published", - }, - map[string]any{ - "@odata.type": "#microsoft.graph.win32LobApp", - "id": "e4938228-aab3-493b-a9d5-8250aa8e9d55", - "displayName": "Test App 3", - "description": "Test application 3 for unit testing", - "publisher": "Test Publisher", - "publishingState": "published", - }, - map[string]any{ - "@odata.type": "#microsoft.graph.win32LobApp", - "id": "e83d36e1-3ff2-4567-90d9-940919184ad5", - "displayName": "Test App 4", - "description": "Test application 4 for unit testing", - "publisher": "Test Publisher", - "publishingState": "published", - }, - map[string]any{ - "@odata.type": "#microsoft.graph.win32LobApp", - "id": "cd4486df-05cc-42bd-8c34-67ac20e10166", - "displayName": "Test App 5", - "description": "Test application 5 for unit testing", - "publisher": "Test Publisher", - "publishingState": "published", - }, - }, + // Define mock apps used in tests + mockApps := map[string]string{ + "12345678-1234-1234-1234-123456789012": "#microsoft.graph.win32LobApp", + "87654321-4321-4321-4321-210987654321": "#microsoft.graph.winGetApp", + "e4938228-aab3-493b-a9d5-8250aa8e9d55": "#microsoft.graph.win32LobApp", + "e83d36e1-3ff2-4567-90d9-940919184ad5": "#microsoft.graph.win32LobApp", + "cd4486df-05cc-42bd-8c34-67ac20e10166": "#microsoft.graph.win32LobApp", + } + + // Mock individual mobile app lookup by ID + httpmock.RegisterResponder("GET", `=~^https://graph\.microsoft\.com/beta/deviceAppManagement/mobileApps/[0-9a-fA-F-]+$`, func(req *http.Request) (*http.Response, error) { + parts := strings.Split(req.URL.Path, "/") + appId := parts[len(parts)-1] + + if odataType, ok := mockApps[appId]; ok { + return httpmock.NewJsonResponse(200, map[string]any{ + "@odata.type": odataType, + "id": appId, + }) } - return httpmock.NewJsonResponse(200, mockApps) + + // Return 404 for unknown app IDs + return httpmock.NewJsonResponse(404, map[string]any{ + "error": map[string]any{ + "code": "ResourceNotFound", + "message": "The requested resource does not exist.", + }, + }) }) httpmock.RegisterResponder("GET", "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations", func(req *http.Request) (*http.Response, error) { @@ -318,6 +296,16 @@ func (m *WindowsEnrollmentStatusPageMock) RegisterErrorMocks() { mockState.enrollmentStatusPages = make(map[string]map[string]any) mockState.Unlock() + // Mock individual mobile app lookup by ID for error scenarios - always return 404 + httpmock.RegisterResponder("GET", `=~^https://graph\.microsoft\.com/beta/deviceAppManagement/mobileApps/[0-9a-fA-F-]+$`, func(req *http.Request) (*http.Response, error) { + return httpmock.NewJsonResponse(404, map[string]any{ + "error": map[string]any{ + "code": "ResourceNotFound", + "message": "The requested resource does not exist.", + }, + }) + }) + httpmock.RegisterResponder("GET", "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations", func(req *http.Request) (*http.Response, error) { jsonStr, _ := helpers.ParseJSONFile("../tests/responses/validate_get/get_windows_enrollment_status_pages_list.json") var responseObj map[string]any diff --git a/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/validate.go b/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/validate.go index 4bd31894..d9198203 100644 --- a/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/validate.go +++ b/internal/services/resources/device_management/graph_beta/windows_enrollment_status_page/validate.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" msgraphbetasdk "github.com/microsoftgraph/msgraph-beta-sdk-go" - "github.com/microsoftgraph/msgraph-beta-sdk-go/deviceappmanagement" + graphmodels "github.com/microsoftgraph/msgraph-beta-sdk-go/models" ) // validateRequest validates the entire request payload @@ -48,60 +48,77 @@ func validateSelectedMobileAppIds(ctx context.Context, client *msgraphbetasdk.Gr return nil } - // Get all Windows app types from Microsoft Graph - filter := "isof('microsoft.graph.windowsAppX') or isof('microsoft.graph.windowsMobileMSI') or isof('microsoft.graph.windowsUniversalAppX') or isof('microsoft.graph.officeSuiteApp') or isof('microsoft.graph.windowsMicrosoftEdgeApp') or isof('microsoft.graph.winGetApp') or isof('microsoft.graph.win32LobApp') or isof('microsoft.graph.win32CatalogApp')" - orderby := "displayname" - top := int32(250) - - requestConfig := &deviceappmanagement.MobileAppsRequestBuilderGetRequestConfiguration{ - QueryParameters: &deviceappmanagement.MobileAppsRequestBuilderGetQueryParameters{ - Filter: &filter, - Orderby: []string{orderby}, - Top: &top, - }, - } - - mobileApps, err := client. - DeviceAppManagement(). - MobileApps(). - Get(ctx, requestConfig) - - if err != nil { - tflog.Error(ctx, "Failed to retrieve mobile apps for validation", map[string]any{ - "error": err.Error(), - }) - return fmt.Errorf("failed to validate mobile app IDs: unable to retrieve available apps from Microsoft Graph") - } + for _, appId := range appIdStrings { + appIdValue := appId.ValueString() - // Create a map of valid app IDs for quick lookup - validAppIds := make(map[string]string) // ID -> DisplayName - validAppTypes := make(map[string]string) // ID -> AppType + // Query the specific app by ID + app, err := client. + DeviceAppManagement(). + MobileApps(). + ByMobileAppId(appIdValue). + Get(ctx, nil) + + if err != nil { + tflog.Error(ctx, "Failed to retrieve mobile app for validation", map[string]any{ + "appId": appIdValue, + "error": err.Error(), + }) + return fmt.Errorf("supplied app ID '%s' does not match any valid Windows app types. Valid app types include: windowsAppX, windowsMobileMSI, windowsUniversalAppX, officeSuiteApp, windowsMicrosoftEdgeApp, winGetApp, win32LobApp, win32CatalogApp", appIdValue) + } - if mobileApps.GetValue() != nil { - for _, app := range mobileApps.GetValue() { - if app.GetId() != nil && app.GetDisplayName() != nil && app.GetOdataType() != nil { - validAppIds[*app.GetId()] = *app.GetDisplayName() - validAppTypes[*app.GetId()] = *app.GetOdataType() + // Validate the app type using SDK type assertions + isValidType := false + var appTypeName string + + switch app.(type) { + case *graphmodels.WindowsAppX: + isValidType = true + appTypeName = "windowsAppX" + case *graphmodels.WindowsMobileMSI: + isValidType = true + appTypeName = "windowsMobileMSI" + case *graphmodels.WindowsUniversalAppX: + isValidType = true + appTypeName = "windowsUniversalAppX" + case *graphmodels.OfficeSuiteApp: + isValidType = true + appTypeName = "officeSuiteApp" + case *graphmodels.WindowsMicrosoftEdgeApp: + isValidType = true + appTypeName = "windowsMicrosoftEdgeApp" + case *graphmodels.WinGetApp: + isValidType = true + appTypeName = "winGetApp" + case *graphmodels.Win32LobApp: + isValidType = true + appTypeName = "win32LobApp" + case *graphmodels.Win32CatalogApp: + isValidType = true + appTypeName = "win32CatalogApp" + default: + if odataType := app.GetOdataType(); odataType != nil { + appTypeName = *odataType + } else { + appTypeName = "unknown" } } - } - // Validate each provided app ID - for _, appId := range appIdStrings { - appIdValue := appId.ValueString() - displayName, exists := validAppIds[appIdValue] + if !isValidType { + return fmt.Errorf("supplied app ID '%s' has type '%s' which is not a valid Windows app type. Valid app types include: windowsAppX, windowsMobileMSI, windowsUniversalAppX, officeSuiteApp, windowsMicrosoftEdgeApp, winGetApp, win32LobApp, win32CatalogApp", appIdValue, appTypeName) + } - if !exists { - return fmt.Errorf("supplied app ID '%s' does not match any valid Windows app types. Valid app types include: windowsAppX, windowsMobileMSI, windowsUniversalAppX, officeSuiteApp, windowsMicrosoftEdgeApp, winGetApp, win32LobApp, win32CatalogApp", appIdValue) + displayName := "" + if app.GetDisplayName() != nil { + displayName = *app.GetDisplayName() } tflog.Debug(ctx, "Validated mobile app", map[string]any{ "appId": appIdValue, "displayName": displayName, - "appType": validAppTypes[appIdValue], + "appType": appTypeName, }) } - tflog.Debug(ctx, fmt.Sprintf("Validated %d mobile app IDs against %d available Windows apps", len(appIdStrings), len(validAppIds))) + tflog.Debug(ctx, fmt.Sprintf("Successfully validated %d mobile app IDs", len(appIdStrings))) return nil }