@@ -40,24 +40,34 @@ struct ConfigStoreTests {
4040 @Test func `save routes to remote in remote mode`() async throws {
4141 var localHit = false
4242 var remoteHit = false
43+ let notificationCenter = NotificationCenter ( )
4344 let changeCount = NotificationCount ( )
44- let observer = NotificationCenter . default . addObserver (
45+ let observer = notificationCenter . addObserver (
4546 forName: . openclawConfigDidChange,
4647 object: nil ,
4748 queue: nil )
48- { _ in changeCount. increment ( ) }
49- defer { NotificationCenter . default. removeObserver ( observer) }
50- await ConfigStore . _testSetOverrides ( . init(
49+ { note in changeCount. record ( note) }
50+ defer { notificationCenter. removeObserver ( observer) }
51+
52+ try await self . withOverrides ( . init(
5153 isRemoteMode: { true } ,
5254 saveLocal: { _ in localHit = true } ,
53- saveRemote: { _ in remoteHit = true } ) )
54-
55- try await ConfigStore . save ( [ " remote " : true ] )
55+ saveRemote: { _ in
56+ remoteHit = true
57+ // Reproduce a concurrent AppState-style publisher overlapping this save.
58+ await Task . detached {
59+ NotificationCenter . default. post ( name: . openclawConfigDidChange, object: nil )
60+ } . value
61+ } ,
62+ notificationCenter: notificationCenter) )
63+ {
64+ try await ConfigStore . save ( [ " remote " : true ] )
65+ }
5666
57- await ConfigStore . _testClearOverrides ( )
5867 #expect( remoteHit)
5968 #expect( !localHit)
6069 #expect( changeCount. value == 1 )
70+ #expect( changeCount. allSendersWereNil)
6171 }
6272
6373 @Test func `save routes to local in local mode`() async throws {
@@ -76,25 +86,32 @@ struct ConfigStoreTests {
7686 }
7787
7888 @Test func `failed save do es not announce config change`() async {
89+ let notificationCenter = NotificationCenter ( )
7990 let changeCount = NotificationCount ( )
80- let observer = NotificationCenter . default . addObserver (
91+ let observer = notificationCenter . addObserver (
8192 forName: . openclawConfigDidChange,
8293 object: nil ,
8394 queue: nil )
84- { _ in changeCount. increment ( ) }
85- defer { NotificationCenter . default. removeObserver ( observer) }
86- await ConfigStore . _testSetOverrides ( . init(
95+ { note in changeCount. record ( note) }
96+ defer { notificationCenter. removeObserver ( observer) }
97+
98+ await self . withOverrides ( . init(
8799 isRemoteMode: { true } ,
88100 saveRemote: { _ in
101+ // Concurrent same-name traffic must not look like a ConfigStore announcement.
102+ await Task . detached {
103+ NotificationCenter . default. post ( name: . openclawConfigDidChange, object: nil )
104+ } . value
89105 throw NSError ( domain: " ConfigStoreTests " , code: 1 )
90- } ) )
91-
92- do {
93- try await ConfigStore . save ( [ " remote " : true ] )
94- Issue . record ( " Expected save to fail " )
95- } catch { }
106+ } ,
107+ notificationCenter: notificationCenter) )
108+ {
109+ do {
110+ try await ConfigStore . save ( [ " remote " : true ] )
111+ Issue . record ( " Expected save to fail " )
112+ } catch { }
113+ }
96114
97- await ConfigStore . _testClearOverrides ( )
98115 #expect( changeCount. value == 0 )
99116 }
100117
@@ -169,17 +186,40 @@ struct ConfigStoreTests {
169186 #expect( ( root ? [ " meta " ] as? [ String : Any ] ) != nil )
170187 }
171188 }
189+
190+ private func withOverrides< T> (
191+ _ overrides: ConfigStore . Overrides ,
192+ _ body: ( ) async throws -> T ) async rethrows -> T
193+ {
194+ await ConfigStore . _testSetOverrides ( overrides)
195+ do {
196+ let result = try await body ( )
197+ await ConfigStore . _testClearOverrides ( )
198+ return result
199+ } catch {
200+ await ConfigStore . _testClearOverrides ( )
201+ throw error
202+ }
203+ }
172204}
173205
174206private final class NotificationCount : @unchecked Sendable {
175207 private let lock = NSLock ( )
176208 private var count = 0
209+ private var sawNonNilSender = false
177210
178211 var value : Int {
179212 self . lock. withLock { self . count }
180213 }
181214
182- func increment( ) {
183- self . lock. withLock { self . count += 1 }
215+ var allSendersWereNil : Bool {
216+ self . lock. withLock { !self . sawNonNilSender }
217+ }
218+
219+ func record( _ notification: Notification ) {
220+ self . lock. withLock {
221+ self . count += 1
222+ self . sawNonNilSender = self . sawNonNilSender || notification. object != nil
223+ }
184224 }
185225}
0 commit comments