diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/AWSJSON/AWSJSONErrorTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/AWSJSON/AWSJSONErrorTests.swift new file mode 100644 index 00000000000..d08401c6295 --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/AWSJSON/AWSJSONErrorTests.swift @@ -0,0 +1,119 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import SmithyTestUtil +import SmithyHTTPAPI +import Smithy +@_spi(SmithyReadWrite) import class SmithyJSON.Reader +@_spi(SmithyReadWrite) import struct AWSClientRuntime.AWSJSONError +@_spi(SmithyReadWrite) import enum ClientRuntime.BaseErrorDecodeError +import XCTest + +class AWSJSONErrorTests: HttpResponseTestBase { + // These error codes are taken from the examples in + // https://smithy.io/2.0/aws/protocols/aws-json-1_0-protocol.html#operation-error-serialization + // (one extra case with leading & trailing whitespace is added) + let errorCodes = [ + "FooError", + " FooError ", + "FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/", + "aws.protocoltests.restjson#FooError", + "aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" + ] + + // MARK: - error code decoding & sanitization + + func test_errorInitThrowsIfNoCode() async throws { + let httpResponse = try httpResponseWithNoErrorCode() + let reader = try await Reader.from(data: httpResponse.body.readData() ?? Data()) + XCTAssertThrowsError(try AWSJSONError(httpResponse: httpResponse, responseReader: reader, noErrorWrapping: true)) { error in + XCTAssertTrue((error as? BaseErrorDecodeError) == BaseErrorDecodeError.missingRequiredData) + } + } + + func test_sanitizeErrorCodeInHeader() async throws { + for errorCode in errorCodes { + let httpResponse = try httpResponseWithHeaderErrorCode(errorCode: errorCode) + let reader = try await Reader.from(data: httpResponse.body.readData() ?? Data()) + let awsJSONError = try AWSJSONError(httpResponse: httpResponse, responseReader: reader, noErrorWrapping: true) + XCTAssertEqual(awsJSONError.code, "FooError", "Error code '\(errorCode)' was not sanitized correctly, result was '\(awsJSONError.code)'") + } + } + + func test_sanitizeErrorCodeInCodeField() async throws { + for errorCode in errorCodes { + let httpResponse = try httpResponseWithCodeFieldErrorCode(errorCode: errorCode) + let reader = try await Reader.from(data: httpResponse.body.readData() ?? Data()) + let awsJSONError = try AWSJSONError(httpResponse: httpResponse, responseReader: reader, noErrorWrapping: true) + XCTAssertEqual(awsJSONError.code, "FooError", "Error code '\(errorCode)' was not sanitized correctly, result was '\(awsJSONError.code)'") + } + } + + func test_sanitizeErrorCodeInTypeField() async throws { + for errorCode in errorCodes { + let httpResponse = try httpResponseWithTypeFieldErrorCode(errorCode: errorCode) + let reader = try await Reader.from(data: httpResponse.body.readData() ?? Data()) + let awsJSONError = try AWSJSONError(httpResponse: httpResponse, responseReader: reader, noErrorWrapping: true) + XCTAssertEqual(awsJSONError.code, "FooError", "Error code '\(errorCode)' was not sanitized correctly, result was '\(awsJSONError.code)'") + } + } + + // MARK: - Private methods + + private func httpResponseWithNoErrorCode() throws -> HTTPResponse { + guard let response = buildHttpResponse( + code: 400, + headers: [ + "Content-Type": "application/json", + ], + content: ByteStream.data(Data("{}".utf8)) + ) else { + throw TestError("Something is wrong with the created http response") + } + return response + } + + private func httpResponseWithHeaderErrorCode(errorCode: String) throws -> HTTPResponse { + guard let response = buildHttpResponse( + code: 400, + headers: [ + "Content-Type": "application/json", + "X-Amzn-Errortype": errorCode, + ], + content: ByteStream.data(Data("{}".utf8)) + ) else { + throw TestError("Something is wrong with the created http response") + } + return response + } + + private func httpResponseWithCodeFieldErrorCode(errorCode: String) throws -> HTTPResponse { + guard let response = buildHttpResponse( + code: 400, + headers: [ + "Content-Type": "application/json", + ], + content: ByteStream.data(Data("{\"code\":\"\(errorCode)\"}".utf8)) + ) else { + throw TestError("Something is wrong with the created http response") + } + return response + } + + private func httpResponseWithTypeFieldErrorCode(errorCode: String) throws -> HTTPResponse { + guard let response = buildHttpResponse( + code: 400, + headers: [ + "Content-Type": "application/json", + ], + content: ByteStream.data(Data("{\"__type\":\"\(errorCode)\"}".utf8)) + ) else { + throw TestError("Something is wrong with the created http response") + } + return response + } +} diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/RestJSON/RestJSONErrorTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/RestJSON/RestJSONErrorTests.swift index 5fc75aa8652..f51af9d8262 100644 --- a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/RestJSON/RestJSONErrorTests.swift +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Protocols/RestJSON/RestJSONErrorTests.swift @@ -17,20 +17,7 @@ class RestJSONErrorTests: HttpResponseTestBase { let host = "myapi.host.com" func testRestJsonComplexError() async throws { - guard let httpResponse = buildHttpResponse( - code: 400, - headers: [ - "Content-Type": "application/json", - "X-Header": "Header", - "X-Amzn-Errortype": "ComplexError" - ], - content: ByteStream.data(""" - {\"TopLevel\": \"Top level\"} - """.data(using: .utf8)) - ) else { - XCTFail("Something is wrong with the created http response") - return - } + let httpResponse = try httpResponse(errorCode: "my.protocoltests.restjson#ComplexError:http://my.fake.com") let greetingWithErrorsError = try await GreetingWithErrorsError.httpError(from:)(httpResponse) @@ -47,16 +34,35 @@ class RestJSONErrorTests: HttpResponseTestBase { } } - func testSanitizeErrorName() { - let errorNames = [ + private func httpResponse(errorCode: String) throws -> HTTPResponse { + guard let response = buildHttpResponse( + code: 400, + headers: [ + "Content-Type": "application/json", + "X-Header": "Header", + "X-Amzn-Errortype": errorCode, + ], + content: ByteStream.data(Data("{\"TopLevel\": \"Top level\"}".utf8)) + ) else { + throw TestError("Something is wrong with the created http response") + } + return response + } + + func testSanitizeErrorName() async throws { + let errorCodes = [ + "FooError", " FooError ", - "FooError:http://my.fake.com/", - "my.protocoltests.restjson#FooError", - "my.protocoltests.restjson#FooError:http://my.fake.com" + "FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/", + "aws.protocoltests.restjson#FooError", + "aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" ] - for errorName in errorNames { - XCTAssertEqual(sanitizeErrorType(errorName), "FooError") + for errorCode in errorCodes { + let httpResponse = try httpResponse(errorCode: errorCode) + let reader = try await Reader.from(data: httpResponse.body.readData() ?? Data()) + let restJSONError = try RestJSONError(httpResponse: httpResponse, responseReader: reader, noErrorWrapping: true) + XCTAssertEqual(restJSONError.code, "FooError", "Error code '\(errorCode)' was not sanitized correctly, result was '\(restJSONError.code)'") } } } diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/TestError.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/TestError.swift new file mode 100644 index 00000000000..3538407285d --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/TestError.swift @@ -0,0 +1,12 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +struct TestError: Error { + let description: String + + init(_ description: String) { self.description = description } +}