Skip to content

Commit 10c06f8

Browse files
authored
Speculative fix for flaky order sync test (#16330)
2 parents cdfdd7e + 0905082 commit 10c06f8

File tree

2 files changed

+59
-7
lines changed

2 files changed

+59
-7
lines changed

Modules/Tests/PointOfSaleTests/Controllers/PointOfSaleOrderControllerTests.swift

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,26 +53,57 @@ struct PointOfSaleOrderControllerTests {
5353
#expect(mockOrderService.syncOrderWasCalled == false)
5454
}
5555

56-
@Test func syncOrder_when_already_syncing_doesnt_call_orderService() async throws {
56+
@Test @MainActor func syncOrder_when_already_syncing_doesnt_call_orderService() async throws {
5757
// Given
5858
let sut = PointOfSaleOrderController(orderService: mockOrderService,
5959
receiptSender: mockReceiptSender,
6060
currencySettingsProvider: MockCurrencySettingsProvider(),
6161
analytics: MockPOSAnalytics())
62-
mockOrderService.simulateSyncing = true
63-
Task {
62+
63+
// Block the sync so it doesn't complete until we manually resume it
64+
mockOrderService.blockNextSync()
65+
66+
// Start the first sync in a detached task so it runs concurrently
67+
let firstSyncTask = Task.detached {
6468
await sut.syncOrder(for: Cart(purchasableItems: [makeItem(quantity: 1)]), retryHandler: {})
6569
}
66-
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
70+
71+
// Wait for the order state to actually become syncing
72+
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
73+
@Sendable func observeOrderState() {
74+
withObservationTracking {
75+
_ = sut.orderState
76+
} onChange: {
77+
Task { @MainActor in
78+
if sut.orderState.isSyncing {
79+
continuation.resume()
80+
} else {
81+
observeOrderState()
82+
}
83+
}
84+
}
85+
}
86+
observeOrderState()
87+
}
88+
89+
// Verify the state is actually syncing
90+
#expect(sut.orderState.isSyncing == true)
91+
#expect(mockOrderService.syncOrderWasCalled == true)
92+
93+
// Reset the flag after confirming the sync has started
6794
mockOrderService.syncOrderWasCalled = false
6895

69-
// When
96+
// When - try to sync while the first sync is still in progress
7097
await sut.syncOrder(for: Cart(purchasableItems: [makeItem(quantity: 2),
7198
makeItem(quantity: 5)]),
7299
retryHandler: {})
73100

74-
// Then
101+
// Then - the second sync should have been skipped
75102
#expect(mockOrderService.syncOrderWasCalled == false)
103+
104+
// Cleanup - allow the first sync to complete
105+
mockOrderService.resumeBlockedSync()
106+
_ = await firstSyncTask.result
76107
}
77108

78109
@Test func syncOrder_with_no_previous_order_calls_orderService() async throws {

Modules/Tests/PointOfSaleTests/Mocks/MockPOSOrderService.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,33 @@ class MockPOSOrderService: POSOrderServiceProtocol {
1414
var spySyncOrderCurrency: CurrencyCode?
1515
var spyCashPaymentChangeDueAmount: String?
1616

17+
// For controlling sync timing in tests
18+
private var syncContinuation: CheckedContinuation<Void, Never>?
19+
private var shouldBlockSync = false
20+
21+
/// Blocks the next sync operation until `resumeBlockedSync()` is called
22+
func blockNextSync() {
23+
shouldBlockSync = true
24+
}
25+
26+
/// Resumes a blocked sync operation
27+
func resumeBlockedSync() {
28+
syncContinuation?.resume()
29+
syncContinuation = nil
30+
shouldBlockSync = false
31+
}
32+
1733
func syncOrder(cart: Yosemite.POSCart,
1834
currency: CurrencyCode) async throws -> Yosemite.Order {
1935
syncOrderWasCalled = true
2036
spySyncOrderCurrency = currency
2137

22-
if simulateSyncing {
38+
if shouldBlockSync {
39+
shouldBlockSync = false // Only block the first call
40+
await withCheckedContinuation { continuation in
41+
syncContinuation = continuation
42+
}
43+
} else if simulateSyncing {
2344
try await Task.sleep(nanoseconds: UInt64(1 * Double(NSEC_PER_SEC)))
2445
}
2546

0 commit comments

Comments
 (0)