1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 | import Foundation
import Testing
@testable import IrdAPI
@Suite struct ResponseDecoderTests {
@Test func successJSONKeepsWireNamesAndMoneyStrings() throws {
let (_, v) = try ResponseDecoder.decode(status: 200, body: Data(#"{"total":"12.50","credit_type":"eur"}"#.utf8))
#expect(v["total"] == "12.50")
#expect(v["credit_type"] == "eur")
}
@Test func acceptedIsASuccess() throws {
let (_, v) = try ResponseDecoder.decode(status: 202, body: Data(#"{"action":{"id":"a1"}}"#.utf8))
#expect(v.at(["action", "id"]) == "a1")
}
@Test func emptySuccessBecomesEmptyObject() throws {
let (data, v) = try ResponseDecoder.decode(status: 204, body: Data())
#expect(v == .object([:]))
#expect(String(decoding: data, as: UTF8.self) == "{}")
let (_, w) = try ResponseDecoder.decode(status: 200, body: Data(" \n".utf8))
#expect(w == .object([:]))
}
@Test func errorEnvelopeWithCodeAndMessage() {
let body = #"{"error":{"code":"insufficient_credits","message":"Not enough credits"}}"#
#expect(throws: IrdError.api(status: 402, code: "insufficient_credits", message: "Not enough credits",
body: try! JSONValue.parse(Data(body.utf8)))) {
_ = try ResponseDecoder.decode(status: 402, body: Data(body.utf8))
}
}
@Test func bareStringEnvelopeUsesTheCodeAsMessage() {
let body = #"{"error":"message_too_large"}"#
#expect(throws: IrdError.api(status: 413, code: "message_too_large", message: "message_too_large",
body: try! JSONValue.parse(Data(body.utf8)))) {
_ = try ResponseDecoder.decode(status: 413, body: Data(body.utf8))
}
}
@Test func pinningServiceEnvelopeMapsReasonAndDetails() {
let body = #"{"error":{"reason":"NOT_FOUND","details":"no such request"}}"#
#expect(throws: IrdError.api(status: 404, code: "NOT_FOUND", message: "no such request",
body: try! JSONValue.parse(Data(body.utf8)))) {
_ = try ResponseDecoder.decode(status: 404, body: Data(body.utf8))
}
}
@Test func plainTextErrorKeepsTheText() {
#expect(throws: IrdError.api(status: 503, code: nil, message: "upstream unavailable", body: nil)) {
_ = try ResponseDecoder.decode(status: 503, body: Data("upstream unavailable\n".utf8))
}
}
@Test func emptyErrorIsNamedAsSuch() {
#expect(throws: IrdError.api(status: 500, code: nil, message: "Empty error response", body: nil)) {
_ = try ResponseDecoder.decode(status: 500, body: Data())
}
}
@Test func nonJSONSuccessIsATransportError() {
#expect(throws: IrdError.transport("Failed to parse JSON response")) {
_ = try ResponseDecoder.decode(status: 200, body: Data("<html>".utf8))
}
}
@Test func transportFailureSurfacesAsTransport() async {
let http = FakeHTTP { _ in throw IrdError.transport("The Internet connection appears to be offline.") }
var c = Configuration(apiKey: "ird_k")
c.transport = http
let e = await caught { _ = try await IrdClient(configuration: c).raw.request(method: .get, path: "/domains/example") }
#expect(e == .transport("The Internet connection appears to be offline."))
}
@Test func typedDecodeMismatchIsATransportError() async {
struct Balance: Decodable { var total: Int }
let (client, _) = captureClient(script: [json(200, #"{"total":"12.50"}"#)])
let op = IrdOperation(method: .get, pathTemplate: "/credits/balance", module: "credits", name: "balance", security: .keyOrJwt)
let e = await caught { let _: Balance = try await client.request(op) }
if case .transport(let detail)? = e { #expect(detail.hasPrefix("decoding Balance")) } else { Issue.record("expected transport, got \(String(describing: e))") }
}
@Test func typedDecodeReadsTheOriginalBytesSoBigIntegersSurvive() async throws {
struct Seq: Decodable { var seq: UInt64 }
let (client, _) = captureClient(script: [json(200, #"{"seq":1874163641836634113}"#)])
let op = IrdOperation(method: .get, pathTemplate: "/x", module: "raw", name: "x", security: .keyOrJwt)
let s: Seq = try await client.request(op)
#expect(s.seq == 1_874_163_641_836_634_113)
}
@Test func jsonValueRoundTripsAndAccessors() throws {
let v = try JSONValue.parse(Data(#"{"a":[1,2.5,"s",null,true],"b":{"c":-0}}"#.utf8))
#expect(v["a"]?[0]?.intValue == 1)
#expect(v["a"]?[1]?.doubleValue == 2.5)
#expect(v["a"]?[1]?.intValue == nil)
#expect(v["a"]?[3]?.isNull == true)
#expect(v["a"]?[4]?.boolValue == true)
#expect(v.at(["b", "c"])?.intValue == 0)
let again = try JSONValue.parse(try v.serialized())
#expect(again == v)
#expect(String(decoding: try JSONValue.number(3).serialized(), as: UTF8.self) == "3")
}
}
|