From 82e4fa9d2a5fb2940ad4bc72f2aa3c803ed287fc Mon Sep 17 00:00:00 2001 From: scttbnsn <80784472+scttbnsn@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:01:46 -0500 Subject: [PATCH] fix(triggers): retain rules when the legacy projection is missing --- Sources/DilatedCore/TriggerStore.swift | 1 + .../DilatedCoreTests/TriggerStoreTests.swift | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 Tests/DilatedCoreTests/TriggerStoreTests.swift diff --git a/Sources/DilatedCore/TriggerStore.swift b/Sources/DilatedCore/TriggerStore.swift index e6101f2..227c5dc 100644 --- a/Sources/DilatedCore/TriggerStore.swift +++ b/Sources/DilatedCore/TriggerStore.swift @@ -32,6 +32,7 @@ public struct TriggerStore: Sendable { try persist(record.rules, previous: legacy) return record.rules } + guard FileManager.default.fileExists(atPath: legacyFile.path) else { return record.rules } let baseline = Dictionary(uniqueKeysWithValues: record.legacy.map { ($0.id, $0) }) let current = Dictionary(uniqueKeysWithValues: legacy.map { ($0.id, $0) }) var rules = record.rules.filter { baseline[$0.id] == nil || current[$0.id] != nil } diff --git a/Tests/DilatedCoreTests/TriggerStoreTests.swift b/Tests/DilatedCoreTests/TriggerStoreTests.swift new file mode 100644 index 0000000..d2e22a9 --- /dev/null +++ b/Tests/DilatedCoreTests/TriggerStoreTests.swift @@ -0,0 +1,28 @@ +import Foundation +import Testing + +@testable import DilatedCore + +struct TriggerStoreTests { + @Test(arguments: [false, true]) + func missingProjectionPreservesRulesWhileAnEmptyProjectionDeletesThem(missing: Bool) throws { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: directory) } + let store = TriggerStore(directory: directory) + let rule = TriggerRule(name: "AC", criteria: [.power(onBattery: false)]) + try store.save([rule]) + let projection = directory.appendingPathComponent("triggers.json") + if missing { + try FileManager.default.removeItem(at: projection) + } else { + try JSONEncoder().encode([TriggerRule]()).write(to: projection, options: .atomic) + } + let expected = missing ? [rule] : [] + let loaded = try store.load() + #expect(loaded == expected) + try store.save(loaded) + #expect(try store.load() == expected) + #expect( + try JSONDecoder().decode([TriggerRule].self, from: Data(contentsOf: projection)) == expected) + } +}