gitbayTests/WebhookTests.swift
153 lines · 6330 bytes
13 symbols in this file
makeClientargvOfstdinOfWebhookListViewModelTestsloadedlistsHooksWithTheirEventsAndSecretFlagnoHooksIsAnEmptyStateaddSendsSecretAndEventsOnlyWhenGivenremoveSendsTheIdThenReloadsaRefusedAddSurfacesAndReturnsFalseWebhookDeliveriesViewModelTestslistsDeliveriesWithStatusAndLastErrorredeliverSendsTheDeliveryIdThenReloads
1import Foundation
2import Testing
3@testable import gitbay
4
5private func makeClient() throws -> (GitbayClient, StubProtocol.Box) {
6 let box = StubProtocol.box()
7 let client = GitbayClient(
8 instance: try GitbayInstance(url: "https://gitbay.org"),
9 token: "test-token",
10 session: box.session()
11 )
12 return (client, box)
13}
14
15private func argvOf(_ seen: StubProtocol.Seen) throws -> [String] {
16 let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any])
17 return try #require(body["argv"] as? [String])
18}
19
20private func stdinOf(_ seen: StubProtocol.Seen) throws -> String? {
21 let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any])
22 return body["stdin"] as? String
23}
24
25private let okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"#
26
27private let hooksJSON = """
28 {"protocol_version":1,"data":[\
29 {"id":4,"url":"https://ci.example/hook","events":"*","active":true,"has_secret":true},\
30 {"id":9,"url":"https://chat.example/in","events":"push,issue.created","active":false,"has_secret":false}\
31 ],"exit_code":0}
32 """
33
34private let deliveriesJSON = """
35 {"protocol_version":1,"data":[\
36 {"id":71,"url":"https://ci.example/hook","event":"push","status":"delivered","attempts":1,"last_status":200},\
37 {"id":70,"url":"https://chat.example/in","event":"issue.created","status":"failed","attempts":3,\
38 "last_status":502,"last_error":"bad gateway"}\
39 ],"exit_code":0}
40 """
41
42@MainActor
43struct WebhookListViewModelTests {
44
45 private func loaded() async throws -> (WebhookListViewModel, StubProtocol.Box) {
46 let (client, stub) = try makeClient()
47 stub.enqueue(.init(status: 200, json: hooksJSON, match: "argv=list"))
48 let model = WebhookListViewModel(client: client, repoPath: "krz/gitbay")
49 await model.load()
50 return (model, stub)
51 }
52
53 @Test func listsHooksWithTheirEventsAndSecretFlag() async throws {
54 let (model, stub) = try await loaded()
55
56 let hooks = try #require(model.state.value)
57 #expect(hooks.map(\.id) == [4, 9])
58 #expect(hooks[0].hasSecret && hooks[0].active)
59 #expect(hooks[1].events == "push,issue.created")
60 #expect(stub.seen.first?.url.query() == "argv=webhook&argv=list&argv=krz/gitbay")
61 }
62
63 @Test func noHooksIsAnEmptyState() async throws {
64 let (client, stub) = try makeClient()
65 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":[],"exit_code":0}"#))
66 let model = WebhookListViewModel(client: client, repoPath: "krz/gitbay")
67
68 await model.load()
69
70 guard case .empty = model.state else {
71 Issue.record("expected .empty, got \(model.state)")
72 return
73 }
74 }
75
76 @Test func addSendsSecretAndEventsOnlyWhenGiven() async throws {
77 let (model, stub) = try await loaded()
78 for _ in 0..<2 {
79 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"id":10,"url":"u","events":"*"},"exit_code":0}"#, match: "cmd"))
80 stub.enqueue(.init(status: 200, json: hooksJSON, match: "argv=list"))
81 }
82
83 let first = await model.add(url: " https://a.example/h ", secret: "s3", events: "push")
84 let second = await model.add(url: "https://b.example/h", secret: "", events: "")
85
86 #expect(first && second)
87 let writes = try stub.seen.filter { $0.method == "POST" }.map(argvOf)
88 #expect(writes == [
89 ["webhook", "add", "krz/gitbay", "https://a.example/h", "--secret", "-", "--events", "push"],
90 ["webhook", "add", "krz/gitbay", "https://b.example/h"],
91 ])
92 let posts = stub.seen.filter { $0.method == "POST" }
93 #expect(try posts.map(stdinOf) == ["s3\n", nil])
94 }
95
96 @Test func removeSendsTheIdThenReloads() async throws {
97 let (model, stub) = try await loaded()
98 stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
99 stub.enqueue(.init(status: 200, json: hooksJSON, match: "argv=list"))
100
101 await model.remove(id: 9)
102
103 let write = try #require(stub.seen.first { $0.method == "POST" })
104 #expect(try argvOf(write) == ["webhook", "remove", "krz/gitbay", "9"])
105 #expect(stub.seen.filter { $0.method == "GET" }.count == 2)
106 }
107
108 @Test func aRefusedAddSurfacesAndReturnsFalse() async throws {
109 let (model, stub) = try await loaded()
110 stub.enqueue(.init(status: 403, json: #"{"protocol_version":1,"error":"admin access required","exit_code":4}"#, match: "cmd"))
111
112 let ok = await model.add(url: "https://a.example/h", secret: "", events: "")
113
114 #expect(!ok)
115 #expect(model.actionError == "admin access required")
116 }
117}
118
119@MainActor
120struct WebhookDeliveriesViewModelTests {
121
122 @Test func listsDeliveriesWithStatusAndLastError() async throws {
123 let (client, stub) = try makeClient()
124 stub.enqueue(.init(status: 200, json: deliveriesJSON))
125 let model = WebhookDeliveriesViewModel(client: client, repoPath: "krz/gitbay")
126
127 await model.load()
128
129 let rows = try #require(model.state.value)
130 #expect(rows.map(\.id) == [71, 70])
131 #expect(rows[1].status == "failed")
132 #expect(rows[1].lastStatus == 502)
133 #expect(rows[1].lastError == "bad gateway")
134 #expect(rows[0].lastError == nil)
135 #expect(stub.seen.first?.url.query() == "argv=webhook&argv=deliveries&argv=krz/gitbay")
136 }
137
138 @Test func redeliverSendsTheDeliveryIdThenReloads() async throws {
139 let (client, stub) = try makeClient()
140 stub.enqueue(.init(status: 200, json: deliveriesJSON, match: "argv=deliveries"))
141 let model = WebhookDeliveriesViewModel(client: client, repoPath: "krz/gitbay")
142 await model.load()
143 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"requeued":70},"exit_code":0}"#, match: "cmd"))
144 stub.enqueue(.init(status: 200, json: deliveriesJSON, match: "argv=deliveries"))
145
146 await model.redeliver(id: 70)
147
148 let write = try #require(stub.seen.first { $0.method == "POST" })
149 #expect(try argvOf(write) == ["webhook", "redeliver", "krz/gitbay", "70"])
150 #expect(stub.seen.filter { $0.method == "GET" }.count == 2)
151 #expect(model.actionError == nil)
152 }
153}