Przeglądaj źródła

Test: GlucoseAlert safety gate and decoder defaults

Pin shouldEvaluate (urgent-low always evaluates regardless of isEnabled;
others gated on isEnabled) and the custom decoder's backward-compatible
defaults (missing isEnabled→true; sound/playsSound/overrides/activeOption→
type defaults) plus a lossless round-trip. Guards a stored config from
silently disabling urgent-low or shifting an alarm's behavior on upgrade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
James Woglom 2 tygodni temu
rodzic
commit
7b7002cd82
2 zmienionych plików z 140 dodań i 0 usunięć
  1. 4 0
      Trio.xcodeproj/project.pbxproj
  2. 136 0
      TrioTests/GlucoseAlertTests.swift

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -237,6 +237,7 @@
 		38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3D525E8FDF40078B0D1 /* MD5.swift */; };
 		38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3D525E8FDF40078B0D1 /* MD5.swift */; };
 		38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */; };
 		38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */; };
 		BD11A001000000000000A002 /* AlertMuterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A002 /* AlertMuterTests.swift */; };
 		BD11A001000000000000A002 /* AlertMuterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A002 /* AlertMuterTests.swift */; };
+		BD11A001000000000000A008 /* GlucoseAlertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A008 /* GlucoseAlertTests.swift */; };
 		BD11A001000000000000A007 /* GlucoseAlertConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */; };
 		BD11A001000000000000A007 /* GlucoseAlertConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */; };
 		BD11A001000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */; };
 		BD11A001000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */; };
 		BD11A001000000000000A003 /* DeviceAlertsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */; };
 		BD11A001000000000000A003 /* DeviceAlertsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */; };
@@ -1241,6 +1242,7 @@
 		38FCF3F125E9028E0078B0D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		38FCF3F125E9028E0078B0D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileStorageTests.swift; sourceTree = "<group>"; };
 		38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileStorageTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A002 /* AlertMuterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertMuterTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A002 /* AlertMuterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertMuterTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A008 /* GlucoseAlertTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertConfigurationTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertConfigurationTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastedGlucoseEvaluatorTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastedGlucoseEvaluatorTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlertsStoreTests.swift; sourceTree = "<group>"; };
 		BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlertsStoreTests.swift; sourceTree = "<group>"; };
@@ -3001,6 +3003,7 @@
 				3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */,
 				3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */,
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
 				BD11A000000000000000A002 /* AlertMuterTests.swift */,
 				BD11A000000000000000A002 /* AlertMuterTests.swift */,
+				BD11A000000000000000A008 /* GlucoseAlertTests.swift */,
 				BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */,
 				BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */,
 				BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */,
 				BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */,
 				BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */,
 				BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */,
@@ -5571,6 +5574,7 @@
 				3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */,
 				3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				BD11A001000000000000A002 /* AlertMuterTests.swift in Sources */,
 				BD11A001000000000000A002 /* AlertMuterTests.swift in Sources */,
+				BD11A001000000000000A008 /* GlucoseAlertTests.swift in Sources */,
 				BD11A001000000000000A007 /* GlucoseAlertConfigurationTests.swift in Sources */,
 				BD11A001000000000000A007 /* GlucoseAlertConfigurationTests.swift in Sources */,
 				BD11A001000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift in Sources */,
 				BD11A001000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift in Sources */,
 				BD11A001000000000000A003 /* DeviceAlertsStoreTests.swift in Sources */,
 				BD11A001000000000000A003 /* DeviceAlertsStoreTests.swift in Sources */,

+ 136 - 0
TrioTests/GlucoseAlertTests.swift

