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