diff --git a/src/System Application/App/Rest Client/app.json b/src/System Application/App/Rest Client/app.json index 4be03c0715..fe0060b51c 100644 --- a/src/System Application/App/Rest Client/app.json +++ b/src/System Application/App/Rest Client/app.json @@ -34,11 +34,10 @@ "idRanges": [ { "from": 2350, - "to": 2361 + "to": 2362 } ], "target": "OnPrem", - "runtime": "12.0", "resourceExposurePolicy": { "allowDebugging": true, "allowDownloadingSource": true, diff --git a/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al b/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al index 3cccb6fb90..cd73e7752e 100644 --- a/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al +++ b/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; codeunit 2361 "HttpAuthOAuthClientCredentials" implements "Http Authentication" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al index abf1c542c3..3ca85bf67e 100644 --- a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al +++ b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; /// Implementation of the "Http Authentication" interface for a anonymous request. codeunit 2358 "Http Authentication Anonymous" implements "Http Authentication" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al index 552d139504..f5499c06a3 100644 --- a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al +++ b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al @@ -9,6 +9,7 @@ using System; codeunit 2359 "Http Authentication Basic" implements "Http Authentication" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al new file mode 100644 index 0000000000..78fc1c25b7 --- /dev/null +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace System.RestClient; + +/// +/// This enum contains the exceptions of the Rest Client. +/// +enum 2351 "Rest Client Exception" +{ + /// + /// Specifies that the exception is an unknown exception. + /// + value(100; UnknownException) + { + Caption = 'Unknown Exception'; + } + /// + /// Specifies that the connection failed. + /// + value(101; ConnectionFailed) + { + Caption = 'Connection Failed'; + } + /// + /// Specifies that the request is blocked by the environment. + /// + value(102; BlockedByEnvironment) + { + Caption = 'Blocked By Environment'; + } + /// + /// Specifies that the request failed. + /// + value(103; RequestFailed) + { + Caption = 'Request Failed'; + } + /// + /// Specifies that the content is not valid JSON. + /// + value(201; InvalidJson) + { + Caption = 'Invalid Json'; + } + /// + /// Specifies that the content is not valid XML. + /// + value(202; InvalidXml) + { + Caption = 'Invalid Xml'; + } +} \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al new file mode 100644 index 0000000000..177845c2b2 --- /dev/null +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace System.RestClient; + +codeunit 2362 "Rest Client Exception Builder" +{ + Access = Public; + InherentEntitlements = X; + InherentPermissions = X; + + /// + /// Creates an exception with the specified error code and message. The exception is collectible if the errors are currently being collected. + /// + /// The error code for the exception. + /// The message for the exception. + /// The exception with the specified error code and message. + procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text) Exception: ErrorInfo + begin + Exception := CreateException(RestClientException, ErrorMessage, IsCollectingErrors()); + end; + + /// + /// Creates an exception with the specified error code, message, and collectible flag. + /// + /// The error code for the exception. + /// The message for the exception. + /// Whether the exception is collectible. + /// The exception with the specified error code, message, and collectible flag. + procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text; Collectible: Boolean) Exception: ErrorInfo + begin + Exception.Message := ErrorMessage; + Exception.CustomDimensions.Add('ExceptionCode', Format(RestClientException.AsInteger())); + Exception.CustomDimensions.Add('ExceptionName', RestClientException.Names.Get(RestClientException.Ordinals.IndexOf(RestClientException.AsInteger()))); + Exception.Collectible := Collectible; + end; + + /// + /// Gets the exception code from the error info. + /// + /// The error info of the exception. + /// The exception code. + procedure GetRestClientException(ErrInfo: ErrorInfo) RestClientException: Enum "Rest Client Exception" + var + ExceptionCode: Integer; + begin + Evaluate(ExceptionCode, ErrInfo.CustomDimensions.Get('ExceptionCode')); + RestClientException := Enum::"Rest Client Exception".FromInteger(ExceptionCode); + end; +} \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al index c7ab685cf7..41f24996e3 100644 --- a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al @@ -6,14 +6,15 @@ namespace System.RestClient; codeunit 2360 "Http Client Handler" implements "Http Client Handler" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; - procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; + procedure Send(CurrHttpClientInstance: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; var ResponseMessage: HttpResponseMessage; begin - Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); - HttpResponseMessage.SetResponseMessage(ResponseMessage); + Success := CurrHttpClientInstance.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); + HttpResponseMessage := HttpResponseMessage.Create(ResponseMessage); end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al index 849a4605ea..b4d492dfd2 100644 --- a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al +++ b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al @@ -6,5 +6,5 @@ namespace System.RestClient; interface "Http Client Handler" { - procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; + procedure Send(CurrHttpClientInstance: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al index 8295f767a9..8c1caf0592 100644 --- a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al @@ -9,6 +9,7 @@ using System.Utilities; /// Holder object for the Http Content data. codeunit 2354 "Http Content" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; @@ -20,18 +21,20 @@ codeunit 2354 "Http Content" /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'text/plain'. - procedure Create(Content: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: Text): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified SecretText content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'text/plain'. - procedure Create(Content: SecretText) HttpContent: Codeunit "Http Content" + procedure Create(Content: SecretText): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified Text content and content type. @@ -39,10 +42,10 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'text/plain'. - procedure Create(Content: Text; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: Text; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified SecretText content and content type. @@ -50,59 +53,60 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'text/plain'. - procedure Create(Content: SecretText; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: SecretText; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified JsonObject content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/json'. - procedure Create(Content: JsonObject) HttpContent: Codeunit "Http Content" + procedure Create(Content: JsonObject): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified JsonArray content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/json'. - procedure Create(Content: JsonArray) HttpContent: Codeunit "Http Content" + procedure Create(Content: JsonArray): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified JsonToken content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/json'. - procedure Create(Content: JsonToken) HttpContent: Codeunit "Http Content" + procedure Create(Content: JsonToken): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified XmlDocument content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'text/xml'. - procedure Create(Content: XmlDocument) HttpContent: Codeunit "Http Content" + procedure Create(Content: XmlDocument): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified "Temp Blob" Codeunit content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/octet-stream'. - procedure Create(Content: Codeunit "Temp Blob") HttpContent: Codeunit "Http Content" + procedure Create(Content: Codeunit "Temp Blob"): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified "Temp Blob" Codeunit content and content type. @@ -110,19 +114,20 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'application/octet-stream'. - procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified InStream content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/octet-stream'. - procedure Create(Content: InStream) HttpContent: Codeunit "Http Content" + procedure Create(Content: InStream): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified InStream content and content type. @@ -130,20 +135,20 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'application/octet-stream'. - procedure Create(Content: InStream; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: InStream; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content object with the specified HttpContent object. /// The HttpContent object. /// The HttpContent object. /// The HttpContent must be properly prepared including the Content-Type header. - procedure Create(Content: HttpContent) HttpContent: Codeunit "Http Content" + procedure Create(Content: HttpContent): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; #endregion @@ -232,57 +237,23 @@ codeunit 2354 "Http Content" begin JsonToken := HttpContentImpl.AsJson(); end; - #endregion - - #region Internal Methods - internal procedure SetContent(Content: Text; ContentType: Text) - begin - HttpContentImpl.SetContent(Content, ContentType); - end; - - internal procedure SetContent(Content: SecretText; ContentType: Text) - begin - HttpContentImpl.SetContent(Content, ContentType); - end; - internal procedure SetContent(Content: InStream; ContentType: Text) - begin - HttpContentImpl.SetContent(Content, ContentType); - end; - - internal procedure SetContent(TempBlob: Codeunit "Temp Blob"; ContentType: Text) - begin - HttpContentImpl.SetContent(TempBlob, ContentType); - end; - - internal procedure SetContent(Content: XmlDocument) - begin - HttpContentImpl.SetContent(Content); - end; - - internal procedure SetContent(Content: JsonObject) - begin - SetContent(Content.AsToken()); - end; - - internal procedure SetContent(Content: JsonArray) - begin - SetContent(Content.AsToken()); - end; - - internal procedure SetContent(Content: JsonToken) - begin - HttpContentImpl.SetContent(Content); - end; - - internal procedure SetContent(var Value: HttpContent) + /// Gets the content of the HTTP response message as a JsonObject. + /// The content of the HTTP response message as a JsonObject. + /// Returns an empty JsonObject in case there is no content. + /// Fails in case the content is not a valid JSON document. + procedure AsJsonObject() JsonObject: JsonObject begin - HttpContentImpl.SetContent(Value); + JsonObject := HttpContentImpl.AsJsonObject(); end; - internal procedure SetHttpContentImpl(Value: Codeunit "Http Content Impl.") + /// Gets the content of the HTTP response message as a JsonArray. + /// The content of the HTTP response message as a JsonArray. + /// Returns an empty JsonArray in case there is no content. + /// Fails in case the content is not a valid JSON document. + procedure AsJsonArray() JsonArray: JsonArray begin - HttpContentImpl := Value; + JsonArray := HttpContentImpl.AsJsonArray(); end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index b384aa28ed..8b1488e5c5 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -8,19 +8,103 @@ using System.Utilities; codeunit 2355 "Http Content Impl." { + Access = Internal; InherentEntitlements = X; InherentPermissions = X; - Access = Internal; - var - HttpContent: HttpContent; + CurrHttpContentInstance: HttpContent; ContentTypeEmptyErr: Label 'The value of the Content-Type header must be specified.'; MimeTypeTextPlainTxt: Label 'text/plain', Locked = true; MimeTypeTextXmlTxt: Label 'text/xml', Locked = true; MimeTypeApplicationOctetStreamTxt: Label 'application/octet-stream', Locked = true; MimeTypeApplicationJsonTxt: Label 'application/json', Locked = true; + #region Constructors + procedure Create(Content: Text) HttpContentImpl: Codeunit "Http Content Impl." + begin + HttpContentImpl := Create(Content, ''); + end; + + procedure Create(Content: SecretText) HttpContentImpl: Codeunit "Http Content Impl." + begin + HttpContentImpl := Create(Content, ''); + end; + + procedure Create(Content: Text; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: SecretText; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: JsonObject): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content.AsToken()); + exit(this); + end; + + procedure Create(Content: JsonArray): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content.AsToken()); + exit(this); + end; + + procedure Create(Content: JsonToken): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content); + exit(this); + end; + + procedure Create(Content: XmlDocument): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content); + exit(this); + end; + + procedure Create(Content: Codeunit "Temp Blob") HttpContentImpl: Codeunit "Http Content Impl." + begin + HttpContentImpl := Create(Content, ''); + end; + + procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: InStream) HttpContent: Codeunit "Http Content Impl." + begin + HttpContent := Create(Content, ''); + end; + + procedure Create(Content: InStream; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: HttpContent): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content); + exit(this); + end; + #endregion + procedure SetContentTypeHeader(ContentType: Text) begin if ContentType = '' then @@ -30,58 +114,58 @@ codeunit 2355 "Http Content Impl." procedure AddContentEncoding(ContentEncoding: Text) var - Headers: HttpHeaders; + ContentHeaders: HttpHeaders; begin - if not HttpContent.GetHeaders(Headers) then begin - HttpContent.Clear(); - HttpContent.GetHeaders(Headers); + if not CurrHttpContentInstance.GetHeaders(ContentHeaders) then begin + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.GetHeaders(ContentHeaders); end; - Headers.Add('Content-Encoding', ContentEncoding); + ContentHeaders.Add('Content-Encoding', ContentEncoding); end; procedure SetHeader(Name: Text; Value: Text) var - Headers: HttpHeaders; + ContentHeaders: HttpHeaders; begin - if not HttpContent.GetHeaders(Headers) then begin - HttpContent.Clear(); - HttpContent.GetHeaders(Headers); + if not CurrHttpContentInstance.GetHeaders(ContentHeaders) then begin + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.GetHeaders(ContentHeaders); end; - if Headers.Contains(Name) then - Headers.Remove(Name); + if ContentHeaders.Contains(Name) or ContentHeaders.ContainsSecret(Name) then + ContentHeaders.Remove(Name); - Headers.Add(Name, Value); + ContentHeaders.Add(Name, Value); end; procedure SetHeader(Name: Text; Value: SecretText) var - Headers: HttpHeaders; + ContentHeaders: HttpHeaders; begin - if not HttpContent.GetHeaders(Headers) then begin - HttpContent.Clear(); - HttpContent.GetHeaders(Headers); + if not CurrHttpContentInstance.GetHeaders(ContentHeaders) then begin + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.GetHeaders(ContentHeaders); end; - if Headers.Contains(Name) then - Headers.Remove(Name); + if ContentHeaders.Contains(Name) or ContentHeaders.ContainsSecret(Name) then + ContentHeaders.Remove(Name); - Headers.Add(Name, Value); + ContentHeaders.Add(Name, Value); end; procedure GetHttpContent() ReturnValue: HttpContent begin - ReturnValue := HttpContent; + ReturnValue := CurrHttpContentInstance; end; procedure AsText() ReturnValue: Text begin - HttpContent.ReadAs(ReturnValue); + CurrHttpContentInstance.ReadAs(ReturnValue); end; procedure AsSecretText() ReturnValue: SecretText begin - HttpContent.ReadAs(ReturnValue); + CurrHttpContentInstance.ReadAs(ReturnValue); end; procedure AsBlob() ReturnValue: Codeunit "Temp Blob" @@ -89,14 +173,14 @@ codeunit 2355 "Http Content Impl." InStr: InStream; OutStr: OutStream; begin - HttpContent.ReadAs(InStr); + CurrHttpContentInstance.ReadAs(InStr); ReturnValue.CreateOutStream(OutStr); CopyStream(OutStr, InStr); end; procedure AsInStream(var InStr: InStream) begin - HttpContent.ReadAs(InStr); + CurrHttpContentInstance.ReadAs(InStr); end; procedure AsXmlDocument() ReturnValue: XmlDocument @@ -104,7 +188,8 @@ codeunit 2355 "Http Content Impl." XmlReadOptions: XmlReadOptions; begin XmlReadOptions.PreserveWhitespace(false); - XmlDocument.ReadFrom(AsText(), XmlReadOptions, ReturnValue); + if not XmlDocument.ReadFrom(AsText(), XmlReadOptions, ReturnValue) then + ThrowInvalidXmlException(); end; procedure AsJson() ReturnValue: JsonToken @@ -112,14 +197,38 @@ codeunit 2355 "Http Content Impl." Json: Text; begin Json := AsText(); - if Json <> '' then - ReturnValue.ReadFrom(AsText()); + if Json = '' then + exit; + if not ReturnValue.ReadFrom(Json) then + ThrowInvalidJsonException(); + end; + + procedure AsJsonObject() ReturnValue: JsonObject + var + Json: Text; + begin + Json := AsText(); + if Json = '' then + exit; + if not ReturnValue.ReadFrom(Json) then + ThrowInvalidJsonException(); + end; + + procedure AsJsonArray() ReturnValue: JsonArray + var + Json: Text; + begin + Json := AsText(); + if Json = '' then + exit; + if not ReturnValue.ReadFrom(Json) then + ThrowInvalidJsonException(); end; procedure SetContent(Content: Text; ContentType: Text) begin - HttpContent.Clear(); - HttpContent.WriteFrom(Content); + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeTextPlainTxt; SetContentTypeHeader(ContentType); @@ -127,8 +236,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: SecretText; ContentType: Text) begin - HttpContent.Clear(); - HttpContent.WriteFrom(Content); + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeTextPlainTxt; SetContentTypeHeader(ContentType); @@ -136,8 +245,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: InStream; ContentType: Text) begin - HttpContent.Clear(); - HttpContent.WriteFrom(Content); + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeApplicationOctetStreamTxt; SetContentTypeHeader(ContentType); @@ -173,6 +282,22 @@ codeunit 2355 "Http Content Impl." procedure SetContent(var Value: HttpContent) begin - HttpContent := Value; + CurrHttpContentInstance := Value; + end; + + local procedure ThrowInvalidJsonException() + var + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + InvalidJsonMessageTxt: Label 'The content is not a valid JSON.'; + begin + Error(RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::InvalidJson, InvalidJsonMessageTxt)); + end; + + local procedure ThrowInvalidXmlException() + var + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + InvalidXmlMessageTxt: Label 'The content is not a valid XML.'; + begin + Error(RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::InvalidXml, InvalidXmlMessageTxt)); end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al index fe0fe9319b..9bbbf56dcc 100644 --- a/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al @@ -7,12 +7,23 @@ namespace System.RestClient; /// Holder object for the HTTP request data. codeunit 2352 "Http Request Message" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; var HttpRequestMessageImpl: Codeunit "Http Request Message Impl."; + /// Creates a new instance of the HttpRequestMessage object. + /// The HTTP method to use. Valid options are GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS + /// The Uri to use for the HTTP request. + /// The Http Content object to use for the HTTP request. + /// The create Http Request Message + procedure Create(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content") HttpRequestMessage: Codeunit "Http Request Message" + begin + HttpRequestMessageImpl := HttpRequestMessageImpl.Create(Method, RequestUri, Content); + HttpRequestMessage := this; + end; /// Sets the HTTP method or the HttpRequestMessage object. /// The HTTP method to use. Valid options are GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS @@ -68,6 +79,79 @@ codeunit 2352 "Http Request Message" HttpRequestMessageImpl.SetHeader(HeaderName, HeaderValue); end; + /// Gets the values of the header with the given name from the HttpRequestMessage object. + /// The name of the header to get. + /// A list of values of the header with the given name. + /// If the header is not found, an empty list is returned. + procedure GetHeaderValues(HeaderName: Text) Values: List of [Text] + begin + Values := HttpRequestMessageImpl.GetHeaderValues(HeaderName); + end; + + /// Gets the secret values of the header with the given name from the HttpRequestMessage object. + /// The name of the header to get. + /// A list of values of the header with the given name. + /// If the header is not found, an empty list is returned. + procedure GetSecretHeaderValues(HeaderName: Text) Values: List of [SecretText] + begin + Values := HttpRequestMessageImpl.GetSecretHeaderValues(HeaderName); + end; + + /// Sets the cookie given a name and value + /// The name of the cookie to set. + /// The value of the cookie to set. + procedure SetCookie(Name: Text; Value: Text) Success: Boolean + begin + Success := HttpRequestMessageImpl.SetCookie(Name, Value); + end; + + /// Sets the cookie given a cookie object + /// The cookie object to set. + procedure SetCookie(Cookie: Cookie) Success: Boolean + begin + Success := HttpRequestMessageImpl.SetCookie(Cookie); + end; + + /// Gets the names of the cookies that are set in the HttpRequestMessage object. + /// The names of the cookies that are set in the HttpRequestMessage object. + procedure GetCookieNames() CookieNames: List of [Text] + begin + CookieNames := HttpRequestMessageImpl.GetCookieNames(); + end; + + /// Gets the cookies that are set in the HttpRequestMessage object. + /// The cookies that are set in the HttpRequestMessage object. + procedure GetCookies() Cookies: List of [Cookie] + begin + Cookies := HttpRequestMessageImpl.GetCookies(); + end; + + /// Gets the cookie with the given name from the HttpRequestMessage object. + /// The name of the cookie to get. + /// The cookie object. + /// If the cookie is not found, an empty cookie object is returned. + procedure GetCookie(Name: Text) ReturnValue: Cookie + begin + ReturnValue := HttpRequestMessageImpl.GetCookie(Name); + end; + + /// Gets the cookie with the given name from the HttpRequestMessage object. + /// The name of the cookie to get. + /// The cookie object to get. + /// True if the cookie was found, false otherwise. + procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + begin + Success := HttpRequestMessageImpl.GetCookie(Name, Cookie); + end; + + /// Removes the cookie with the given name from the HttpRequestMessage object. + /// The name of the cookie to remove. + /// True if the cookie was removed, false otherwise. + procedure RemoveCookie(Name: Text) Success: Boolean + begin + Success := HttpRequestMessageImpl.RemoveCookie(Name); + end; + /// Sets the HttpRequestMessage that is represented by the HttpRequestMessage object. /// The HttpRequestMessage to set. procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage) diff --git a/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al index e95a1f0907..6ac1c89795 100644 --- a/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al @@ -11,11 +11,20 @@ codeunit 2353 "Http Request Message Impl." InherentPermissions = X; var - HttpRequestMessage: HttpRequestMessage; + CurrHttpRequestMessageInstance: HttpRequestMessage; + + procedure Create(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"): Codeunit "Http Request Message Impl." + begin + ClearAll(); + SetHttpMethod(Method); + SetRequestUri(RequestUri); + SetContent(Content); + exit(this); + end; procedure SetHttpMethod(Method: Text) begin - HttpRequestMessage.Method := Method; + CurrHttpRequestMessageInstance.Method := Method; end; procedure SetHttpMethod(Method: Enum "Http Method") @@ -25,51 +34,128 @@ codeunit 2353 "Http Request Message Impl." procedure GetHttpMethod() ReturnValue: Text begin - ReturnValue := HttpRequestMessage.Method; + ReturnValue := CurrHttpRequestMessageInstance.Method; end; procedure SetRequestUri(Uri: Text) begin - HttpRequestMessage.SetRequestUri(Uri); + CurrHttpRequestMessageInstance.SetRequestUri(Uri); end; procedure GetRequestUri() Uri: Text begin - Uri := HttpRequestMessage.GetRequestUri(); + Uri := CurrHttpRequestMessageInstance.GetRequestUri(); end; procedure SetHeader(HeaderName: Text; HeaderValue: Text) var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) then - HttpHeaders.Remove(HeaderName); - HttpHeaders.Add(HeaderName, HeaderValue); + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) or RequestHttpHeaders.ContainsSecret(HeaderName) then + RequestHttpHeaders.Remove(HeaderName); + RequestHttpHeaders.Add(HeaderName, HeaderValue); end; procedure SetHeader(HeaderName: Text; HeaderValue: SecretText) var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; + begin + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) or RequestHttpHeaders.ContainsSecret(HeaderName) then + RequestHttpHeaders.Remove(HeaderName); + RequestHttpHeaders.Add(HeaderName, HeaderValue); + end; + + procedure GetHeaders() ReturnValue: HttpHeaders + begin + CurrHttpRequestMessageInstance.GetHeaders(ReturnValue); + end; + + procedure GetHeaderValue(HeaderName: Text) Value: Text + var + RequestHttpHeaders: HttpHeaders; + Values: List of [Text]; + begin + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) then begin + RequestHttpHeaders.GetValues(HeaderName, Values); + if Values.Count > 0 then + Value := Values.Get(1); + end; + end; + + procedure GetHeaderValues(HeaderName: Text) Values: List of [Text] + var + RequestHttpHeaders: HttpHeaders; + begin + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) then + RequestHttpHeaders.GetValues(HeaderName, Values); + end; + + procedure GetSecretHeaderValues(HeaderName: Text) Values: List of [SecretText] + var + RequestHttpHeaders: HttpHeaders; + begin + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.ContainsSecret(HeaderName) then + RequestHttpHeaders.GetSecretValues(HeaderName, Values); + end; + + procedure SetCookie(Name: Text; Value: Text) Success: Boolean + begin + Success := CurrHttpRequestMessageInstance.SetCookie(Name, Value); + end; + + procedure SetCookie(TheCookie: Cookie) Success: Boolean + begin + Success := CurrHttpRequestMessageInstance.SetCookie(TheCookie); + end; + + procedure GetCookieNames() CookieNames: List of [Text] + begin + CookieNames := CurrHttpRequestMessageInstance.GetCookieNames(); + end; + + procedure GetCookies() Cookies: List of [Cookie] + var + CookieName: Text; + TheCookie: Cookie; + begin + foreach CookieName in CurrHttpRequestMessageInstance.GetCookieNames() do begin + CurrHttpRequestMessageInstance.GetCookie(CookieName, TheCookie); + Cookies.Add(TheCookie); + end; + end; + + procedure GetCookie(Name: Text) ReturnValue: Cookie + begin + if CurrHttpRequestMessageInstance.GetCookie(Name, ReturnValue) then; + end; + + procedure GetCookie(Name: Text; var TheCookie: Cookie) Success: Boolean + begin + Success := CurrHttpRequestMessageInstance.GetCookie(Name, TheCookie); + end; + + procedure RemoveCookie(Name: Text) Success: Boolean begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) then - HttpHeaders.Remove(HeaderName); - HttpHeaders.Add(HeaderName, HeaderValue); + Success := CurrHttpRequestMessageInstance.RemoveCookie(Name); end; procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage) begin - HttpRequestMessage := RequestMessage; + CurrHttpRequestMessageInstance := RequestMessage; end; procedure SetContent(HttpContent: Codeunit "Http Content") begin - HttpRequestMessage.Content := HttpContent.GetHttpContent(); + CurrHttpRequestMessageInstance.Content := HttpContent.GetHttpContent(); end; procedure GetRequestMessage() ReturnValue: HttpRequestMessage begin - ReturnValue := HttpRequestMessage; + ReturnValue := CurrHttpRequestMessageInstance; end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al index d53e3ca94e..cdb1833173 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al @@ -7,12 +7,24 @@ namespace System.RestClient; /// Holder object for the HTTP response data. codeunit 2356 "Http Response Message" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; var HttpResponseMessageImpl: Codeunit "Http Response Message Impl."; + #region Constructors + /// Initializes a new instance of the HttpResponseMessage class. + /// The HTTP response message. + /// The HttpResponseMessage object. + procedure Create(HttpResponseMessage: HttpResponseMessage): Codeunit "Http Response Message" + begin + HttpResponseMessageImpl := HttpResponseMessageImpl.Create(HttpResponseMessage); + exit(this); + end; + #endregion + #region IsBlockedByEnvironment /// Sets whether the request is blocked by the environment. /// True if the request is blocked by the environment; otherwise, false. @@ -127,6 +139,47 @@ codeunit 2356 "Http Response Message" end; #endregion + #region Cookies + /// Sets the cookies in the HTTP response message. + /// The cookies to set. + procedure SetCookies(Cookies: Dictionary of [Text, Cookie]) + begin + HttpResponseMessageImpl.SetCookies(Cookies); + end; + + /// Gets the cookies in the HTTP response message. + /// The cookies in the HTTP response message. + procedure GetCookies() ReturnValue: Dictionary of [Text, Cookie] + begin + ReturnValue := HttpResponseMessageImpl.GetCookies(); + end; + + /// Gets the names of the cookies that are set in the HTTP response message. + /// The names of the cookies that are set in the HTTP response message. + procedure GetCookieNames() ReturnValue: List of [Text] + begin + ReturnValue := HttpResponseMessageImpl.GetCookieNames(); + end; + + /// Gets the cookie with the given name from the HTTP response message. + /// The name of the cookie to get. + /// The cookie object. + /// If the cookie is not found, an empty cookie object is returned. + procedure GetCookie(Name: Text) ReturnValue: Cookie + begin + ReturnValue := HttpResponseMessageImpl.GetCookie(Name); + end; + + /// Gets the cookie with the given name from the HTTP response message. + /// The name of the cookie to get. + /// The cookie object to get. + /// True if the cookie was found, false otherwise. + procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + begin + Success := HttpResponseMessageImpl.GetCookie(Name, Cookie); + end; + #endregion + #region ErrorMessage /// Sets an error message when the request failed. /// The error message. @@ -141,5 +194,19 @@ codeunit 2356 "Http Response Message" begin ReturnValue := HttpResponseMessageImpl.GetErrorMessage(); end; + + /// Sets the exception information when the request failed. + /// The exception information. + procedure SetException(Exception: ErrorInfo) + begin + HttpResponseMessageImpl.SetException(Exception); + end; + + /// Gets the exception information when the request failed. + /// The exception information. + procedure GetException() Exception: ErrorInfo + begin + Exception := HttpResponseMessageImpl.GetException(); + end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al index 7355f0244d..c6117a53e1 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al @@ -10,6 +10,14 @@ codeunit 2357 "Http Response Message Impl." InherentEntitlements = X; InherentPermissions = X; + #region Constructors + procedure Create(ResponseMessage: HttpResponseMessage): Codeunit "Http Response Message Impl." + begin + SetResponseMessage(ResponseMessage); + exit(this); + end; + #endregion + #region IsBlockedByEnvironment var IsBlockedByEnvironment: Boolean; @@ -47,7 +55,7 @@ codeunit 2357 "Http Response Message Impl." procedure GetIsSuccessStatusCode() Result: Boolean begin - Result := HttpResponseMessage.IsSuccessStatusCode; + Result := IsSuccessStatusCode; end; procedure SetIsSuccessStatusCode(Value: Boolean) @@ -67,7 +75,7 @@ codeunit 2357 "Http Response Message Impl." procedure GetReasonPhrase() ReturnValue: Text begin - ReturnValue := HttpResponseMessage.ReasonPhrase; + ReturnValue := ReasonPhrase; end; #endregion @@ -88,55 +96,114 @@ codeunit 2357 "Http Response Message Impl." #region HttpResponseMessage var - HttpResponseMessage: HttpResponseMessage; + CurrHttpResponseMessageInstance: HttpResponseMessage; procedure SetResponseMessage(var ResponseMessage: HttpResponseMessage) + var + Cookie: Cookie; + Cookies: Dictionary of [Text, Cookie]; + CookieName: Text; begin - HttpResponseMessage := ResponseMessage; + ClearAll(); + CurrHttpResponseMessageInstance := ResponseMessage; SetIsBlockedByEnvironment(ResponseMessage.IsBlockedByEnvironment); SetHttpStatusCode(ResponseMessage.HttpStatusCode); SetReasonPhrase(ResponseMessage.ReasonPhrase); SetIsSuccessStatusCode(ResponseMessage.IsSuccessStatusCode); SetHeaders(ResponseMessage.Headers); SetContent(HttpContent.Create(ResponseMessage.Content)); + foreach CookieName in ResponseMessage.GetCookieNames() do begin + ResponseMessage.GetCookie(CookieName, Cookie); + Cookies.Add(CookieName, Cookie); + end; + SetCookies(Cookies); end; procedure GetResponseMessage() ReturnValue: HttpResponseMessage begin - ReturnValue := HttpResponseMessage; + ReturnValue := CurrHttpResponseMessageInstance; end; #endregion #region HttpHeaders var - HttpHeaders: HttpHeaders; + ResponseHttpHeaders: HttpHeaders; procedure SetHeaders(Headers: HttpHeaders) begin - HttpHeaders := Headers; + ResponseHttpHeaders := Headers; end; procedure GetHeaders() ReturnValue: HttpHeaders begin - ReturnValue := HttpHeaders; + ReturnValue := ResponseHttpHeaders; + end; + #endregion + + #region Cookies + var + GlobalCookies: Dictionary of [Text, Cookie]; + + procedure SetCookies(Cookies: Dictionary of [Text, Cookie]) + begin + GlobalCookies := Cookies; + end; + + procedure GetCookies() Cookies: Dictionary of [Text, Cookie] + begin + Cookies := GlobalCookies; + end; + + procedure GetCookieNames() CookieNames: List of [Text] + begin + CookieNames := GlobalCookies.Keys; + end; + + procedure GetCookie(Name: Text) TheCookie: Cookie + begin + if GlobalCookies.Get(Name, TheCookie) then; + end; + + procedure GetCookie(Name: Text; var TheCookie: Cookie) Success: Boolean + begin + Success := GlobalCookies.Get(Name, TheCookie); end; #endregion #region ErrorMessage var ErrorMessage: Text; + GlobalException: ErrorInfo; procedure SetErrorMessage(Value: Text) begin ErrorMessage := Value; end; - procedure GetErrorMessage() ReturnValue: Text + procedure GetErrorMessage(): Text begin + if GlobalException.Message <> '' then + exit(GlobalException.Message); + if ErrorMessage <> '' then - ReturnValue := ErrorMessage + exit(ErrorMessage); + + exit(GetLastErrorText()); + end; + + procedure SetException(Exception: ErrorInfo) + begin + GlobalException := Exception; + end; + + procedure GetException() Exception: ErrorInfo + var + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + begin + if GlobalException.Message = '' then + Exception := RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::UnknownException, GetErrorMessage()) else - ReturnValue := GetLastErrorText(); + Exception := GlobalException; end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al index fcc243cd50..5d7f69cb08 100644 --- a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al @@ -7,12 +7,54 @@ namespace System.RestClient; /// Provides functionality to easily work with the HttpClient object. codeunit 2350 "Rest Client" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; var RestClientImpl: Codeunit "Rest Client Impl."; + #region Constructors + /// Initializes a new instance of the Rest Client class. + /// The Rest Client object. + /// The default Http Client Handler and anonymous Http authentication will be used. + procedure Create(): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(); + exit(this); + end; + + /// Initializes a new instance of the Rest Client class. + /// The Http Client Handler to use. + /// The Rest Client object. + /// The anynomous Http Authentication will be used. + procedure Create(HttpClientHandler: Interface "Http Client Handler"): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(HttpClientHandler); + exit(this); + end; + + /// Initializes a new instance of the Rest Client class. + /// The authentication to use. + /// The Rest Client object. + /// The default Http Client Handler will be used. + procedure Create(HttpAuthentication: Interface "Http Authentication"): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(HttpAuthentication); + exit(this); + end; + + /// Initializes a new instance of the Rest Client class. + /// The Http Client Handler to use. + /// The authentication to use. + /// The Rest Client object. + procedure Create(HttpClientHandler: Interface "Http Client Handler"; HttpAuthentication: Interface "Http Authentication"): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(HttpClientHandler, HttpAuthentication); + exit(this); + end; + #endregion + #region Initialization /// Initializes the Rest Client with the default Http Client Handler and anonymous Http authentication. procedure Initialize() @@ -135,11 +177,21 @@ codeunit 2350 "Rest Client" begin RestClientImpl.SetAuthorizationHeader(Value); end; + + /// Sets the use of response cookies in subsequent requests. + /// Use this function to enable or disable automatically attach cookies received in the response to all subsequent requests. + /// The Rest Client will be initialized if it was not initialized before. + /// If true, the client automatically attaches cookies received in the response to all subsequent requests. False to disable + procedure SetUseResponseCookies(Value: Boolean) + begin + RestClientImpl.SetUseResponseCookies(Value); + end; #endregion #region BasicMethods /// Sends a GET request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. + /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The response message object procedure Get(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message" @@ -148,7 +200,7 @@ codeunit 2350 "Rest Client" end; /// Sends a POST request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The content to send. @@ -159,7 +211,7 @@ codeunit 2350 "Rest Client" end; /// Sends a PATCH request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The content to send. @@ -170,7 +222,7 @@ codeunit 2350 "Rest Client" end; /// Sends a PUT request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The content to send. @@ -181,23 +233,22 @@ codeunit 2350 "Rest Client" end; /// Sends a DELETE request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The response message object - procedure Delete(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message"; + procedure Delete(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message" begin HttpResponseMessage := Send(Enum::"Http Method"::DELETE, RequestUri); end; - #endregion #region BasicMethodsAsJson /// Sends a GET request to the specified Uri and returns the response content as JsonToken. - /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function fails with a collectible error if the request could not be sent or a response was not received. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. - /// In case the response contains content, then the function fails if the content is invalid JSON. + /// In case the response contains content, then the function fails with a collectible error if the content is invalid JSON. /// The Uri the request is sent to. /// The response content as JsonToken procedure GetAsJson(RequestUri: Text) JsonToken: JsonToken @@ -207,7 +258,7 @@ codeunit 2350 "Rest Client" /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -220,7 +271,7 @@ codeunit 2350 "Rest Client" /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -233,7 +284,7 @@ codeunit 2350 "Rest Client" /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -246,7 +297,7 @@ codeunit 2350 "Rest Client" /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -259,7 +310,7 @@ codeunit 2350 "Rest Client" /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -272,7 +323,7 @@ codeunit 2350 "Rest Client" /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -285,7 +336,7 @@ codeunit 2350 "Rest Client" /// Sends a PUT request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -298,7 +349,7 @@ codeunit 2350 "Rest Client" /// Sends a PUT request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -311,7 +362,7 @@ codeunit 2350 "Rest Client" /// Sends a PUT request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -325,7 +376,7 @@ codeunit 2350 "Rest Client" #region GenericSendMethods /// Sends a request with the specific Http method and an empty content to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The HTTP method to use. /// The Uri the request is sent to. @@ -336,7 +387,7 @@ codeunit 2350 "Rest Client" end; /// Sends a request with the specific Http method and the given content to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The HTTP method to use. /// The Uri the request is sent to. @@ -348,12 +399,13 @@ codeunit 2350 "Rest Client" end; /// Sends the given request message and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// The request message to send. /// The response message object procedure Send(var HttpRequestMessage: Codeunit "Http Request Message") HttpResponseMessage: Codeunit "Http Response Message" begin HttpResponseMessage := RestClientImpl.Send(HttpRequestMessage); end; + #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al index c2c30f9498..6ba4f969ae 100644 --- a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al @@ -13,14 +13,39 @@ codeunit 2351 "Rest Client Impl." var DefaultHttpClientHandler: Codeunit "Http Client Handler"; HttpAuthenticationAnonymous: Codeunit "Http Authentication Anonymous"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; HttpAuthentication: Interface "Http Authentication"; HttpClientHandler: Interface "Http Client Handler"; - HttpClient: HttpClient; + CurrHttpClientInstance: HttpClient; IsInitialized: Boolean; - EnvironmentBlocksErr: Label 'Environment blocks an outgoing HTTP request to ''%1''.', Comment = '%1 = url, e.g. https://microsoft.com'; - ConnectionErr: Label 'Connection to the remote service ''%1'' could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; - RequestFailedErr: Label 'The request failed: %1 %2', Comment = '%1 = HTTP status code, %2 = Reason phrase'; + EnvironmentBlocksErr: Label 'The outgoing HTTP request to "%1" was blocked by the environment.', Comment = '%1 = url, e.g. https://microsoft.com'; + ConnectionErr: Label 'Connection to the remote service "%1" could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; + RequestFailedErr: Label 'The request to "%1" failed with status code %2 %3.', Comment = '%1 = url, %2 = HTTP status code, %3 = Reason phrase'; UserAgentLbl: Label 'Dynamics 365 Business Central - |%1| %2/%3', Locked = true, Comment = '%1 = App Publisher; %2 = App Name; %3 = App Version'; + TimeoutOutOfRangeErr: Label 'The timeout value must be greater than 0.'; + + #region Constructors + procedure Create() RestClientImpl: Codeunit "Rest Client Impl." + begin + RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthenticationAnonymous); + end; + + procedure Create(HttpClientHandlerInstance: Interface "Http Client Handler") RestClientImpl: Codeunit "Rest Client Impl." + begin + RestClientImpl := RestClientImpl.Create(HttpClientHandlerInstance, HttpAuthenticationAnonymous); + end; + + procedure Create(HttpAuthenticationInstance: Interface "Http Authentication") RestClientImpl: Codeunit "Rest Client Impl." + begin + RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthenticationInstance); + end; + + procedure Create(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication"): Codeunit "Rest Client Impl." + begin + Initialize(HttpClientHandlerInstance, HttpAuthenticationInstance); + exit(this); + end; + #endregion #region Initialization procedure Initialize() @@ -28,23 +53,21 @@ codeunit 2351 "Rest Client Impl." Initialize(DefaultHttpClientHandler, HttpAuthenticationAnonymous); end; -#pragma warning disable AA0244 - procedure Initialize(HttpClientHandler: Interface "Http Client Handler") + procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler") begin - Initialize(HttpClientHandler, HttpAuthenticationAnonymous); + Initialize(HttpClientHandlerInstance, HttpAuthenticationAnonymous); end; - procedure Initialize(HttpAuthentication: Interface "Http Authentication") + procedure Initialize(HttpAuthenticationInstance: Interface "Http Authentication") begin - Initialize(DefaultHttpClientHandler, HttpAuthentication); + Initialize(DefaultHttpClientHandler, HttpAuthenticationInstance); end; -#pragma warning restore AA0244 procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication") begin ClearAll(); - HttpClient.Clear(); + CurrHttpClientInstance.Clear(); HttpClientHandler := HttpClientHandlerInstance; HttpAuthentication := HttpAuthenticationInstance; IsInitialized := true; @@ -54,53 +77,55 @@ codeunit 2351 "Rest Client Impl." procedure SetDefaultRequestHeader(Name: Text; Value: Text) begin CheckInitialized(); - if HttpClient.DefaultRequestHeaders.Contains(Name) then - HttpClient.DefaultRequestHeaders.Remove(Name); - HttpClient.DefaultRequestHeaders.Add(Name, Value); + if CurrHttpClientInstance.DefaultRequestHeaders.Contains(Name) then + CurrHttpClientInstance.DefaultRequestHeaders.Remove(Name); + CurrHttpClientInstance.DefaultRequestHeaders.Add(Name, Value); end; procedure SetDefaultRequestHeader(Name: Text; Value: SecretText) begin CheckInitialized(); - if HttpClient.DefaultRequestHeaders.Contains(Name) then - HttpClient.DefaultRequestHeaders.Remove(Name); - HttpClient.DefaultRequestHeaders.Add(Name, Value); + if CurrHttpClientInstance.DefaultRequestHeaders.Contains(Name) then + CurrHttpClientInstance.DefaultRequestHeaders.Remove(Name); + CurrHttpClientInstance.DefaultRequestHeaders.Add(Name, Value); end; procedure SetBaseAddress(Url: Text) begin CheckInitialized(); - HttpClient.SetBaseAddress(Url); + CurrHttpClientInstance.SetBaseAddress(Url); end; procedure GetBaseAddress() Url: Text begin CheckInitialized(); - Url := HttpClient.GetBaseAddress; + Url := CurrHttpClientInstance.GetBaseAddress; end; procedure SetTimeOut(TimeOut: Duration) begin CheckInitialized(); - HttpClient.Timeout := TimeOut; + if TimeOut <= 0 then + Error(TimeoutOutOfRangeErr); + CurrHttpClientInstance.Timeout := TimeOut; end; procedure GetTimeOut() TimeOut: Duration begin CheckInitialized(); - TimeOut := HttpClient.Timeout; + TimeOut := CurrHttpClientInstance.Timeout; end; procedure AddCertificate(Certificate: Text) begin CheckInitialized(); - HttpClient.AddCertificate(Certificate); + CurrHttpClientInstance.AddCertificate(Certificate); end; procedure AddCertificate(Certificate: Text; Password: SecretText) begin CheckInitialized(); - HttpClient.AddCertificate(Certificate, Password); + CurrHttpClientInstance.AddCertificate(Certificate, Password); end; procedure SetAuthorizationHeader(Value: SecretText) @@ -112,8 +137,13 @@ codeunit 2351 "Rest Client Impl." begin SetDefaultRequestHeader('User-Agent', Value); end; - #endregion + procedure SetUseResponseCookies(Value: Boolean) + begin + CheckInitialized(); + CurrHttpClientInstance.UseResponseCookies(Value); + end; + #endregion #region BasicMethodsAsJson procedure GetAsJson(RequestUri: Text) JsonToken: JsonToken @@ -121,8 +151,16 @@ codeunit 2351 "Rest Client Impl." HttpResponseMessage: Codeunit "Http Response Message"; begin HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri); + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; + if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; JsonToken := HttpResponseMessage.GetContent().AsJson(); end; @@ -133,9 +171,16 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, HttpContent.Create(Content)); + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -146,9 +191,16 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, HttpContent.Create(Content)); + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -159,9 +211,16 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, HttpContent.Create(Content)); + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -179,15 +238,7 @@ codeunit 2351 "Rest Client Impl." var HttpRequestMessage: Codeunit "Http Request Message"; begin - CheckInitialized(); - - HttpRequestMessage.SetHttpMethod(Method); - if RequestUri.StartsWith('http://') or RequestUri.StartsWith('https://') then - HttpRequestMessage.SetRequestUri(RequestUri) - else - HttpRequestMessage.SetRequestUri(GetBaseAddress() + RequestUri); - HttpRequestMessage.SetContent(Content); - + HttpRequestMessage := CreateHttpRequestMessage(Method, RequestUri, Content); HttpResponseMessage := Send(HttpRequestMessage); end; @@ -196,8 +247,9 @@ codeunit 2351 "Rest Client Impl." CheckInitialized(); if not SendRequest(HttpRequestMessage, HttpResponseMessage) then - Error(HttpResponseMessage.GetErrorMessage()); + Error(HttpResponseMessage.GetException()); end; + #endregion #region Local Methods @@ -218,27 +270,36 @@ codeunit 2351 "Rest Client Impl." SetUserAgentHeader(UserAgentString); end; + local procedure CreateHttpRequestMessage(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content") HttpRequestMessage: Codeunit "Http Request Message" + begin + if not (RequestUri.StartsWith('http://') or RequestUri.StartsWith('https://')) then + RequestUri := GetBaseAddress() + RequestUri; + HttpRequestMessage := HttpRequestMessage.Create(Method, RequestUri, Content); + end; + local procedure SendRequest(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean - var - ErrorMessage: Text; begin Clear(HttpResponseMessage); if HttpAuthentication.IsAuthenticationRequired() then Authorize(HttpRequestMessage); - if not HttpClientHandler.Send(HttpClient, HttpRequestMessage, HttpResponseMessage) then begin + if not HttpClientHandler.Send(CurrHttpClientInstance, HttpRequestMessage, HttpResponseMessage) then begin if HttpResponseMessage.GetIsBlockedByEnvironment() then - ErrorMessage := StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()) + HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, + StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()))) else - ErrorMessage := StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()); + HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, + StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()))); exit(false); end; - if not HttpResponseMessage.GetIsSuccessStatusCode() then begin - ErrorMessage := StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()); - HttpResponseMessage.SetErrorMessage(ErrorMessage); - end; + if not HttpResponseMessage.GetIsSuccessStatusCode() then + HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, + StrSubstNo(RequestFailedErr, HttpRequestMessage.GetRequestUri(), HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); exit(true); end; diff --git a/src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json b/src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json new file mode 100644 index 0000000000..6c38dccb1f --- /dev/null +++ b/src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json @@ -0,0 +1,32 @@ +[ + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestResponseWithCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestRequestWithCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestWithoutUseResponseCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestWithUseResponseCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestUseResponseCookiesWithAdditionalCookies" + } +] \ No newline at end of file diff --git a/src/System Application/Test/Rest Client/app.json b/src/System Application/Test/Rest Client/app.json index 2d14cf6c3f..d6ad2f8ba4 100644 --- a/src/System Application/Test/Rest Client/app.json +++ b/src/System Application/Test/Rest Client/app.json @@ -35,7 +35,7 @@ "idRanges": [ { "from": 134970, - "to": 134974 + "to": 134977 } ], "resourceExposurePolicy": { diff --git a/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al b/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al index 48834ba82e..4568b0ef9a 100644 --- a/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al @@ -28,7 +28,7 @@ codeunit 134973 "Http Authentication Tests" // [WHEN] The authentication object is asked to return the authorization header // [THEN] The authentication object should return an empty list - Assert.AreEqual(HttpAuthenticationAnonymous.GetAuthorizationHeaders().Count, 0, 'Anonymous authentication should not return an authorization header'); + Assert.AreEqual(0, HttpAuthenticationAnonymous.GetAuthorizationHeaders().Count, 'Anonymous authentication should not return an authorization header'); end; [NonDebuggable] @@ -49,10 +49,10 @@ codeunit 134973 "Http Authentication Tests" // [WHEN] The authentication object is asked to return the authorization header // [THEN] THe authentication object should return a dictionary with one element that is a base64 encoded string AuthHeader := HttpAuthenticationBasic.GetAuthorizationHeaders(); - Assert.AreEqual(AuthHeader.Count, 1, 'Basic authentication should return one authorization header'); - Assert.AreEqual(AuthHeader.ContainsKey('Authorization'), true, 'Basic authentication should return an authorization header'); + Assert.AreEqual(1, AuthHeader.Count, 'Basic authentication should return one authorization header'); + Assert.AreEqual(true, AuthHeader.ContainsKey('Authorization'), 'Basic authentication should return an authorization header'); BasicAuthHeaderValue := AuthHeader.Get('Authorization'); - Assert.AreEqual(BasicAuthHeaderValue.Unwrap(), 'Basic VVNFUjAxOlBhc3N3b3JkMTIzIQ==', 'Basic authentication should return a base64 encoded string'); + Assert.AreEqual('Basic VVNFUjAxOlBhc3N3b3JkMTIzIQ==', BasicAuthHeaderValue.Unwrap(), 'Basic authentication should return a base64 encoded string'); end; } \ No newline at end of file diff --git a/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al b/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al new file mode 100644 index 0000000000..5a9d00379b --- /dev/null +++ b/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al @@ -0,0 +1,290 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Test.RestClient; + +using System.RestClient; +using System.TestLibraries.Utilities; + +codeunit 134977 "Http Exception Tests" +{ + Subtype = Test; + + var + Assert: Codeunit "Library Assert"; + HttpClientHandler: Codeunit "Test Http Client Handler"; + + [Test] + procedure TestConnectionFailed() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Error is raised when the connection fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockConnectionFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + asserterror HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns an error + Assert.ExpectedError('Connection to the remote service "http://www.example.com/" could not be established.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestConnectionFailedWithErrorCollection() + var + RestClient: Codeunit "Rest Client"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + HttpResponseMessage: Codeunit "Http Response Message"; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when the connection fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockConnectionFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns one collectible error of type ConnectionFailed + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::ConnectionFailed, RestClientException, 'The exception should be of type ConnectionFailed'); + Assert.AreEqual('ConnectionFailed', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "ConnectionFailed"'); + Assert.AreEqual('Connection to the remote service "http://www.example.com/" could not be established.', Exception.Message, 'The error message is incorrect'); + end; + + [Test] + procedure TestBlockedByEnvironment() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Error is raised when the request is blocked by the environment + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockIsBlockedByEnvironment(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + asserterror HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns an error + Assert.ExpectedError('The outgoing HTTP request to "http://www.example.com/" was blocked by the environment.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestBlockedByEnvironmentWithErrorCollection() + var + RestClient: Codeunit "Rest Client"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + HttpResponseMessage: Codeunit "Http Response Message"; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when the request is blocked by the environment + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockIsBlockedByEnvironment(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns one collectible error of type BlockedByEnvironment + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::BlockedByEnvironment, RestClientException, 'The exception should be of type BlockedByEnvironment'); + Assert.AreEqual('BlockedByEnvironment', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "BlockedByEnvironment"'); + Assert.AreEqual('The outgoing HTTP request to "http://www.example.com/" was blocked by the environment.', Exception.Message, 'The error message is incorrect'); + end; + + [Test] + procedure TestRequestFailedOnGenericMethod() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Error is raised when the request fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockRequestFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] HttpResponseMessage contains an error + Assert.IsFalse(HttpResponseMessage.GetIsSuccessStatusCode(), 'The request should not be successful'); + asserterror Error(HttpResponseMessage.GetException()); + Assert.ExpectedError('The request to "http://www.example.com/" failed with status code 400 Bad Request.'); + end; + + [Test] + procedure TestRequestFailedOnTypedMethod() + var + RestClient: Codeunit "Rest Client"; + JsonToken: JsonToken; + begin + // [SCENARIO] Error is raised when the request fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockRequestFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + asserterror JsonToken := RestClient.GetAsJson('http://www.example.com'); + + // [THEN] HttpResponseMessage contains an error + Assert.ExpectedError('The request to "http://www.example.com/" failed with status code 400 Bad Request.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestRequestFailedWithErrorCollection() + var + RestClient: Codeunit "Rest Client"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + JsonToken: JsonToken; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when the request fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockRequestFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + JsonToken := RestClient.GetAsJson('http://www.example.com'); + + // [THEN] RestClient returns one collectible error of type RequestFailed + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::RequestFailed, RestClientException, 'The exception should be of type RequestFailed'); + Assert.AreEqual('RequestFailed', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "RequestFailed"'); + Assert.AreEqual('The request to "http://www.example.com/" failed with status code 400 Bad Request.', Exception.Message, 'The error message is incorrect'); + end; + + [Test] + procedure TestInvalidJson() + var + HttpContent: Codeunit "Http Content"; + JsonToken: JsonToken; + begin + // [SCENARIO] Error is raised when trying to read as JSON from a HttpContent object with invalid JSON + + // [GIVEN] HttpContent object with invalid JSON + HttpContent := HttpContent.Create('{"key": "value"'); + + // [WHEN] Trying to read as JSON + asserterror JsonToken := HttpContent.AsJson(); + + // [THEN] HttpContent returns an error + Assert.ExpectedError('The content is not a valid JSON.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestInvalidJsonWithErrorCollection() + var + HttpContent: Codeunit "Http Content"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + JsonToken: JsonToken; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when trying to read as JSON from a HttpContent object with invalid JSON + + // [GIVEN] HttpContent object with invalid JSON + HttpContent := HttpContent.Create('{"key": "value"'); + + // [WHEN] Trying to read as JSON + JsonToken := HttpContent.AsJson(); + + // [THEN] HttpContent returns one collectible error of type InvalidJson + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::InvalidJson, RestClientException, 'The exception should be of type InvalidJson'); + Assert.AreEqual('InvalidJson', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "InvalidJson"'); + Assert.AreEqual('The content is not a valid JSON.', Exception.Message, 'The message should be "The content is not a valid JSON."'); + end; + + [Test] + procedure TestInvalidXml() + var + HttpContent: Codeunit "Http Content"; + XmlDocument: XmlDocument; + begin + // [SCENARIO] Error is raised when trying to read as XML from a HttpContent object with invalid XML + + // [GIVEN] HttpContent object with invalid XML + HttpContent := HttpContent.Create(''); + + // [WHEN] Trying to read as XML + asserterror XmlDocument := HttpContent.AsXmlDocument(); + + // [THEN] HttpContent returns an error + Assert.ExpectedError('The content is not a valid XML.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestInvalidXmlWithErrorCollection() + var + HttpContent: Codeunit "Http Content"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + XmlDocument: XmlDocument; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when trying to read as XML from a HttpContent object with invalid XML + + // [GIVEN] HttpContent object with invalid XML + HttpContent := HttpContent.Create(''); + + // [WHEN] Trying to read as XML + XmlDocument := HttpContent.AsXmlDocument(); + + // [THEN] HttpContent returns one collectible error of type InvalidXml + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::InvalidXml, RestClientException, 'The exception should be of type InvalidXml'); + Assert.AreEqual('InvalidXml', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "InvalidXml"'); + Assert.AreEqual('The content is not a valid XML.', Exception.Message, 'The message should be "The content is not a valid XML."'); + end; +} \ No newline at end of file diff --git a/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al b/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al index 10d54896a4..48b82fabc7 100644 --- a/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al @@ -30,7 +30,7 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'PATCH', 'The request message method is not correct.'); + Assert.AreEqual('PATCH', HttpRequestMessage.Method(), 'The request message method is not correct.'); end; [Test] @@ -49,8 +49,8 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'GET', 'The request message method is not correct.'); - Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.'); + Assert.AreEqual('GET', HttpRequestMessage.Method(), 'The request message method is not correct.'); + Assert.AreEqual('https://www.microsoft.com/', HttpRequestMessage.GetRequestUri(), 'The request message request URI is not correct.'); end; [Test] @@ -74,17 +74,17 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'POST', 'The request message method is not correct.'); - Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.'); + Assert.AreEqual('POST', HttpRequestMessage.Method(), 'The request message method is not correct.'); + Assert.AreEqual('https://www.microsoft.com/', HttpRequestMessage.GetRequestUri(), 'The request message request URI is not correct.'); HttpRequestMessage.Content().ReadAs(ContentText); - Assert.AreEqual(ContentText, 'Hello World!', 'The request message content is not correct.'); + Assert.AreEqual('Hello World!', ContentText, 'The request message content is not correct.'); HttpRequestMessage.Content.GetHeaders(ContentHeaders); - Assert.AreEqual(ContentHeaders.Contains('Content-Type'), true, 'The content type header is missing.'); + Assert.AreEqual(true, ContentHeaders.Contains('Content-Type'), 'The content type header is missing.'); ContentHeaders.GetValues('Content-Type', ContentHeaderValues); - Assert.AreEqual(ContentHeaderValues.Get(1), 'text/plain', 'The request message content type is not correct.'); + Assert.AreEqual('text/plain', ContentHeaderValues.Get(1), 'The request message content type is not correct.'); end; [Test] @@ -110,19 +110,19 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'POST', 'The request message method is not correct.'); - Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.'); + Assert.AreEqual('POST', HttpRequestMessage.Method(), 'The request message method is not correct.'); + Assert.AreEqual('https://www.microsoft.com/', HttpRequestMessage.GetRequestUri(), 'The request message request URI is not correct.'); HttpRequestMessage.Content().ReadAs(ContentText); - Assert.AreEqual(ContentJson.ReadFrom(ContentText), true, 'The request message content is not a valid JSON object.'); - Assert.AreEqual(ContentJson.Contains('value'), true, 'The request message content does not contain the expected property "value".'); - Assert.AreEqual(GetJsonToken(ContentJson, 'value').AsValue().AsText(), 'Hello World!', 'The request message content property "value" is not correct.'); + Assert.AreEqual(true, ContentJson.ReadFrom(ContentText), 'The request message content is not a valid JSON object.'); + Assert.AreEqual(true, ContentJson.Contains('value'), 'The request message content does not contain the expected property "value".'); + Assert.AreEqual('Hello World!', GetJsonToken(ContentJson, 'value').AsValue().AsText(), 'The request message content property "value" is not correct.'); HttpRequestMessage.Content.GetHeaders(ContentHeaders); - Assert.AreEqual(ContentHeaders.Contains('Content-Type'), true, 'The content type header is missing.'); + Assert.AreEqual(true, ContentHeaders.Contains('Content-Type'), 'The content type header is missing.'); ContentHeaders.GetValues('Content-Type', ContentHeaderValues); - Assert.AreEqual(ContentHeaderValues.Get(1), 'application/json', 'The request message content type is not correct.'); + Assert.AreEqual('application/json', ContentHeaderValues.Get(1), 'The request message content type is not correct.'); end; [Test] @@ -145,12 +145,86 @@ codeunit 134972 "Request Message Tests" // [THEN] The request message is initialized correctly HttpRequestMessage.GetHeaders(ContentHeaders); - Assert.AreEqual(ContentHeaders.Contains('X-Custom-Header'), true, 'The custom header is missing.'); + Assert.IsTrue(ContentHeaders.Contains('X-Custom-Header'), 'The custom header is missing.'); ContentHeaders.GetValues('X-Custom-Header', ContentHeaderValues); - Assert.AreEqual(ContentHeaderValues.Get(1), 'My Request Header', 'The custom header value is not correct.'); + Assert.AreEqual('My Request Header', ContentHeaderValues.Get(1), 'The custom header value is not correct.'); end; + [Test] + [NonDebuggable] + procedure TestAddSecretRequestHeader() + var + ALHttpRequestMessage: Codeunit "Http Request Message"; + HttpRequestMessage: HttpRequestMessage; + ContentHeaders: HttpHeaders; + SecretHeaderText: SecretText; + ContentHeaderValues: List of [SecretText]; + begin + // [GIVEN] An initialized Http Request Message + ALHttpRequestMessage.SetHttpMethod('GET'); + ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/'); + + // [GIVEN] The request message has a secret header + SecretHeaderText := SecretStrSubstNo('My Secret Request Header'); + ALHttpRequestMessage.SetHeader('X-Secret-Header', SecretHeaderText); + + // [WHEN] The request message is read + HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); + + // [THEN] The request message is initialized correctly + HttpRequestMessage.GetHeaders(ContentHeaders); + Assert.IsTrue(ContentHeaders.ContainsSecret('X-Secret-Header'), 'The secret header is missing.'); + + ContentHeaders.GetSecretValues('X-Secret-Header', ContentHeaderValues); + Assert.AreEqual(SecretHeaderText.Unwrap(), ContentHeaderValues.Get(1).Unwrap(), 'The secret header value is not correct.'); + end; + + [Test] + procedure TestAddCookie() + var + ALHttpRequestMessage: Codeunit "Http Request Message"; + HttpRequestMessage: HttpRequestMessage; + RequestCookie: Cookie; + begin + // [GIVEN] An initialized Http Request Message + ALHttpRequestMessage.SetHttpMethod('GET'); + ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/'); + + // [GIVEN] The request message has a cookie + ALHttpRequestMessage.SetCookie('MyCookie', 'MyCookieValue'); + + // [WHEN] The request message is read + HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); + + // [THEN] The request message is initialized correctly + Assert.IsTrue(HttpRequestMessage.GetCookie('MyCookie', RequestCookie), 'The cookie is missing.'); + Assert.AreEqual('MyCookieValue', RequestCookie.Value(), 'The cookie value is not correct.'); + end; + + [Test] + procedure TestRemoveCookie() + var + ALHttpRequestMessage: Codeunit "Http Request Message"; + HttpRequestMessage: HttpRequestMessage; + RequestCookie: Cookie; + begin + // [GIVEN] An initialized Http Request Message + ALHttpRequestMessage.SetHttpMethod('GET'); + ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/'); + + // [GIVEN] The request message has a cookie + ALHttpRequestMessage.SetCookie('MyCookie', 'MyCookieValue'); + ALHttpRequestMessage.RemoveCookie('MyCookie'); + + // [WHEN] The request message is read + HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); + + // [THEN] The request message is initialized correctly + Assert.IsFalse(HttpRequestMessage.GetCookie('MyCookie', RequestCookie), 'The cookie is not removed.'); + end; + + local procedure GetJsonToken(JsonObject: JsonObject; Name: Text) JsonToken: JsonToken begin JsonObject.Get(Name, JsonToken); diff --git a/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al b/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al index a29dde8c92..96d5048d86 100644 --- a/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al @@ -25,17 +25,42 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test GET request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Get method is called HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + end; + + [Test] + procedure TestGetWithQueryParameters() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with query parameters + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [WHEN] The Get method is called with query parameters + HttpResponseMessage := RestClient.Get('https://httpbin.org/get?param1=value1¶m2=value2'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.args.param1').AsValue().AsText(), 'The response should contain the expected query parameter'); + Assert.AreEqual('value2', SelectJsonToken(JsonObject, '$.args.param2').AsValue().AsText(), 'The response should contain the expected query parameter'); end; [Test] @@ -48,18 +73,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test POST request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Post method is called HttpResponseMessage := RestClient.Post('https://httpbin.org/post', HttpGetContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/post', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -72,18 +98,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PATCH request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Patch method is called HttpResponseMessage := RestClient.Patch('https://httpbin.org/patch', HttpGetContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/patch', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/patch', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -96,18 +123,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PUT request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Put method is called HttpResponseMessage := RestClient.Put('https://httpbin.org/put', HttpGetContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/put', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/put', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -119,17 +147,18 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test DELETE request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Delete method is called HttpResponseMessage := RestClient.Delete('https://httpbin.org/delete'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/delete', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/delete', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -142,6 +171,7 @@ codeunit 134971 "Rest Client Tests" // [SCENARIO] Test GET request with headers // [GIVEN] An initialized Rest Client with default request headers + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); RestClient.SetDefaultRequestHeader('X-Test-Header', 'Test'); @@ -149,11 +179,11 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); - Assert.AreEqual(SelectJsonToken('$.headers.X-Test-Header', JsonObject).AsValue().AsText(), 'Test', 'The response should contain the expected header'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Test', SelectJsonToken(JsonObject, '$.headers.X-Test-Header').AsValue().AsText(), 'The response should contain the expected header'); end; [Test] @@ -166,6 +196,7 @@ codeunit 134971 "Rest Client Tests" // [SCENARIO] Test GET request with base address // [GIVEN] An initialized Rest Client with base address + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); RestClient.SetBaseAddress('https://httpbin.org'); @@ -173,10 +204,10 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -188,17 +219,18 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test GET request with default User-Agent header - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Get method is called using the default User-Agent header HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.IsTrue(SelectJsonToken('$.headers.User-Agent', JsonObject).AsValue().AsText().StartsWith('Dynamics 365 Business Central '), 'The response should contain a User-Agent header'); + Assert.IsTrue(SelectJsonToken(JsonObject, '$.headers.User-Agent').AsValue().AsText().StartsWith('Dynamics 365 Business Central '), 'The response should contain a User-Agent header'); end; [Test] @@ -211,6 +243,7 @@ codeunit 134971 "Rest Client Tests" // [SCENARIO] Test GET request with custom User-Agent header // [GIVEN] An initialized Rest Client with a customer User-Agent header + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); RestClient.SetUserAgentHeader('BC Rest Client Test'); @@ -218,10 +251,10 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(SelectJsonToken('$.headers.User-Agent', JsonObject).AsValue().AsText(), 'BC Rest Client Test', 'The response should contain the expected User-Agent header'); + Assert.AreEqual('BC Rest Client Test', SelectJsonToken(JsonObject, '$.headers.User-Agent').AsValue().AsText(), 'The response should contain the expected User-Agent header'); end; [Test] @@ -232,14 +265,40 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test GET request with JSON response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The GetAsJson method is called JsonObject := RestClient.GetAsJson('https://httpbin.org/get').AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestGetAsJsonWithCollectingErrors() + var + RestClient: Codeunit "Rest Client"; + JsonToken: JsonToken; + ExceptionList: List of [ErrorInfo]; + Exception: ErrorInfo; + begin + // [SCENARIO] Test GET request with JSON response + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [WHEN] The GetAsJson method is called + JsonToken := RestClient.GetAsJson('https://httpbin.org/xml'); + + // [THEN] The response contains the expected data + ExceptionList := GetCollectedErrors(true); + Exception := ExceptionList.Get(1); + + Assert.AreEqual('The content is not a valid JSON.', Exception.Message, 'The collected error message should be as expected'); end; [Test] @@ -252,7 +311,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test POST request with JSON request and response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [GIVEN] A Json object @@ -264,10 +324,10 @@ codeunit 134971 "Rest Client Tests" JsonObject2 := RestClient.PostAsJson('https://httpbin.org/post', JsonObject1).AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/post', GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'The response should contain the expected url'); JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText()); - Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data'); - Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data'); + Assert.AreEqual('John', GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'The response should contain the expected data'); + Assert.AreEqual(30, GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 'The response should contain the expected data'); end; [Test] @@ -280,7 +340,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PATCH request with JSON request and response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [GIVEN] A Json object @@ -292,10 +353,10 @@ codeunit 134971 "Rest Client Tests" JsonObject2 := RestClient.PatchAsJson('https://httpbin.org/patch', JsonObject1).AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/patch', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/patch', GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'The response should contain the expected url'); JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText()); - Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data'); - Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data'); + Assert.AreEqual('John', GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'The response should contain the expected data'); + Assert.AreEqual(30, GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 'The response should contain the expected data'); end; [Test] @@ -308,7 +369,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PUT request with JSON request and response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [GIVEN] A Json object @@ -320,10 +382,10 @@ codeunit 134971 "Rest Client Tests" JsonObject2 := RestClient.PutAsJson('https://httpbin.org/put', JsonObject1).AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/put', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/put', GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'The response should contain the expected url'); JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText()); - Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data'); - Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data'); + Assert.AreEqual('John', GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'The response should contain the expected data'); + Assert.AreEqual(30, GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 'The response should contain the expected data'); end; [Test] @@ -335,17 +397,18 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test Send method without Getcontent - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Send method is called without Getcontent HttpResponseMessage := RestClient.Send(Enum::"Http Method"::GET, 'https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -358,18 +421,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test Send method with Getcontent - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Send method is called with Getcontent HttpResponseMessage := RestClient.Send(Enum::"Http Method"::POST, 'https://httpbin.org/post', HttpContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/post', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -382,7 +446,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test Send method with request message - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Send method is called with a request message @@ -391,10 +456,10 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Send(ALHttpRequestMessage); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -417,9 +482,152 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('https://httpbin.org/basic-auth/user01/Password123'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual(true, GetJsonToken(JsonObject, 'authenticated').AsValue().AsBoolean(), 'The response should contain the expected data'); + end; + + [Test] + procedure TestResponseWithCookies() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Test GET request with cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [WHEN] The Get method is called + HttpResponseMessage := RestClient.Get('https://postman-echo.com/get'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + Assert.IsTrue(HttpResponseMessage.GetCookieNames().Contains('sails.sid'), 'The response should contain the expected cookie'); + end; + + [Test] + procedure TestRequestWithCookies() + var + RestClient: Codeunit "Rest Client"; + HttpRequestMessage: Codeunit "Http Request Message"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] A request message with cookies + HttpRequestMessage.SetRequestUri('https://httpbin.org/cookies'); + HttpRequestMessage.SetCookie('cookie1', 'value1'); + HttpRequestMessage.SetCookie('cookie2', 'value2'); + + // [WHEN] The Send method is called with a request message + HttpResponseMessage := RestClient.Send(HttpRequestMessage); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.cookies.cookie1').AsValue().AsText(), 'The response should contain the expected cookie1'); + Assert.AreEqual('value2', SelectJsonToken(JsonObject, '$.cookies.cookie2').AsValue().AsText(), 'The response should contain the expected cookie2'); + end; + + [Test] + procedure TestWithoutUseResponseCookies() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + JsonToken: JsonToken; + begin + // [SCENARIO] Test GET request without using response cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] Use response cookies is disabled + RestClient.SetUseResponseCookies(false); + + // [GIVEN] Specific cookies are set + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies/set?cookie1=value1'); + + // [WHEN] The cookies list is retrieved + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.IsFalse(JsonObject.SelectToken('$.cookies.cookie1', JsonToken), 'The response should not contain cookies'); + end; + + [Test] + procedure TestWithUseResponseCookies() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with using response cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] Use response cookies is enabled + RestClient.SetUseResponseCookies(true); + + // [GIVEN] Specific cookies are set + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies/set?cookie1=value1'); + + // [WHEN] The cookies list is retrieved + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.cookies.cookie1').AsValue().AsText(), 'The response should contain the expected cookie'); + end; + + [Test] + procedure TestUseResponseCookiesWithAdditionalCookies() + var + RestClient: Codeunit "Rest Client"; + HttpRequestMessage: Codeunit "Http Request Message"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with using response cookies and additional cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] Use response cookies is enabled + RestClient.SetUseResponseCookies(true); + + // [GIVEN] Specific cookies are set + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies/set?cookie1=value1'); + + // [WHEN] The cookies list is retrieved with additional cookies + HttpRequestMessage.SetRequestUri('https://httpbin.org/cookies'); + HttpRequestMessage.SetCookie('cookie2', 'value2'); + HttpResponseMessage := RestClient.Send(HttpRequestMessage); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'authenticated').AsValue().AsBoolean(), true, 'The response should contain the expected data'); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.cookies.cookie1').AsValue().AsText(), 'The response should contain the expected cookie1'); + Assert.AreEqual('value2', SelectJsonToken(JsonObject, '$.cookies.cookie2').AsValue().AsText(), 'The response should contain the expected cookie2'); end; local procedure GetJsonToken(JsonObject: JsonObject; Name: Text) JsonToken: JsonToken @@ -427,7 +635,7 @@ codeunit 134971 "Rest Client Tests" JsonObject.Get(Name, JsonToken); end; - local procedure SelectJsonToken(Path: Text; JsonObject: JsonObject) JsonToken: JsonToken + local procedure SelectJsonToken(JsonObject: JsonObject; Path: Text) JsonToken: JsonToken begin JsonObject.SelectToken(Path, JsonToken); end; diff --git a/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al b/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al index 379b36ccf1..4f89e806a0 100644 --- a/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al @@ -11,11 +11,62 @@ codeunit 134974 "Test Http Client Handler" implements "Http Client Handler" { SingleInstance = true; + var + MockConnectionFailed: Boolean; + MockIsBlockedByEnvironment: Boolean; + MockRequestFailed: Boolean; + procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; var ResponseMessage: HttpResponseMessage; begin + if MockConnectionFailed then begin + Success := false; + exit; + end; + + if MockIsBlockedByEnvironment then begin + Success := false; + HttpResponseMessage.SetIsBlockedByEnvironment(true); + exit; + end; + + if MockRequestFailed then begin + Success := true; + HttpResponseMessage.SetHttpStatusCode(400); + HttpResponseMessage.SetReasonPhrase('Bad Request'); + exit; + end; + Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); HttpResponseMessage.SetResponseMessage(ResponseMessage); end; + + procedure Initialize() + begin + MockConnectionFailed := false; + MockIsBlockedByEnvironment := false; + MockRequestFailed := false; + end; + + procedure SetMockConnectionFailed() + begin + MockConnectionFailed := true; + MockIsBlockedByEnvironment := false; + MockRequestFailed := false; + end; + + procedure SetMockIsBlockedByEnvironment() + begin + MockConnectionFailed := false; + MockIsBlockedByEnvironment := true; + MockRequestFailed := false; + end; + + procedure SetMockRequestFailed() + begin + MockConnectionFailed := false; + MockIsBlockedByEnvironment := false; + MockRequestFailed := true; + end; } \ No newline at end of file