@@ -0,0 +1,136 @@
+import Foundation
+import Testing
+
+@testable import Trio
+
+@Suite("Trio Alerts: GlucoseAlert") struct GlucoseAlertTests {
+    // MARK: - Group A: shouldEvaluate
+
+    @Test("urgentLow evaluates even when isEnabled is false") func urgentLowDisabledStillEvaluates() {
+        var a = GlucoseAlert(type: .urgentLow)
+        a.isEnabled = false
+        #expect(a.shouldEvaluate == true)
+    }
+
+    @Test("urgentLow evaluates when isEnabled is true") func urgentLowEnabledEvaluates() {
+        var a = GlucoseAlert(type: .urgentLow)
+        a.isEnabled = true
+        #expect(a.shouldEvaluate == true)
+    }
+
+    @Test("low does not evaluate when disabled") func lowDisabledDoesNotEvaluate() {
+        var a = GlucoseAlert(type: .low)
+        a.isEnabled = false
+        #expect(a.shouldEvaluate == false)
+    }
+
+    @Test("low evaluates when enabled") func lowEnabledEvaluates() {
+        var a = GlucoseAlert(type: .low)
+        a.isEnabled = true
+        #expect(a.shouldEvaluate == true)
+    }
+
+    @Test("high does not evaluate when disabled") func highDisabledDoesNotEvaluate() {
+        var a = GlucoseAlert(type: .high)
+        a.isEnabled = false
+        #expect(a.shouldEvaluate == false)
+    }
+
+    @Test("high evaluates when enabled") func highEnabledEvaluates() {
+        var a = GlucoseAlert(type: .high)
+        a.isEnabled = true
+        #expect(a.shouldEvaluate == true)
+    }
+
+    @Test("forecastedLow does not evaluate when disabled") func forecastedLowDisabledDoesNotEvaluate() {
+        var a = GlucoseAlert(type: .forecastedLow)
+        a.isEnabled = false
+        #expect(a.shouldEvaluate == false)
+    }
+
+    @Test("forecastedLow evaluates when enabled") func forecastedLowEnabledEvaluates() {
+        var a = GlucoseAlert(type: .forecastedLow)
+        a.isEnabled = true
+        #expect(a.shouldEvaluate == true)
+    }
+
+    // MARK: - Group B: decoder defaults
+
+    @Test("Omitted isEnabled defaults to true") func decodeOmittedIsEnabledDefaultsTrue() throws {
+        let json = """
+        {
+            "id": "11111111-1111-1111-1111-111111111111",
+            "type": "low",
+            "name": "Low Glucose",
+            "thresholdMgDL": 72
+        }
+        """
+        let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
+        #expect(decoded.isEnabled == true)
+    }
+
+    @Test("urgentLow with isEnabled false decodes false but still evaluates") func decodeUrgentLowDisabledStillEvaluates() throws {
+        let json = """
+        {
+            "id": "22222222-2222-2222-2222-222222222222",
+            "type": "urgentLow",
+            "name": "Urgent Low Glucose",
+            "isEnabled": false,
+            "thresholdMgDL": 54
+        }
+        """
+        let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
+        #expect(decoded.isEnabled == false)
+        #expect(decoded.shouldEvaluate == true)
+    }
+
+    @Test("high with optional fields omitted uses type defaults") func decodeHighDefaults() throws {
+        let json = """
+        {
+            "id": "33333333-3333-3333-3333-333333333333",
+            "type": "high",
+            "name": "High Glucose",
+            "thresholdMgDL": 270
+        }
+        """
+        let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
+        #expect(decoded.soundFilename == GlucoseAlertType.high.defaultSoundFilename)
+        #expect(decoded.playsSound == true)
+        #expect(decoded.overridesSilenceAndDND == false)
+        #expect(decoded.activeOption == .always)
+        #expect(decoded.snoozedUntil == nil)
+        #expect(decoded.isEnabled == true)
+    }
+
+    @Test("urgentLow with overridesSilenceAndDND omitted uses type defaults") func decodeUrgentLowOverrideDefault() throws {
+        let json = """
+        {
+            "id": "44444444-4444-4444-4444-444444444444",
+            "type": "urgentLow",
+            "name": "Urgent Low Glucose",
+            "thresholdMgDL": 54
+        }
+        """
+        let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
+        #expect(decoded.overridesSilenceAndDND == true)
+        #expect(decoded.soundFilename == GlucoseAlertType.urgentLow.defaultSoundFilename)
+    }
+
+    // MARK: - Group C: round-trip
+
+    @Test("Fully-populated value survives encode/decode round-trip") func roundTrip() throws {
+        var original = GlucoseAlert(type: .low)
+        original.name = "Custom Low Alarm"
+        original.isEnabled = false
+        original.thresholdMgDL = 65
+        original.soundFilename = "custom_sound.caf"
+        original.playsSound = false
+        original.overridesSilenceAndDND = true
+        original.activeOption = .night
+        original.snoozedUntil = Date(timeIntervalSinceReferenceDate: 1_000_000)
+
+        let data = try JSONEncoder().encode(original)
+        let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: data)
+        #expect(decoded == original)
+    }
+}