api-swift / Tests / IrdAPITests / Gate / DomainGateTests.swift
454 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
import Foundation
import Testing
@testable import IrdAPI

let registrationsPage = json(200, #"{"registrations":[{"id":"r1","domain":"Prod.RO"},{"id":"r2","domain":"dev.ro"}],"next_cursor":null}"#)

func gatedClient(rules: GateRules, script: [HTTPResponse], apiKey: String? = "ird_k") async throws -> (IrdClient, FakeHTTP) {
    let http = FakeHTTP(script: [registrationsPage] + script)
    var c = Configuration()
    if let apiKey { c.credential = StaticCredential(apiKey) }
    c.transport = http
    c.clock = InstantClock()
    let client = try await IrdClient.gated(c, rules: rules)
    return (client, http)
}

@Suite struct DomainGateTests {
    @Test func indexIsBuiltEagerlyWithCursorPagination() async throws {
        let http = FakeHTTP(script: [
            json(200, #"{"registrations":[{"id":"r1","domain":"a.ro"}],"next_cursor":"c2"}"#),
            json(200, #"{"registrations":[{"id":"r2","domain":"B.RO."}],"next_cursor":null}"#),
        ])
        var c = Configuration(apiKey: "ird_k")
        c.transport = http
        let client = try await IrdClient.gated(c, rules: GateRules(deniedDomains: ["b.ro"]))
        #expect(http.trail == ["GET /domains/registrations?limit=100", "GET /domains/registrations?limit=100&cursor=c2"])
        #expect(await client.gate?.registrationIndex == ["r1": "a.ro", "r2": "b.ro"])
        #expect(client.isGated)
    }

    @Test func registrationIdsResolveThroughTheCacheWithoutARequest() async throws {
        let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["prod.ro"]), script: [])
        let before = http.count
        let e = await caught { try await client.checkGate(op: "POST /domains/registrations/{id}/renew", target: .registrationId("r1")) }
        #expect(e == .domainDenied(domain: "prod.ro", pattern: "prod.ro", op: "POST /domains/registrations/{id}/renew"))
        try await client.checkGate(op: "x", target: .registrationId("r2"))
        #expect(http.count == before)
    }

    @Test func cacheMissFetchesOnceThenCaches() async throws {
        let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["new.ro"]),
                                                   script: [json(200, #"{"registration":{"id":"r9","domain":"New.RO"}}"#)])
        let e = await caught { try await client.checkGate(op: "op", target: .registrationId("r9")) }
        #expect(e == .domainDenied(domain: "new.ro", pattern: "new.ro", op: "op"))
        #expect(http.trail.last == "GET /domains/registrations/r9")
        let n = http.count
        _ = await caught { try await client.checkGate(op: "op", target: .registrationId("r9")) }
        #expect(http.count == n)
    }

    @Test func unknownIdPassesUnderDenyListAndFailsClosedUnderAllowList() async throws {
        let (deny, _) = try await gatedClient(rules: GateRules(deniedDomains: ["prod.ro"]), script: [json(404, #"{"error":{"code":"not_found","message":"no"}}"#)])
        try await deny.checkGate(op: "op", target: .registrationId("nope"))
        let (allow, _) = try await gatedClient(rules: GateRules(allowedDomains: ["prod.ro"]), script: [json(404, #"{"error":{"code":"not_found","message":"no"}}"#)])
        let e = await caught { try await allow.checkGate(op: "op", target: .registrationId("nope")) }
        #expect(e == .domainDenied(domain: nil, pattern: nil, op: "op"))
    }

    @Test func gateRequiresACredential() async {
        var c = Configuration()
        c.transport = FakeHTTP(script: [registrationsPage])
        let e = await caught { _ = try await IrdClient.gated(c, rules: GateRules(deniedDomains: ["x.ro"])) }
        #expect(e == .config("Domain gate options require an authenticated client (apiKey)"))
    }

    @Test func gatedMutationIsRefusedBeforeAnyRequest() async throws {
        let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["*.ro"]), script: [])
        let n = http.count
        let e = await caught { _ = try await client.raw.request(method: .post, path: "/tunnels", body: ["hostname": "dev.x.ro"]) }
        #expect(e == .domainDenied(domain: "dev.x.ro", pattern: "*.ro", op: "POST /tunnels"))
        #expect(http.count == n)
    }

    @Test func ungatedClientsSkipTheGateEntirely() async throws {
        let (client, http) = captureClient()
        _ = try await client.raw.request(method: .post, path: "/tunnels", body: ["hostname": "dev.x.ro"])
        #expect(http.trail == ["POST /tunnels"])
        #expect(!client.isGated)
        try await client.refreshGate()
        try await client.checkGate(op: "x", target: .domain("anything"))
        #expect(http.count == 1)
    }

    @Test func refreshPicksUpNewRegistrations() async throws {
        let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["prod.ro"]),
                                                   script: [json(200, #"{"registrations":[{"id":"r3","domain":"c.ro"}],"next_cursor":null}"#)])
        try await client.refreshGate()
        #expect(http.trail.last == "GET /domains/registrations?limit=100")
        #expect(await client.gate?.registrationIndex == ["r3": "c.ro"])
    }
}

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