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}