krz/octosentry

macOS menu bar app to monitor GitHub security alerts

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

8821ac1cbecd3d0af12a9d337fbad7d51c7f50f3

signed_unknown_key

author: Christian Cleberg <hello@cleberg.net> · 2026-08-22T23:23:57Z
committer: <noreply@github.com>

Add tests for UpdateStore.isNewer (#49)

It decides whether anyone ever hears about a release and had no
coverage. Covers ordering, numeric rather than lexical comparison,
missing trailing components, a leading v, unparseable tags, and this
repo's real tag sequence.
 octosentryTests/UpdateStoreTests.swift | 83 ++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)

diff --git a/octosentryTests/UpdateStoreTests.swift b/octosentryTests/UpdateStoreTests.swift
new file mode 100644
index 0000000..5134099
--- /dev/null
+++ b/octosentryTests/UpdateStoreTests.swift
@@ -0,0 +1,83 @@
+//
+//  UpdateStoreTests.swift
+//  octosentryTests
+//
+//  isNewer decides whether anyone ever hears about a release: too strict and
+//  updates go unnoticed, too loose and every user is nagged to install what
+//  they already have.
+//
+
+import Foundation
+import Testing
+@testable import octosentry
+
+// isNewer is a pure function but inherits the target's MainActor isolation.
+@MainActor
+struct UpdateStoreTests {
+
+    // MARK: - Ordering
+
+    @Test func aNewerPatchMinorOrMajorIsOffered() {
+        #expect(UpdateStore.isNewer("1.1.1", than: "1.1.0"))
+        #expect(UpdateStore.isNewer("1.2.0", than: "1.1.9"))
+        #expect(UpdateStore.isNewer("2.0.0", than: "1.9.9"))
+    }
+
+    @Test func theSameVersionIsNotOffered() {
+        #expect(!UpdateStore.isNewer("1.1.1", than: "1.1.1"))
+        #expect(!UpdateStore.isNewer("1.0.0", than: "1.0.0"))
+    }
+
+    @Test func anOlderReleaseIsNotOffered() {
+        #expect(!UpdateStore.isNewer("1.1.0", than: "1.1.1"))
+        #expect(!UpdateStore.isNewer("1.0.1", than: "1.1.0"))
+        #expect(!UpdateStore.isNewer("0.7.0", than: "1.0.0"))
+    }
+
+    // A local build ahead of the latest release must not prompt for the
+    // release it already contains.
+    @Test func aBundleAheadOfTheLatestReleaseIsNotPrompted() {
+        #expect(!UpdateStore.isNewer("1.1", than: "1.1.1"))
+    }
+
+    // MARK: - Numeric, not lexical
+
+    // The comparison that a string compare gets wrong: "1.10" sorts before
+    // "1.9" as text, and after it as versions.
+    @Test func componentsCompareNumericallyNotAsText() {
+        #expect(UpdateStore.isNewer("1.10.0", than: "1.9.0"))
+        #expect(!UpdateStore.isNewer("1.9.0", than: "1.10.0"))
+        #expect(UpdateStore.isNewer("1.1.10", than: "1.1.9"))
+    }
+
+    // MARK: - Shapes the tag names actually take
+
+    @Test func missingTrailingComponentsCountAsZero() {
+        #expect(!UpdateStore.isNewer("1.1", than: "1.1.0"))
+        #expect(!UpdateStore.isNewer("1.1.0", than: "1.1"))
+        #expect(UpdateStore.isNewer("1.1.1", than: "1.1"))
+    }
+
+    @Test func aLeadingVIsIgnored() {
+        #expect(UpdateStore.isNewer("v1.1.1", than: "1.1.0"))
+        #expect(!UpdateStore.isNewer("v1.1.1", than: "1.1.1"))
+        #expect(UpdateStore.isNewer("v2.0.0", than: "v1.9.9"))
+    }
+
+    // Rather than crash or throw on a tag that isn't a version.
+    @Test func unparseableComponentsCountAsZero() {
+        #expect(!UpdateStore.isNewer("nightly", than: "1.1.1"))
+        #expect(UpdateStore.isNewer("1.1.1", than: "nightly"))
+    }
+
+    // MARK: - This repo's real tags
+
+    @Test func everyShippedTagIsNewerThanTheOneBefore() {
+        let shipped = ["0.1.0", "0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.7.0", "1.0.0", "1.0.1", "1.1", "1.1.1"]
+
+        for (earlier, later) in zip(shipped, shipped.dropFirst()) {
+            #expect(UpdateStore.isNewer(later, than: earlier), "\(later) should be newer than \(earlier)")
+            #expect(!UpdateStore.isNewer(earlier, than: later), "\(earlier) should not be newer than \(later)")
+        }
+    }
+}