api-swift / Tests / IrdAPITests / Core / ResponseDecoderTests.swift
463 words
  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")
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/api-swift.git