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
104
105 | import Foundation
import Testing
@testable import IrdAPI
@Suite struct GatedOperationsTests {
/// The gated mutations of the sibling TypeScript client, verbatim.
static let expected: Set<String> = [
"POST /domains/registrations", "POST /domains/transfers-in", "POST /domains/trades-in", "POST /domains/adoptions",
"PUT /dns/domains/{domain}/records", "DELETE /dns/domains/{domain}/records", "POST /dns/claims",
"POST /tunnels", "POST /ipfs/domains", "POST /p2p/apps/{id}/domains", "POST /vps/instances/{id}/domains",
"POST /domains/registrations/{id}/renew", "PUT /domains/registrations/{id}/auto-renew", "PUT /domains/registrations/{id}/sensitive",
"PUT /domains/registrations/{id}/nameservers", "POST /domains/registrations/{id}/hosts", "PUT /domains/registrations/{id}/hosts/{host}",
"DELETE /domains/registrations/{id}/hosts/{host}", "POST /domains/registrations/{id}/dsdata", "DELETE /domains/registrations/{id}/dsdata",
"POST /domains/registrations/{id}/transfer-out", "PUT /domains/registrations/{id}/registrant",
"POST /domains/registrations/{id}/registrant-email/start", "POST /domains/registrations/{id}/registrant-email/confirm",
"POST /domains/registrations/{id}/registrant-email/adopt", "POST /domains/registrations/{id}/trade",
"POST /domains/registrations/{id}/dns", "DELETE /domains/registrations/{id}/dns", "POST /domains/registrations/{id}/dns/dnssec",
"DELETE /domains/registrations/{id}/dns/dnssec", "POST /domains/registrations/{id}/dns/nameservers",
"POST /domains/registrations/{id}/grants", "DELETE /domains/registrations/{id}/grants/{grant-id}",
"POST /domains/actions/{id}/cancel", "DELETE /dns/claims/{id}", "PUT /dns/claims/{id}/seed", "DELETE /dns/claims/{id}/seed",
"POST /ipfs/domains/{id}/pins", "DELETE /ipfs/domains/{id}", "DELETE /p2p/apps/{id}/domains/{domain-id}",
"DELETE /tunnels/{id}", "PATCH /tunnels/{id}", "DELETE /vps/instances/{id}/domains/{connection-id}",
]
@Test func tableMatchesTheSiblingClientsAndTheSpec() {
let keys = Set(GatedOperations.table.keys)
#expect(keys == Self.expected, "\(keys.symmetricDifference(Self.expected).sorted())")
#expect(keys.count == 43)
let spec = Spec.operations
for k in keys {
#expect(spec.contains(k), "\(k) is not a spec operation")
#expect(!k.hasPrefix("GET "), "reads are never gated: \(k)")
}
for case .resolve(let r) in GatedOperations.table.values {
#expect(spec.contains(r.read.key), "resolve read \(r.read.key) is not a spec operation")
}
}
@Test func gatewayDomainMutationsResolveThenCheck() async throws {
let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["gw.x.ro"]),
script: [json(200, #"{"domain":{"id":"gw-1","domain":"gw.x.ro"}}"#)])
let start = http.count
let e = await caught { _ = try await client.raw.request(method: .post, path: "/ipfs/domains/{id}/pins", params: ["id": "gw-1"], body: ["add": ["p2"]]) }
#expect(e == .domainDenied(domain: "gw.x.ro", pattern: "gw.x.ro", op: "POST /ipfs/domains/{id}/pins"))
#expect(Array(http.trail[start...]) == ["GET /ipfs/domains/gw-1"])
let (ok, http2) = try await gatedClient(rules: GateRules(deniedDomains: ["other.ro"]),
script: [json(200, #"{"domain":{"id":"gw-1","domain":"gw.x.ro"}}"#), json(200, #"{"domain":{"id":"gw-1"}}"#)])
let s2 = http2.count
_ = try await ok.raw.request(method: .post, path: "/ipfs/domains/{id}/pins", params: ["id": "gw-1"], body: ["add": ["p2"]])
#expect(Array(http2.trail[s2...]) == ["GET /ipfs/domains/gw-1", "POST /ipfs/domains/gw-1/pins"])
#expect(bodyJSON(http2.last) == ["add": ["p2"]])
}
@Test func claimMutationsResolveThroughTheClaim() async throws {
let (client, http) = try await gatedClient(rules: GateRules(allowedDomains: ["example.com"]),
script: [json(200, #"{"claim":{"id":"c1","domain":"example.com"}}"#), json(200, "{}")])
let s = http.count
_ = try await client.raw.request(method: .put, path: "/dns/claims/{id}/seed", params: ["id": "c1"], body: ["snapshot_id": "s1"])
#expect(Array(http.trail[s...]) == ["GET /dns/claims/c1", "PUT /dns/claims/c1/seed"])
let (denied, http2) = try await gatedClient(rules: GateRules(allowedDomains: ["example.com"]),
script: [json(200, #"{"claim":{"id":"c2","domain":"other.com"}}"#)])
let s2 = http2.count
let e = await caught { _ = try await denied.raw.request(method: .delete, path: "/dns/claims/{id}", params: ["id": "c2"]) }
#expect(e == .domainDenied(domain: "other.com", pattern: nil, op: "DELETE /dns/claims/{id}"))
#expect(Array(http2.trail[s2...]) == ["GET /dns/claims/c2"])
}
@Test func cancelActionResolvesTheRegistrationId() async throws {
let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["prod.ro"]),
script: [json(200, #"{"action":{"id":"a1","registration_id":"r1"}}"#)])
let s = http.count
let e = await caught { _ = try await client.raw.request(method: .post, path: "/domains/actions/{id}/cancel", params: ["id": "a1"]) }
#expect(e == .domainDenied(domain: "prod.ro", pattern: "prod.ro", op: "POST /domains/actions/{id}/cancel"))
#expect(Array(http.trail[s...]) == ["GET /domains/actions/a1"])
}
@Test func detachRowsAreFoundByIdInTheListing() async throws {
let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["p2p.x.ro"]),
script: [json(200, #"{"domains":[{"id":"d0","hostname":"a.ro"},{"id":"d1","hostname":"p2p.x.ro"}]}"#)])
let s = http.count
let e = await caught { _ = try await client.raw.request(method: .delete, path: "/p2p/apps/{id}/domains/{domain-id}", params: ["id": "a1", "domain-id": "d1"]) }
#expect(e == .domainDenied(domain: "p2p.x.ro", pattern: "p2p.x.ro", op: "DELETE /p2p/apps/{id}/domains/{domain-id}"))
#expect(Array(http.trail[s...]) == ["GET /p2p/apps/a1/domains"])
let (vps, http2) = try await gatedClient(rules: GateRules(deniedDomains: ["x.ro"]),
script: [json(200, #"{"connections":[{"id":"c1","zone_origin":"x.ro"}]}"#)])
let e2 = await caught { _ = try await vps.raw.request(method: .delete, path: "/vps/instances/{id}/domains/{connection-id}", params: ["id": "i1", "connection-id": "c1"]) }
#expect(e2 == .domainDenied(domain: "x.ro", pattern: "x.ro", op: "DELETE /vps/instances/{id}/domains/{connection-id}"))
#expect(http2.trail.last == "GET /vps/instances/i1/domains")
}
@Test func pathParamAndBodyFieldSources() async throws {
let (client, http) = try await gatedClient(rules: GateRules(deniedDomains: ["x.ro"]), script: [])
let s = http.count
let e = await caught { _ = try await client.raw.request(method: .put, path: "/dns/domains/{domain}/records", params: ["domain": "X.RO"], body: ["record": [:]]) }
#expect(e == .domainDenied(domain: "x.ro", pattern: "x.ro", op: "PUT /dns/domains/{domain}/records"))
let e2 = await caught { _ = try await client.raw.request(method: .post, path: "/vps/instances/{id}/domains", params: ["id": "i1"], body: ["zone": "x.ro"]) }
#expect(e2 == .domainDenied(domain: "x.ro", pattern: "x.ro", op: "POST /vps/instances/{id}/domains"))
#expect(http.count == s)
_ = try await client.raw.request(method: .post, path: "/vps/instances/{id}/domains", params: ["id": "i1"], body: ["zone": "y.ro"])
#expect(http.trail.last == "POST /vps/instances/i1/domains")
}
}
|