diff --git a/src/System Application/App/SharePoint/src/graph/SharePointGraphClient.Codeunit.al b/src/System Application/App/SharePoint/src/graph/SharePointGraphClient.Codeunit.al index 2c324cb6df..6c64915e0d 100644 --- a/src/System Application/App/SharePoint/src/graph/SharePointGraphClient.Codeunit.al +++ b/src/System Application/App/SharePoint/src/graph/SharePointGraphClient.Codeunit.al @@ -209,6 +209,47 @@ codeunit 9119 "SharePoint Graph Client" exit(SharePointGraphClientImpl.CreateListItem(ListId, Title, GraphListItem)); end; + /// + /// Gets a single item from a SharePoint list. + /// + /// ID of the list. + /// ID of the item. + /// Record to store the result. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.Read.All + procedure GetListItem(ListId: Text; ItemId: Text; var GraphListItem: Record "SharePoint Graph List Item" temporary): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.GetListItem(ListId, ItemId, GraphListItem)); + end; + + /// + /// Gets a single item from a SharePoint list. + /// + /// ID of the list. + /// ID of the item. + /// Record to store the result. + /// A wrapper for optional header and query parameters. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.Read.All + procedure GetListItem(ListId: Text; ItemId: Text; var GraphListItem: Record "SharePoint Graph List Item" temporary; GraphOptionalParameters: Codeunit "Graph Optional Parameters"): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.GetListItem(ListId, ItemId, GraphListItem, GraphOptionalParameters)); + end; + + /// + /// Updates an existing list item's fields. + /// + /// ID of the list. + /// ID of the item to update. + /// JSON object containing the fields to update. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.ReadWrite.All + procedure UpdateListItem(ListId: Text; ItemId: Text; FieldsJsonObject: JsonObject; var GraphListItem: Record "SharePoint Graph List Item" temporary): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.UpdateListItem(ListId, ItemId, FieldsJsonObject, GraphListItem)); + end; + #endregion #region Drive and Items @@ -730,6 +771,62 @@ codeunit 9119 "SharePoint Graph Client" #endregion + #region Update Item + + /// + /// Updates a drive item's properties (name, description, etc.) by ID. + /// + /// ID of the item to update. + /// JSON object containing the properties to update (e.g., name, description). + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.ReadWrite.All. + procedure UpdateDriveItem(ItemId: Text; UpdatePropertiesJsonObject: JsonObject; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.UpdateDriveItem(ItemId, UpdatePropertiesJsonObject, GraphDriveItem)); + end; + + /// + /// Updates a drive item's properties (name, description, etc.) by path. + /// + /// Path to the item (e.g., 'Documents/file.docx'). + /// JSON object containing the properties to update (e.g., name, description). + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.ReadWrite.All. + procedure UpdateDriveItemByPath(ItemPath: Text; UpdatePropertiesJsonObject: JsonObject; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.UpdateDriveItemByPath(ItemPath, UpdatePropertiesJsonObject, GraphDriveItem)); + end; + + /// + /// Renames a drive item by ID. + /// + /// ID of the item to rename. + /// New name for the item. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.ReadWrite.All. + procedure RenameDriveItem(ItemId: Text; NewName: Text; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.RenameDriveItem(ItemId, NewName, GraphDriveItem)); + end; + + /// + /// Renames a drive item by path. + /// + /// Path to the item (e.g., 'Documents/file.docx'). + /// New name for the item. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + /// Required Microsoft Graph permission: Sites.ReadWrite.All. + procedure RenameDriveItemByPath(ItemPath: Text; NewName: Text; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + begin + exit(SharePointGraphClientImpl.RenameDriveItemByPath(ItemPath, NewName, GraphDriveItem)); + end; + + #endregion + /// /// Creates an OData query to filter items in SharePoint /// diff --git a/src/System Application/App/SharePoint/src/graph/SharePointGraphClientImpl.Codeunit.al b/src/System Application/App/SharePoint/src/graph/SharePointGraphClientImpl.Codeunit.al index 3f392d432b..a05b54fea2 100644 --- a/src/System Application/App/SharePoint/src/graph/SharePointGraphClientImpl.Codeunit.al +++ b/src/System Application/App/SharePoint/src/graph/SharePointGraphClientImpl.Codeunit.al @@ -86,6 +86,14 @@ codeunit 9120 "SharePoint Graph Client Impl." InvalidTargetPathErr: Label 'Target path cannot be empty'; FailedToCopyItemErr: Label 'Failed to copy item: %1', Comment = '%1 = Error message'; FailedToMoveItemErr: Label 'Failed to move item: %1', Comment = '%1 = Error message'; + InvalidUpdateBodyErr: Label 'Update properties cannot be empty'; + FailedToUpdateDriveItemErr: Label 'Failed to update drive item: %1', Comment = '%1 = Error message'; + FailedToParseUpdatedDriveItemErr: Label 'Failed to parse updated drive item details from response'; + InvalidNewNameErr: Label 'New name cannot be empty'; + InvalidFieldsErr: Label 'Fields JSON object cannot be empty'; + FailedToRetrieveListItemErr: Label 'Failed to retrieve list item: %1', Comment = '%1 = Error message'; + FailedToParseListItemErr: Label 'Failed to parse list item details from response'; + FailedToUpdateListItemErr: Label 'Failed to update list item: %1', Comment = '%1 = Error message'; GraphSharePointCategoryLbl: Label 'AL Graph SharePoint', Locked = true; OperationSuccessTelemetryMsg: Label '%1 completed successfully.', Locked = true, Comment = '%1 = Operation name'; @@ -548,6 +556,125 @@ codeunit 9120 "SharePoint Graph Client Impl." exit(CreateListItem(ListId, FieldsJsonObject, GraphListItem)); end; + /// + /// Gets a single item from a SharePoint list. + /// + /// ID of the list. + /// ID of the item. + /// Record to store the result. + /// An operation response object containing the result of the operation. + procedure GetListItem(ListId: Text; ItemId: Text; var GraphListItem: Record "SharePoint Graph List Item" temporary): Codeunit "SharePoint Graph Response" + var + GraphOptionalParameters: Codeunit "Graph Optional Parameters"; + begin + exit(GetListItem(ListId, ItemId, GraphListItem, GraphOptionalParameters)); + end; + + /// + /// Gets a single item from a SharePoint list. + /// + /// ID of the list. + /// ID of the item. + /// Record to store the result. + /// A wrapper for optional header and query parameters. + /// An operation response object containing the result of the operation. + procedure GetListItem(ListId: Text; ItemId: Text; var GraphListItem: Record "SharePoint Graph List Item" temporary; GraphOptionalParameters: Codeunit "Graph Optional Parameters"): Codeunit "SharePoint Graph Response" + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + JsonResponse: JsonObject; + ErrorMessage: Text; + begin + EnsureInitialized(); + EnsureSiteId(); + + SharePointGraphResponse.SetRequestHelper(SharePointGraphRequestHelper); + + if ListId = '' then begin + SharePointGraphResponse.SetError(InvalidListIdErr); + Session.LogMessage('', InvalidListIdErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if ItemId = '' then begin + SharePointGraphResponse.SetError(InvalidItemIdErr); + Session.LogMessage('', InvalidItemIdErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if not SharePointGraphRequestHelper.Get(SharePointGraphUriBuilder.GetListItemByIdEndpoint(ListId, ItemId), JsonResponse, GraphOptionalParameters) then begin + ErrorMessage := StrSubstNo(FailedToRetrieveListItemErr, SharePointGraphRequestHelper.GetDiagnostics().GetResponseReasonPhrase()); + SharePointGraphResponse.SetError(ErrorMessage); + Session.LogMessage('', ErrorMessage, Verbosity::Error, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + GraphListItem.Init(); + GraphListItem.ListId := CopyStr(ListId, 1, MaxStrLen(GraphListItem.ListId)); + if not SharePointGraphParser.ParseListItemDetail(JsonResponse, GraphListItem) then begin + SharePointGraphResponse.SetError(FailedToParseListItemErr); + Session.LogMessage('', FailedToParseListItemErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + GraphListItem.Insert(); + + SharePointGraphResponse.SetSuccess(); + Session.LogMessage('', StrSubstNo(OperationSuccessTelemetryMsg, 'GetListItem'), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + /// + /// Updates an existing list item's fields. + /// + /// ID of the list. + /// ID of the item to update. + /// JSON object containing the fields to update. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + procedure UpdateListItem(ListId: Text; ItemId: Text; FieldsJsonObject: JsonObject; var GraphListItem: Record "SharePoint Graph List Item" temporary): Codeunit "SharePoint Graph Response" + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + JsonResponse: JsonObject; + ErrorMessage: Text; + begin + EnsureInitialized(); + EnsureSiteId(); + + SharePointGraphResponse.SetRequestHelper(SharePointGraphRequestHelper); + + if ListId = '' then begin + SharePointGraphResponse.SetError(InvalidListIdErr); + Session.LogMessage('', InvalidListIdErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if ItemId = '' then begin + SharePointGraphResponse.SetError(InvalidItemIdErr); + Session.LogMessage('', InvalidItemIdErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if FieldsJsonObject.Keys().Count() = 0 then begin + SharePointGraphResponse.SetError(InvalidFieldsErr); + Session.LogMessage('', InvalidFieldsErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if not SharePointGraphRequestHelper.Patch(SharePointGraphUriBuilder.GetUpdateListItemFieldsEndpoint(ListId, ItemId), FieldsJsonObject, JsonResponse) then begin + ErrorMessage := StrSubstNo(FailedToUpdateListItemErr, SharePointGraphRequestHelper.GetDiagnostics().GetResponseReasonPhrase()); + SharePointGraphResponse.SetError(ErrorMessage); + Session.LogMessage('', ErrorMessage, Verbosity::Error, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + SharePointGraphResponse := GetListItem(ListId, ItemId, GraphListItem); + if not SharePointGraphResponse.IsSuccessful() then + exit(SharePointGraphResponse); + + SharePointGraphResponse.SetSuccess(); + Session.LogMessage('', StrSubstNo(OperationSuccessTelemetryMsg, 'UpdateListItem'), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + #endregion #region Drive and Items @@ -2047,6 +2174,151 @@ codeunit 9120 "SharePoint Graph Client Impl." exit(MoveItem(TempGraphDriveItem.Id, TargetFolderId, NewName)); end; + /// + /// Updates a drive item's properties (name, description, etc.) by ID. + /// + /// ID of the item to update. + /// JSON object containing the properties to update. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + procedure UpdateDriveItem(ItemId: Text; UpdatePropertiesJsonObject: JsonObject; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + ResponseJson: JsonObject; + ErrorMessage: Text; + begin + EnsureInitialized(); + EnsureSiteId(); + EnsureDefaultDriveId(); + + SharePointGraphResponse.SetRequestHelper(SharePointGraphRequestHelper); + + if ItemId = '' then begin + SharePointGraphResponse.SetError(InvalidItemIdErr); + Session.LogMessage('', InvalidItemIdErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if UpdatePropertiesJsonObject.Keys().Count() = 0 then begin + SharePointGraphResponse.SetError(InvalidUpdateBodyErr); + Session.LogMessage('', InvalidUpdateBodyErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if not SharePointGraphRequestHelper.Patch(SharePointGraphUriBuilder.GetDriveItemByIdEndpoint(ItemId), UpdatePropertiesJsonObject, ResponseJson) then begin + ErrorMessage := StrSubstNo(FailedToUpdateDriveItemErr, SharePointGraphRequestHelper.GetDiagnostics().GetResponseReasonPhrase()); + SharePointGraphResponse.SetError(ErrorMessage); + Session.LogMessage('', ErrorMessage, Verbosity::Error, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + GraphDriveItem.Init(); + GraphDriveItem.DriveId := CopyStr(DefaultDriveId, 1, MaxStrLen(GraphDriveItem.DriveId)); + if not SharePointGraphParser.ParseDriveItemDetail(ResponseJson, GraphDriveItem) then begin + SharePointGraphResponse.SetError(FailedToParseUpdatedDriveItemErr); + Session.LogMessage('', FailedToParseUpdatedDriveItemErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + GraphDriveItem.Insert(); + + SharePointGraphResponse.SetSuccess(); + Session.LogMessage('', StrSubstNo(OperationSuccessTelemetryMsg, 'UpdateDriveItem'), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + /// + /// Updates a drive item's properties (name, description, etc.) by path. + /// + /// Path to the item (e.g., 'Documents/file.docx'). + /// JSON object containing the properties to update. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + procedure UpdateDriveItemByPath(ItemPath: Text; UpdatePropertiesJsonObject: JsonObject; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + var + TempGraphDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + EnsureInitialized(); + EnsureSiteId(); + EnsureDefaultDriveId(); + + SharePointGraphResponse.SetRequestHelper(SharePointGraphRequestHelper); + + if ItemPath = '' then begin + SharePointGraphResponse.SetError(InvalidItemPathErr); + Session.LogMessage('', InvalidItemPathErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + if UpdatePropertiesJsonObject.Keys().Count() = 0 then begin + SharePointGraphResponse.SetError(InvalidUpdateBodyErr); + Session.LogMessage('', InvalidUpdateBodyErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + SharePointGraphResponse := GetDriveItemByPath(ItemPath, TempGraphDriveItem); + if not SharePointGraphResponse.IsSuccessful() then + exit(SharePointGraphResponse); + + exit(UpdateDriveItem(TempGraphDriveItem.Id, UpdatePropertiesJsonObject, GraphDriveItem)); + end; + + /// + /// Renames a drive item by ID. + /// + /// ID of the item to rename. + /// New name for the item. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + procedure RenameDriveItem(ItemId: Text; NewName: Text; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + RequestJsonObj: JsonObject; + begin + EnsureInitialized(); + EnsureSiteId(); + EnsureDefaultDriveId(); + + SharePointGraphResponse.SetRequestHelper(SharePointGraphRequestHelper); + + if NewName = '' then begin + SharePointGraphResponse.SetError(InvalidNewNameErr); + Session.LogMessage('', InvalidNewNameErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + RequestJsonObj.Add('name', NewName); + exit(UpdateDriveItem(ItemId, RequestJsonObj, GraphDriveItem)); + end; + + /// + /// Renames a drive item by path. + /// + /// Path to the item (e.g., 'Documents/file.docx'). + /// New name for the item. + /// Record to store the updated item details. + /// An operation response object containing the result of the operation. + procedure RenameDriveItemByPath(ItemPath: Text; NewName: Text; var GraphDriveItem: Record "SharePoint Graph Drive Item" temporary): Codeunit "SharePoint Graph Response" + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + RequestJsonObj: JsonObject; + begin + EnsureInitialized(); + EnsureSiteId(); + EnsureDefaultDriveId(); + + SharePointGraphResponse.SetRequestHelper(SharePointGraphRequestHelper); + + if NewName = '' then begin + SharePointGraphResponse.SetError(InvalidNewNameErr); + Session.LogMessage('', InvalidNewNameErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GraphSharePointCategoryLbl); + exit(SharePointGraphResponse); + end; + + RequestJsonObj.Add('name', NewName); + exit(UpdateDriveItemByPath(ItemPath, RequestJsonObj, GraphDriveItem)); + end; + #endregion /// diff --git a/src/System Application/App/SharePoint/src/graph/helpers/SharePointGraphUriBuilder.Codeunit.al b/src/System Application/App/SharePoint/src/graph/helpers/SharePointGraphUriBuilder.Codeunit.al index d1b6395ee8..d10dbbbb1c 100644 --- a/src/System Application/App/SharePoint/src/graph/helpers/SharePointGraphUriBuilder.Codeunit.al +++ b/src/System Application/App/SharePoint/src/graph/helpers/SharePointGraphUriBuilder.Codeunit.al @@ -24,7 +24,9 @@ codeunit 9121 "SharePoint Graph Uri Builder" ListsLbl: Label '/sites/%1/lists', Locked = true; ListByIdLbl: Label '/sites/%1/lists/%2', Locked = true; ListItemsLbl: Label '/sites/%1/lists/%2/items', Locked = true; + ListItemByIdLbl: Label '/sites/%1/lists/%2/items/%3', Locked = true; CreateListItemLbl: Label '/sites/%1/lists/%2/items', Locked = true; + UpdateListItemFieldsLbl: Label '/sites/%1/lists/%2/items/%3/fields', Locked = true; SiteByHostAndPathLbl: Label '/sites/%1:%2', Locked = true; DriveLbl: Label '/sites/%1/drive', Locked = true; DrivesLbl: Label '/sites/%1/drives', Locked = true; @@ -113,6 +115,28 @@ codeunit 9121 "SharePoint Graph Uri Builder" exit(StrSubstNo(CreateListItemLbl, SiteId, EscapeDataString(ListId))); end; + /// + /// Gets the endpoint for getting a single list item by ID. + /// + /// The list ID. + /// The item ID. + /// The endpoint. + procedure GetListItemByIdEndpoint(ListId: Text; ItemId: Text): Text + begin + exit(StrSubstNo(ListItemByIdLbl, SiteId, EscapeDataString(ListId), EscapeDataString(ItemId))); + end; + + /// + /// Gets the endpoint for updating list item fields. + /// + /// The list ID. + /// The item ID. + /// The endpoint. + procedure GetUpdateListItemFieldsEndpoint(ListId: Text; ItemId: Text): Text + begin + exit(StrSubstNo(UpdateListItemFieldsLbl, SiteId, EscapeDataString(ListId), EscapeDataString(ItemId))); + end; + /// /// Gets the endpoint for getting the default drive. /// diff --git a/src/System Application/Test Library/SharePoint/src/graph/SharePointGraphTestLibrary.Codeunit.al b/src/System Application/Test Library/SharePoint/src/graph/SharePointGraphTestLibrary.Codeunit.al index 07b1c5b4c9..d33f90d748 100644 --- a/src/System Application/Test Library/SharePoint/src/graph/SharePointGraphTestLibrary.Codeunit.al +++ b/src/System Application/Test Library/SharePoint/src/graph/SharePointGraphTestLibrary.Codeunit.al @@ -30,6 +30,31 @@ codeunit 132975 "SharePoint Graph Test Library" MockHttpClientHandler.ExpectSendToFailWithError(ErrorText); end; + procedure AddMockResponse(StatusCode: Integer; ResponseBody: Text) + begin + MockHttpClientHandler.AddResponse(StatusCode, ResponseBody); + end; + + procedure AddMockResponse(var NewHttpResponseMessage: Codeunit "Http Response Message") + begin + MockHttpClientHandler.AddResponse(NewHttpResponseMessage); + end; + + procedure GetMockHttpRequestUri(Index: Integer): Text + begin + exit(MockHttpClientHandler.GetHttpRequestUri(Index)); + end; + + procedure GetMockHttpRequestMethod(Index: Integer): Text + begin + exit(MockHttpClientHandler.GetHttpRequestMethod(Index)); + end; + + procedure GetMockRequestCount(): Integer + begin + exit(MockHttpClientHandler.GetRequestCount()); + end; + procedure ResetMockHandler() begin Clear(this.MockHttpClientHandler); diff --git a/src/System Application/Test Library/SharePoint/src/graph/SharePointHttpClientHandler.Codeunit.al b/src/System Application/Test Library/SharePoint/src/graph/SharePointHttpClientHandler.Codeunit.al index 4389d9dd66..7b5334339a 100644 --- a/src/System Application/Test Library/SharePoint/src/graph/SharePointHttpClientHandler.Codeunit.al +++ b/src/System Application/Test Library/SharePoint/src/graph/SharePointHttpClientHandler.Codeunit.al @@ -13,9 +13,14 @@ codeunit 132981 "SharePoint Http Client Handler" implements "Http Client Handler InherentPermissions = X; var - HttpRequestMessage: Codeunit "Http Request Message"; - HttpResponseMessage: Codeunit "Http Response Message"; - ResponseMessageSet: Boolean; + LastHttpRequestMessage: Codeunit "Http Request Message"; + SingleHttpResponseMessage: Codeunit "Http Response Message"; + HttpRequestUris: List of [Text]; + HttpRequestMethods: List of [Text]; + QueuedStatusCodes: List of [Integer]; + QueuedBodies: List of [Text]; + CurrentResponseIndex: Integer; + SingleResponseSet: Boolean; SendError: Text; procedure Send(HttpClient: HttpClient; InHttpRequestMessage: Codeunit "Http Request Message"; var OutHttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; @@ -31,23 +36,83 @@ codeunit 132981 "SharePoint Http Client Handler" implements "Http Client Handler procedure SetResponse(var NewHttpResponseMessage: Codeunit "Http Response Message") begin - this.HttpResponseMessage := NewHttpResponseMessage; - this.ResponseMessageSet := true; + Clear(this.QueuedStatusCodes); + Clear(this.QueuedBodies); + this.CurrentResponseIndex := 0; + Clear(this.HttpRequestUris); + Clear(this.HttpRequestMethods); + this.SingleHttpResponseMessage := NewHttpResponseMessage; + this.SingleResponseSet := true; end; procedure GetHttpRequestMessage(var OutHttpRequestMessage: Codeunit "Http Request Message") begin - OutHttpRequestMessage := this.HttpRequestMessage; + OutHttpRequestMessage := this.LastHttpRequestMessage; + end; + + procedure AddResponse(StatusCode: Integer; ResponseBody: Text) + begin + this.QueuedStatusCodes.Add(StatusCode); + this.QueuedBodies.Add(ResponseBody); + end; + + procedure AddResponse(var NewHttpResponseMessage: Codeunit "Http Response Message") + var + ResponseBody: Text; + begin + ResponseBody := NewHttpResponseMessage.GetContent().AsText(); + AddResponse(NewHttpResponseMessage.GetHttpStatusCode(), ResponseBody); + end; + + procedure GetHttpRequestUri(Index: Integer): Text + begin + if (Index > 0) and (Index <= this.HttpRequestUris.Count()) then + exit(this.HttpRequestUris.Get(Index)); + end; + + procedure GetHttpRequestMethod(Index: Integer): Text + begin + if (Index > 0) and (Index <= this.HttpRequestMethods.Count()) then + exit(this.HttpRequestMethods.Get(Index)); + end; + + procedure GetRequestCount(): Integer + begin + exit(this.HttpRequestUris.Count()); + end; + + procedure Reset() + begin + Clear(this.HttpRequestUris); + Clear(this.HttpRequestMethods); + Clear(this.QueuedStatusCodes); + Clear(this.QueuedBodies); + this.CurrentResponseIndex := 0; + this.SingleResponseSet := false; + this.SendError := ''; end; [TryFunction] local procedure TrySend(InHttpRequestMessage: Codeunit "Http Request Message"; var OutHttpResponseMessage: Codeunit "Http Response Message") + var + HttpContent: Codeunit "Http Content"; begin - this.HttpRequestMessage := InHttpRequestMessage; - if SendError <> '' then - Error(SendError); + this.LastHttpRequestMessage := InHttpRequestMessage; + this.HttpRequestUris.Add(InHttpRequestMessage.GetRequestUri()); + this.HttpRequestMethods.Add(InHttpRequestMessage.GetHttpMethod()); + + if this.SendError <> '' then + Error(this.SendError); - if ResponseMessageSet then - OutHttpResponseMessage := this.HttpResponseMessage; + if (this.QueuedBodies.Count() > 0) and (this.CurrentResponseIndex < this.QueuedBodies.Count()) then begin + this.CurrentResponseIndex += 1; + OutHttpResponseMessage.SetHttpStatusCode(this.QueuedStatusCodes.Get(this.CurrentResponseIndex)); + HttpContent := HttpContent.Create(this.QueuedBodies.Get(this.CurrentResponseIndex)); + OutHttpResponseMessage.SetContent(HttpContent); + end else + if this.SingleResponseSet then + OutHttpResponseMessage := this.SingleHttpResponseMessage + else + Error('No mock response queued. Request count: %1, queue size: %2', this.HttpRequestUris.Count(), this.QueuedBodies.Count()); end; } \ No newline at end of file diff --git a/src/System Application/Test/DisabledTests/SharePointGraphAdvancedTest.json b/src/System Application/Test/DisabledTests/SharePointGraphAdvancedTest.json deleted file mode 100644 index 63ce5b1732..0000000000 --- a/src/System Application/Test/DisabledTests/SharePointGraphAdvancedTest.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "codeunitId": 132985, - "CodeunitName": "SharePoint Graph Advanced Test", - "Method": "TestCopyItemByPath" - } -] \ No newline at end of file diff --git a/src/System Application/Test/SharePoint/src/graph/SharePointGraphAdvancedTest.Codeunit.al b/src/System Application/Test/SharePoint/src/graph/SharePointGraphAdvancedTest.Codeunit.al index e8f3581e29..27900427fa 100644 --- a/src/System Application/Test/SharePoint/src/graph/SharePointGraphAdvancedTest.Codeunit.al +++ b/src/System Application/Test/SharePoint/src/graph/SharePointGraphAdvancedTest.Codeunit.al @@ -611,17 +611,13 @@ codeunit 132985 "SharePoint Graph Advanced Test" [Test] procedure TestCopyItemByPath() var - HttpContent: Codeunit "Http Content"; - MockHttpContent: Codeunit "Http Content"; - MockHttpResponseMessage: Codeunit "Http Response Message"; SharePointGraphResponse: Codeunit "SharePoint Graph Response"; begin - // [GIVEN] Mock response for CopyItemByPath - Initialize(); - MockHttpResponseMessage.SetHttpStatusCode(202); - MockHttpContent := HttpContent.Create(''); - MockHttpResponseMessage.SetContent(MockHttpContent); - SharePointGraphTestLibrary.SetMockResponse(MockHttpResponseMessage); + // [GIVEN] Multi-response handler: GET (resolve target folder) + GET (resolve source item) + POST (copy) + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(202, ''); // [WHEN] Calling CopyItemByPath SharePointGraphResponse := SharePointGraphClient.CopyItemByPath('Documents/Original.txt', 'Documents/Archive', 'CopiedFile.txt'); @@ -674,6 +670,298 @@ codeunit 132985 "SharePoint Graph Advanced Test" LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'MoveItemByPath should succeed'); end; + [Test] + procedure TestCopyItemByPath_MultiResponse() + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Multi-response handler with queued responses for CopyItemByPath (resolve target folder, resolve source item, POST copy) + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(202, ''); + + // [WHEN] Calling CopyItemByPath + SharePointGraphResponse := SharePointGraphClient.CopyItemByPath('Documents/Original.txt', 'Documents/Archive', 'CopiedFile.txt'); + + // [THEN] Operation should succeed with 3 HTTP requests (resolve target, resolve source, POST copy) + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'CopyItemByPath should succeed'); + LibraryAssert.AreEqual(3, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 3 HTTP requests'); + LibraryAssert.AreEqual('GET', SharePointGraphTestLibrary.GetMockHttpRequestMethod(1), 'First request should be GET (resolve target folder)'); + LibraryAssert.AreEqual('GET', SharePointGraphTestLibrary.GetMockHttpRequestMethod(2), 'Second request should be GET (resolve source item)'); + LibraryAssert.AreEqual('POST', SharePointGraphTestLibrary.GetMockHttpRequestMethod(3), 'Third request should be POST (copy action)'); + end; + + [Test] + procedure TestMoveItemByPath_MultiResponse() + var + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Multi-response handler with queued responses for MoveItemByPath (resolve source item, PATCH move) + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + + // [WHEN] Calling MoveItemByPath + SharePointGraphResponse := SharePointGraphClient.MoveItemByPath('Documents/Original.txt', 'Documents/Archive', 'MovedFile.txt'); + + // [THEN] Operation should succeed; final call should be PATCH + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'MoveItemByPath should succeed'); + LibraryAssert.IsTrue(SharePointGraphTestLibrary.GetMockRequestCount() >= 2, 'Should have made at least 2 HTTP requests'); + LibraryAssert.AreEqual('PATCH', SharePointGraphTestLibrary.GetMockHttpRequestMethod(SharePointGraphTestLibrary.GetMockRequestCount()), 'Last request should be PATCH (move action)'); + end; + + [Test] + procedure TestQueueExhausted() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Multi-response handler with one response queued + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + + // [WHEN] First call consumes the only queued response, second call has no response + SharePointGraphResponse := SharePointGraphClient.GetDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', TempDriveItem); + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'First call should succeed'); + + asserterror SharePointGraphClient.GetDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', TempDriveItem); + + // [THEN] Second call should fail because the queue is exhausted + LibraryAssert.ExpectedError('could not be established'); + end; + + [Test] + procedure TestStickyModeStillWorks() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + HttpContent: Codeunit "Http Content"; + MockHttpContent: Codeunit "Http Content"; + MockHttpResponseMessage: Codeunit "Http Response Message"; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Sticky (single) response set via SetMockResponse — original API with valid driveItem JSON + InitializeMultiResponse(); + MockHttpResponseMessage.SetHttpStatusCode(200); + MockHttpContent := HttpContent.Create(GetDriveItemResponse()); + MockHttpResponseMessage.SetContent(MockHttpContent); + SharePointGraphTestLibrary.SetMockResponse(MockHttpResponseMessage); + + // [WHEN] Making two separate calls — sticky mode returns same response for both + SharePointGraphResponse := SharePointGraphClient.GetDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', TempDriveItem); + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'First call should succeed in sticky mode'); + + TempDriveItem.DeleteAll(); + SharePointGraphResponse := SharePointGraphClient.GetDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', TempDriveItem); + + // [THEN] Both calls succeed — sticky mode returns the same response on every Send + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'Second call should also succeed in sticky mode'); + LibraryAssert.AreEqual(2, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 2 HTTP requests'); + end; + + [Test] + procedure TestUpdateDriveItem() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + UpdateProperties: JsonObject; + begin + // [GIVEN] Mock response for UpdateDriveItem + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetUpdatedDriveItemResponse()); + + // [WHEN] Calling UpdateDriveItem with name and description + UpdateProperties.Add('name', 'RenamedReport.docx'); + UpdateProperties.Add('description', 'Updated description'); + SharePointGraphResponse := SharePointGraphClient.UpdateDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', UpdateProperties, TempDriveItem); + + // [THEN] Operation should succeed and return updated item + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'UpdateDriveItem should succeed'); + LibraryAssert.AreEqual(1, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 1 HTTP request'); + LibraryAssert.IsTrue(SharePointGraphTestLibrary.GetMockHttpRequestUri(1).Contains('/drive/items/01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ'), 'Request URI should target the correct item'); + LibraryAssert.AreEqual('PATCH', SharePointGraphTestLibrary.GetMockHttpRequestMethod(1), 'Request method should be PATCH'); + LibraryAssert.AreEqual('RenamedReport.docx', TempDriveItem.Name, 'Item name should be updated'); + end; + + [Test] + procedure TestRenameDriveItem() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Mock response for RenameDriveItem + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetUpdatedDriveItemResponse()); + + // [WHEN] Calling RenameDriveItem + SharePointGraphResponse := SharePointGraphClient.RenameDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', 'RenamedReport.docx', TempDriveItem); + + // [THEN] Operation should succeed and request should be PATCH with name in body + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'RenameDriveItem should succeed'); + LibraryAssert.AreEqual(1, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 1 HTTP request'); + LibraryAssert.IsTrue(SharePointGraphTestLibrary.GetMockHttpRequestUri(1).Contains('/drive/items/01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ'), 'Request URI should target the correct item'); + LibraryAssert.AreEqual('PATCH', SharePointGraphTestLibrary.GetMockHttpRequestMethod(1), 'Request method should be PATCH'); + LibraryAssert.AreEqual('RenamedReport.docx', TempDriveItem.Name, 'Item name should be updated'); + end; + + [Test] + procedure TestRenameDriveItem_EmptyNewName() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Initialized client + InitializeMultiResponse(); + + // [WHEN] Calling RenameDriveItem with empty name + SharePointGraphResponse := SharePointGraphClient.RenameDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', '', TempDriveItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'RenameDriveItem should fail with empty name'); + end; + + [Test] + procedure TestUpdateDriveItemByPath() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + UpdateProperties: JsonObject; + begin + // [GIVEN] Multi-response handler: GET (resolve path) + PATCH (update) + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetUpdatedDriveItemResponse()); + + // [WHEN] Calling UpdateDriveItemByPath + UpdateProperties.Add('name', 'RenamedReport.docx'); + SharePointGraphResponse := SharePointGraphClient.UpdateDriveItemByPath('Documents/Report.docx', UpdateProperties, TempDriveItem); + + // [THEN] Operation should succeed with 2 HTTP requests (GET resolve + PATCH update) + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'UpdateDriveItemByPath should succeed'); + LibraryAssert.AreEqual(2, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 2 HTTP requests'); + LibraryAssert.AreEqual('GET', SharePointGraphTestLibrary.GetMockHttpRequestMethod(1), 'First request should be GET (resolve path)'); + LibraryAssert.AreEqual('PATCH', SharePointGraphTestLibrary.GetMockHttpRequestMethod(2), 'Second request should be PATCH (update)'); + LibraryAssert.AreEqual('RenamedReport.docx', TempDriveItem.Name, 'Item name should be updated'); + end; + + [Test] + procedure TestRenameDriveItemByPath() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Multi-response handler: GET (resolve path) + PATCH (rename) + InitializeMultiResponse(); + SharePointGraphTestLibrary.AddMockResponse(200, GetDriveItemResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetUpdatedDriveItemResponse()); + + // [WHEN] Calling RenameDriveItemByPath + SharePointGraphResponse := SharePointGraphClient.RenameDriveItemByPath('Documents/Report.docx', 'RenamedReport.docx', TempDriveItem); + + // [THEN] Operation should succeed with 2 HTTP requests; final should be PATCH + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'RenameDriveItemByPath should succeed'); + LibraryAssert.AreEqual(2, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 2 HTTP requests'); + LibraryAssert.AreEqual('PATCH', SharePointGraphTestLibrary.GetMockHttpRequestMethod(2), 'Second request should be PATCH (rename)'); + LibraryAssert.AreEqual('RenamedReport.docx', TempDriveItem.Name, 'Item name should be updated'); + end; + + [Test] + procedure TestUpdateDriveItem_EmptyItemId() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + UpdateProperties: JsonObject; + begin + // [GIVEN] Initialized client + InitializeMultiResponse(); + + // [WHEN] Calling UpdateDriveItem with empty item ID + UpdateProperties.Add('name', 'NewName.docx'); + SharePointGraphResponse := SharePointGraphClient.UpdateDriveItem('', UpdateProperties, TempDriveItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateDriveItem should fail with empty item ID'); + end; + + [Test] + procedure TestUpdateDriveItem_EmptyBody() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + UpdateProperties: JsonObject; + begin + // [GIVEN] Initialized client + InitializeMultiResponse(); + + // [WHEN] Calling UpdateDriveItem with empty properties + SharePointGraphResponse := SharePointGraphClient.UpdateDriveItem('01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ', UpdateProperties, TempDriveItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateDriveItem should fail with empty body'); + end; + + [Test] + procedure TestUpdateDriveItemByPath_EmptyPath() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + UpdateProperties: JsonObject; + begin + // [GIVEN] Initialized client + InitializeMultiResponse(); + + // [WHEN] Calling UpdateDriveItemByPath with empty path + UpdateProperties.Add('name', 'NewName.docx'); + SharePointGraphResponse := SharePointGraphClient.UpdateDriveItemByPath('', UpdateProperties, TempDriveItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateDriveItemByPath should fail with empty path'); + end; + + [Test] + procedure TestRenameDriveItemByPath_EmptyPath() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Initialized client + InitializeMultiResponse(); + + // [WHEN] Calling RenameDriveItemByPath with empty path + SharePointGraphResponse := SharePointGraphClient.RenameDriveItemByPath('', 'NewName.docx', TempDriveItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'RenameDriveItemByPath should fail with empty path'); + end; + + [Test] + procedure TestRenameDriveItemByPath_EmptyName() + var + TempDriveItem: Record "SharePoint Graph Drive Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Initialized client + InitializeMultiResponse(); + + // [WHEN] Calling RenameDriveItemByPath with empty name + SharePointGraphResponse := SharePointGraphClient.RenameDriveItemByPath('Documents/Report.docx', '', TempDriveItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'RenameDriveItemByPath should fail with empty name'); + end; + + local procedure InitializeMultiResponse() + var + MockHttpClientHandler: Interface "Http Client Handler"; + begin + SharePointGraphTestLibrary.ResetMockHandler(); + MockHttpClientHandler := SharePointGraphTestLibrary.GetMockHandler(); + SharePointGraphClient.Initialize(SharePointUrlLbl, Enum::"Graph API Version"::"v1.0", SharePointGraphAuthMock, MockHttpClientHandler); + SharePointGraphClient.SetSiteIdForTesting('contoso.sharepoint.com,e6991d99-75d5-4be4-4ede-2c82b1d40cd6,1b58abad-4105-4125-a0e0-7a6d39571a5b'); + SharePointGraphClient.SetDefaultDriveIdForTesting('b!mR2-5tV1S-RO3C82s1DNbdCrWBwFQKFUoOB6bTlXClvD9fcjLXO5TbNk5sDyD7c8'); + end; + local procedure Initialize() var MockHttpClientHandler: Interface "Http Client Handler"; @@ -964,4 +1252,27 @@ codeunit 132985 "SharePoint Graph Advanced Test" ResponseText.Append('}'); exit(ResponseText.ToText()); end; + + local procedure GetUpdatedDriveItemResponse(): Text + var + ResponseText: TextBuilder; + begin + ResponseText.Append('{'); + ResponseText.Append(' "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#driveItems/$entity",'); + ResponseText.Append(' "id": "01EZJNRYQYENJ6SXVPCNBYA3QZRHKJWLNZ",'); + ResponseText.Append(' "name": "RenamedReport.docx",'); + ResponseText.Append(' "description": "Updated description",'); + ResponseText.Append(' "createdDateTime": "2023-05-10T14:25:37Z",'); + ResponseText.Append(' "lastModifiedDateTime": "2023-08-01T11:15:42Z",'); + ResponseText.Append(' "webUrl": "https://contoso.sharepoint.com/sites/test/Shared%20Documents/RenamedReport.docx",'); + ResponseText.Append(' "file": {'); + ResponseText.Append(' "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",'); + ResponseText.Append(' "hashes": {'); + ResponseText.Append(' "quickXorHash": "dF5GC7lcTJbHDrcPKJc8rJtEhCo="'); + ResponseText.Append(' }'); + ResponseText.Append(' },'); + ResponseText.Append(' "size": 45321'); + ResponseText.Append('}'); + exit(ResponseText.ToText()); + end; } \ No newline at end of file diff --git a/src/System Application/Test/SharePoint/src/graph/SharePointGraphClientTest.Codeunit.al b/src/System Application/Test/SharePoint/src/graph/SharePointGraphClientTest.Codeunit.al index 12aa89b04c..93f90a34f8 100644 --- a/src/System Application/Test/SharePoint/src/graph/SharePointGraphClientTest.Codeunit.al +++ b/src/System Application/Test/SharePoint/src/graph/SharePointGraphClientTest.Codeunit.al @@ -186,6 +186,167 @@ codeunit 132984 "SharePoint Graph Client Test" LibraryAssert.AreEqual('3', TempListItem.Id, 'Id should match'); end; + [Test] + procedure TestGetListItem() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + HttpContent: Codeunit "Http Content"; + MockHttpContent: Codeunit "Http Content"; + MockHttpResponseMessage: Codeunit "Http Response Message"; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] Mock response for GetListItem + Initialize(); + MockHttpResponseMessage.SetHttpStatusCode(200); + MockHttpContent := HttpContent.Create(GetListItemResponse()); + MockHttpResponseMessage.SetContent(MockHttpContent); + SharePointGraphTestLibrary.SetMockResponse(MockHttpResponseMessage); + + // [WHEN] Calling GetListItem + SharePointGraphResponse := SharePointGraphClient.GetListItem('01bjtwww-5j35-426b-a4d5-608f6e2a9f84', '1', TempListItem); + + // [THEN] Operation should succeed and return correct data + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'GetListItem should succeed'); + LibraryAssert.AreEqual('1', TempListItem.Id, 'Id should match'); + LibraryAssert.AreEqual('Test Item 1', TempListItem.Title, 'Title should match'); + LibraryAssert.AreEqual('Item', TempListItem.ContentType, 'ContentType should match'); + end; + + [Test] + procedure TestGetListItem_EmptyListId() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] An initialized client + Initialize(); + + // [WHEN] Calling GetListItem with empty ListId + SharePointGraphResponse := SharePointGraphClient.GetListItem('', '1', TempListItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'GetListItem should fail with empty ListId'); + end; + + [Test] + procedure TestGetListItem_EmptyItemId() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + begin + // [GIVEN] An initialized client + Initialize(); + + // [WHEN] Calling GetListItem with empty ItemId + SharePointGraphResponse := SharePointGraphClient.GetListItem('01bjtwww-5j35-426b-a4d5-608f6e2a9f84', '', TempListItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'GetListItem should fail with empty ItemId'); + end; + + [Test] + procedure TestUpdateListItem() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + FieldsJson: JsonObject; + begin + // [GIVEN] Mock responses for UpdateListItem (PATCH fields + follow-up GET) + Initialize(); + SharePointGraphTestLibrary.ResetMockHandler(); + SharePointGraphTestLibrary.AddMockResponse(200, GetUpdateListItemFieldsResponse()); + SharePointGraphTestLibrary.AddMockResponse(200, GetListItemResponse()); + + // [WHEN] Calling UpdateListItem + FieldsJson.Add('Title', 'Updated Title'); + FieldsJson.Add('Status', 'Approved'); + SharePointGraphResponse := SharePointGraphClient.UpdateListItem('01bjtwww-5j35-426b-a4d5-608f6e2a9f84', '1', FieldsJson, TempListItem); + + // [THEN] Operation should succeed and return the full item from the follow-up GET + LibraryAssert.IsTrue(SharePointGraphResponse.IsSuccessful(), 'UpdateListItem should succeed'); + LibraryAssert.AreEqual('1', TempListItem.Id, 'Id should match'); + LibraryAssert.AreEqual('Test Item 1', TempListItem.Title, 'Title should match from GET response'); + LibraryAssert.AreEqual(2, SharePointGraphTestLibrary.GetMockRequestCount(), 'Should have made 2 requests (PATCH + GET)'); + LibraryAssert.IsTrue(SharePointGraphTestLibrary.GetMockHttpRequestMethod(1).Contains('PATCH'), 'First request should be PATCH'); + LibraryAssert.IsTrue(SharePointGraphTestLibrary.GetMockHttpRequestMethod(2).Contains('GET'), 'Second request should be GET'); + LibraryAssert.IsTrue(SharePointGraphTestLibrary.GetMockHttpRequestUri(1).Contains('/fields'), 'First request URI should target /fields'); + end; + + [Test] + procedure TestUpdateListItem_EmptyListId() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + FieldsJson: JsonObject; + begin + // [GIVEN] An initialized client + Initialize(); + FieldsJson.Add('Title', 'Updated'); + + // [WHEN] Calling UpdateListItem with empty ListId + SharePointGraphResponse := SharePointGraphClient.UpdateListItem('', '1', FieldsJson, TempListItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateListItem should fail with empty ListId'); + end; + + [Test] + procedure TestUpdateListItem_EmptyItemId() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + FieldsJson: JsonObject; + begin + // [GIVEN] An initialized client + Initialize(); + FieldsJson.Add('Title', 'Updated'); + + // [WHEN] Calling UpdateListItem with empty ItemId + SharePointGraphResponse := SharePointGraphClient.UpdateListItem('01bjtwww-5j35-426b-a4d5-608f6e2a9f84', '', FieldsJson, TempListItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateListItem should fail with empty ItemId'); + end; + + [Test] + procedure TestUpdateListItem_EmptyFields() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + FieldsJson: JsonObject; + begin + // [GIVEN] An initialized client with empty fields JSON + Initialize(); + + // [WHEN] Calling UpdateListItem with empty FieldsJsonObject + SharePointGraphResponse := SharePointGraphClient.UpdateListItem('01bjtwww-5j35-426b-a4d5-608f6e2a9f84', '1', FieldsJson, TempListItem); + + // [THEN] Operation should fail with validation error + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateListItem should fail with empty fields'); + end; + + [Test] + procedure TestUpdateListItem_FollowUpGetFails() + var + TempListItem: Record "SharePoint Graph List Item" temporary; + SharePointGraphResponse: Codeunit "SharePoint Graph Response"; + FieldsJson: JsonObject; + begin + // [GIVEN] Mock responses: successful PATCH, then a 404 on the follow-up GET + Initialize(); + SharePointGraphTestLibrary.ResetMockHandler(); + SharePointGraphTestLibrary.AddMockResponse(200, GetUpdateListItemFieldsResponse()); + SharePointGraphTestLibrary.AddMockResponse(404, GetErrorResponse()); + + // [WHEN] Calling UpdateListItem + FieldsJson.Add('Title', 'Updated Title'); + SharePointGraphResponse := SharePointGraphClient.UpdateListItem('01bjtwww-5j35-426b-a4d5-608f6e2a9f84', '1', FieldsJson, TempListItem); + + // [THEN] Operation should fail (the GET failed), but both requests were made + LibraryAssert.IsFalse(SharePointGraphResponse.IsSuccessful(), 'UpdateListItem should fail when follow-up GET fails'); + LibraryAssert.AreEqual(2, SharePointGraphTestLibrary.GetMockRequestCount(), 'Both PATCH and GET requests should have fired'); + end; + [Test] procedure TestGetDrives() var @@ -736,6 +897,41 @@ codeunit 132984 "SharePoint Graph Client Test" exit(ResponseText.ToText()); end; + local procedure GetListItemResponse(): Text + var + ResponseText: TextBuilder; + begin + ResponseText.Append('{'); + ResponseText.Append(' "id": "1",'); + ResponseText.Append(' "contentType": {'); + ResponseText.Append(' "id": "0x0100",'); + ResponseText.Append(' "name": "Item"'); + ResponseText.Append(' },'); + ResponseText.Append(' "createdDateTime": "2023-05-15T08:12:39Z",'); + ResponseText.Append(' "lastModifiedDateTime": "2023-06-20T14:45:12Z",'); + ResponseText.Append(' "webUrl": "https://contoso.sharepoint.com/sites/test/Lists/Test%20List/1_.000",'); + ResponseText.Append(' "fields": {'); + ResponseText.Append(' "Title": "Test Item 1",'); + ResponseText.Append(' "Description": "This is a test item",'); + ResponseText.Append(' "Priority": "High"'); + ResponseText.Append(' }'); + ResponseText.Append('}'); + exit(ResponseText.ToText()); + end; + + local procedure GetUpdateListItemFieldsResponse(): Text + var + ResponseText: TextBuilder; + begin + ResponseText.Append('{'); + ResponseText.Append(' "Title": "Updated Title",'); + ResponseText.Append(' "Status": "Approved",'); + ResponseText.Append(' "Description": "This is a test item",'); + ResponseText.Append(' "Priority": "High"'); + ResponseText.Append('}'); + exit(ResponseText.ToText()); + end; + local procedure GetErrorResponse(): Text var ResponseText: TextBuilder;