krz/octosentry
macOS menu bar app to monitor GitHub security alerts
clone: git clone https://gitbay.org/krz/octosentry.git
1.1.1: octosentryTests/AlertDiffTests.swift · raw
1//
2// AlertDiffTests.swift
3// octosentryTests
4//
5
6import Foundation
7import Testing
8@testable import octosentry
9
10struct AlertDiffTests {
11
12 private let repo = "octocat/hello-world"
13
14 private func fetched(_ ids: [String], severity: SecurityEventSeverity = .high) -> [String: [SecurityEvent]] {
15 [repo: ids.map { TestEvents.event(id: $0, repo: repo, severity: severity) }]
16 }
17
18 // MARK: - Seeding
19
20 // The first sync of a busy repo is the case that would otherwise produce a
21 // notification storm.
22 @Test func firstEverSyncNotifiesAboutNothing() {
23 let new = AlertDiff.newlyAppeared(
24 in: fetched(["a", "b", "c"]),
25 baseline: nil,
26 minimumSeverity: .low
27 )
28
29 #expect(new.isEmpty)
30 }
31
32 @Test func newlyWatchedRepoIsSeededQuietly() {
33 let new = AlertDiff.newlyAppeared(
34 in: fetched(["a", "b"]),
35 baseline: ["octocat/other": ["z"]],
36 minimumSeverity: .low
37 )
38
39 #expect(new.isEmpty)
40 }
41
42 // MARK: - Detecting new alerts
43
44 @Test func reportsOnlyAlertsMissingFromTheBaseline() {
45 let new = AlertDiff.newlyAppeared(
46 in: fetched(["a", "b", "c"]),
47 baseline: [repo: ["a", "b"]],
48 minimumSeverity: .low
49 )
50
51 #expect(new.map(\.id) == ["c"])
52 }
53
54 @Test func reportsNothingWhenTheFeedIsUnchanged() {
55 let new = AlertDiff.newlyAppeared(
56 in: fetched(["a", "b"]),
57 baseline: [repo: ["a", "b"]],
58 minimumSeverity: .low
59 )
60
61 #expect(new.isEmpty)
62 }
63
64 // An alert that disappeared and came back is reported again — GitHub
65 // re-opening it is worth knowing about.
66 @Test func reappearingAlertIsReportedAgain() {
67 let new = AlertDiff.newlyAppeared(
68 in: fetched(["a"]),
69 baseline: [repo: ["b"]],
70 minimumSeverity: .low
71 )
72
73 #expect(new.map(\.id) == ["a"])
74 }
75
76 // MARK: - Severity threshold
77
78 @Test func respectsTheMinimumSeverityThreshold() {
79 let events = [repo: [
80 TestEvents.event(id: "low", repo: repo, severity: .low),
81 TestEvents.event(id: "critical", repo: repo, severity: .critical),
82 ]]
83
84 let new = AlertDiff.newlyAppeared(in: events, baseline: [repo: []], minimumSeverity: .high)
85
86 #expect(new.map(\.id) == ["critical"])
87 }
88
89 @Test func thresholdAdmitsAlertsExactlyAtTheFloor() {
90 let new = AlertDiff.newlyAppeared(
91 in: fetched(["a"], severity: .high),
92 baseline: [repo: []],
93 minimumSeverity: .high
94 )
95
96 #expect(new.map(\.id) == ["a"])
97 }
98
99 // MARK: - Ordering
100
101 @Test func resultsAreOrderedBySeverityThenID() {
102 let events = [
103 "b/repo": [TestEvents.event(id: "2", repo: "b/repo", severity: .medium)],
104 "a/repo": [
105 TestEvents.event(id: "3", repo: "a/repo", severity: .critical),
106 TestEvents.event(id: "1", repo: "a/repo", severity: .critical),
107 ],
108 ]
109 let baseline = ["a/repo": Set<String>(), "b/repo": Set<String>()]
110
111 let new = AlertDiff.newlyAppeared(in: events, baseline: baseline, minimumSeverity: .low)
112
113 #expect(new.map(\.id) == ["1", "3", "2"])
114 }
115
116 // MARK: - Baseline maintenance
117
118 @Test func baselineTakesFreshIDsForReposThatAnswered() {
119 let baseline = AlertDiff.updatedBaseline(
120 from: fetched(["a", "b"]),
121 watchedRepos: [repo],
122 previous: [repo: ["old"]]
123 )
124
125 #expect(baseline[repo] == ["a", "b"])
126 }
127
128 // The case that would otherwise re-announce everything after a blip.
129 @Test func baselineKeepsEntriesForReposThatFailedToFetch() {
130 let baseline = AlertDiff.updatedBaseline(
131 from: [:],
132 watchedRepos: [repo],
133 previous: [repo: ["a", "b"]]
134 )
135
136 #expect(baseline[repo] == ["a", "b"])
137 }
138
139 @Test func baselineDropsUnwatchedRepos() {
140 let baseline = AlertDiff.updatedBaseline(
141 from: [:],
142 watchedRepos: ["octocat/kept"],
143 previous: ["octocat/kept": ["a"], "octocat/removed": ["b"]]
144 )
145
146 #expect(Set(baseline.keys) == ["octocat/kept"])
147 }
148
149 @Test func baselineStartsFromNothingWhenThereIsNoPrevious() {
150 let baseline = AlertDiff.updatedBaseline(
151 from: fetched(["a"]),
152 watchedRepos: [repo],
153 previous: nil
154 )
155
156 #expect(baseline == [repo: ["a"]])
157 }
158
159 // Seed once, then the same alerts are no longer new.
160 @Test func seedingThenPollingReportsOnlyWhatArrivedAfterwards() {
161 let firstPoll = fetched(["a", "b"])
162 let seeded = AlertDiff.updatedBaseline(from: firstPoll, watchedRepos: [repo], previous: nil)
163 #expect(AlertDiff.newlyAppeared(in: firstPoll, baseline: nil, minimumSeverity: .low).isEmpty)
164
165 let secondPoll = fetched(["a", "b", "c"])
166 let new = AlertDiff.newlyAppeared(in: secondPoll, baseline: seeded, minimumSeverity: .low)
167
168 #expect(new.map(\.id) == ["c"])
169 }
170}