krz/octosentry

macOS menu bar app to monitor GitHub security alerts

clone: git clone https://gitbay.org/krz/octosentry.git

1.1: octosentryTests/AccountTests.swift · raw

  1//
  2//  AccountTests.swift
  3//  octosentryTests
  4//
  5
  6import Foundation
  7import Testing
  8@testable import octosentry
  9
 10struct AccountTests {
 11
 12    // An install that already has a token must keep using the Keychain item
 13    // it's in  rewriting it at upgrade time risks stranding the token.
 14    @Test func theLegacyAccountPointsAtTheExistingKeychainItem() {
 15        let account = Account.legacy()
 16
 17        #expect(account.keychainAccount == KeychainTokenStore.legacyAccount)
 18        #expect(account.id == 0)
 19        #expect(account.login.isEmpty)
 20    }
 21
 22    @Test func newAccountsGetTheirOwnKeychainItem() {
 23        let first = Account.new(id: 1, login: "octocat", hasRepoScope: false)
 24        let second = Account.new(id: 2, login: "hubot", hasRepoScope: true)
 25
 26        #expect(first.keychainAccount != second.keychainAccount)
 27        #expect(first.keychainAccount != KeychainTokenStore.legacyAccount)
 28        #expect(second.hasRepoScope)
 29    }
 30
 31    @Test func displayNameFallsBackBeforeTheLoginIsKnown() {
 32        #expect(Account.legacy().displayName == "GitHub account")
 33        #expect(Account.new(id: 1, login: "octocat", hasRepoScope: false).displayName == "octocat")
 34    }
 35
 36    @Test func roundTripsThroughCodable() throws {
 37        let account = Account.new(id: 7, login: "octocat", hasRepoScope: true)
 38        let decoded = try JSONDecoder().decode(Account.self, from: try JSONEncoder().encode(account))
 39
 40        #expect(decoded == account)
 41    }
 42
 43    @Test func watchedRepoRoundTripsThroughCodable() throws {
 44        let repo = WatchedRepo(fullName: "octocat/hello-world", accountID: 7)
 45        let decoded = try JSONDecoder().decode(WatchedRepo.self, from: try JSONEncoder().encode(repo))
 46
 47        #expect(decoded == repo)
 48    }
 49}
 50
 51struct MultiAccountFeedTests {
 52
 53    private func event(_ id: String, repo: String = "octocat/hello-world", logins: [String]) -> SecurityEvent {
 54        var event = TestEvents.event(id: id, repo: repo)
 55        event.accountLogins = logins
 56        return event
 57    }
 58
 59    // MARK: - Merging
 60
 61    // The same alert reached through two identities is one alert.
 62    @Test func duplicateAlertsCollapseAndUnionTheirAttributions() {
 63        let merged = SecurityEventStore.merged([
 64            event("a", logins: ["octocat"]),
 65            event("a", logins: ["hubot"]),
 66        ])
 67
 68        #expect(merged.count == 1)
 69        #expect(merged[0].accountLogins.sorted() == ["hubot", "octocat"])
 70    }
 71
 72    @Test func mergingLeavesDistinctAlertsAlone() {
 73        let merged = SecurityEventStore.merged([
 74            event("a", logins: ["octocat"]),
 75            event("b", logins: ["octocat"]),
 76        ])
 77
 78        #expect(merged.map(\.id) == ["a", "b"])
 79    }
 80
 81    @Test func mergingPreservesInputOrder() {
 82        let merged = SecurityEventStore.merged([
 83            event("b", logins: ["octocat"]),
 84            event("a", logins: ["hubot"]),
 85            event("b", logins: ["hubot"]),
 86        ])
 87
 88        #expect(merged.map(\.id) == ["b", "a"])
 89    }
 90
 91    @Test func mergingDoesNotRepeatTheSameLogin() {
 92        let merged = SecurityEventStore.merged([
 93            event("a", logins: ["octocat"]),
 94            event("a", logins: ["octocat"]),
 95        ])
 96
 97        #expect(merged[0].accountLogins == ["octocat"])
 98    }
 99
100    @Test func mergingAnEmptyFeedIsEmpty() {
101        #expect(SecurityEventStore.merged([]).isEmpty)
102    }
103
104    // MARK: - Attribution
105
106    // Attribution is noise unless a repo is genuinely reachable two ways.
107    @Test func onlyReposWatchedUnderSeveralAccountsAreAttributed() {
108        let watched = [
109            WatchedRepo(fullName: "octocat/shared", accountID: 1),
110            WatchedRepo(fullName: "octocat/shared", accountID: 2),
111            WatchedRepo(fullName: "octocat/solo", accountID: 1),
112        ]
113
114        let attributed = SecurityEventStore.reposWatchedBySeveralAccounts(in: watched)
115
116        #expect(attributed == ["octocat/shared"])
117    }
118
119    @Test func aRepoListedTwiceUnderOneAccountIsNotAttributed() {
120        let watched = [
121            WatchedRepo(fullName: "octocat/solo", accountID: 1),
122            WatchedRepo(fullName: "octocat/solo", accountID: 1),
123        ]
124
125        #expect(SecurityEventStore.reposWatchedBySeveralAccounts(in: watched).isEmpty)
126    }
127
128    @Test func anEmptyWatchListAttributesNothing() {
129        #expect(SecurityEventStore.reposWatchedBySeveralAccounts(in: []).isEmpty)
130    }
131}