Skip to content

Commit e425e44

Browse files
authored
[POS][Local Catalog] Always full sync, if site not in database (#16099)
2 parents 5182fcd + c9e3758 commit e425e44

File tree

3 files changed

+96
-14
lines changed

3 files changed

+96
-14
lines changed

Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import Storage
3+
import GRDB
34

45
public protocol POSCatalogSyncCoordinatorProtocol {
56
/// Performs a full catalog sync for the specified site
@@ -17,11 +18,14 @@ public protocol POSCatalogSyncCoordinatorProtocol {
1718
public final class POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
1819
private let fullSyncService: POSCatalogFullSyncServiceProtocol
1920
private let settingsStore: SiteSpecificAppSettingsStoreMethodsProtocol
21+
private let grdbManager: GRDBManagerProtocol
2022

2123
public init(fullSyncService: POSCatalogFullSyncServiceProtocol,
22-
settingsStore: SiteSpecificAppSettingsStoreMethodsProtocol? = nil) {
24+
settingsStore: SiteSpecificAppSettingsStoreMethodsProtocol? = nil,
25+
grdbManager: GRDBManagerProtocol) {
2326
self.fullSyncService = fullSyncService
2427
self.settingsStore = settingsStore ?? SiteSpecificAppSettingsStoreMethods(fileStorage: PListFileStorage())
28+
self.grdbManager = grdbManager
2529
}
2630

2731
public func performFullSync(for siteID: Int64) async throws {
@@ -36,6 +40,11 @@ public final class POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol
3640
}
3741

3842
public func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) -> Bool {
43+
if !siteExistsInDatabase(siteID: siteID) {
44+
DDLogInfo("📋 POSCatalogSyncCoordinator: Site \(siteID) not found in database, sync needed")
45+
return true
46+
}
47+
3948
guard let lastSyncDate = lastFullSyncDate(for: siteID) else {
4049
DDLogInfo("📋 POSCatalogSyncCoordinator: No previous sync found for site \(siteID), sync needed")
4150
return true
@@ -58,4 +67,16 @@ public final class POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol
5867
private func lastFullSyncDate(for siteID: Int64) -> Date? {
5968
return settingsStore.getPOSLastFullSyncDate(for: siteID)
6069
}
70+
71+
private func siteExistsInDatabase(siteID: Int64) -> Bool {
72+
do {
73+
return try grdbManager.databaseConnection.read { db in
74+
return try PersistedSite.filter(key: siteID).fetchCount(db) > 0
75+
}
76+
} catch {
77+
DDLogError("⛔️ POSCatalogSyncCoordinator: Error checking if site \(siteID) exists in database: \(error)")
78+
// On error, assume site exists to avoid unnecessary syncs
79+
return true
80+
}
81+
}
6182
}

Modules/Tests/YosemiteTests/Tools/POS/POSCatalogSyncCoordinatorTests.swift

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import Foundation
22
import Testing
33
@testable import Yosemite
4-
import Storage
4+
@testable import Storage
55

66
struct POSCatalogSyncCoordinatorTests {
77
private let mockSyncService: MockPOSCatalogFullSyncService
88
private let mockSettingsStore: MockSiteSpecificAppSettingsStoreMethods
9+
private let grdbManager: GRDBManager
910
private let sut: POSCatalogSyncCoordinator
1011
private let sampleSiteID: Int64 = 134
1112

12-
init() {
13+
init() throws {
1314
self.mockSyncService = MockPOSCatalogFullSyncService()
1415
self.mockSettingsStore = MockSiteSpecificAppSettingsStoreMethods()
16+
self.grdbManager = try GRDBManager()
1517
self.sut = POSCatalogSyncCoordinator(
1618
fullSyncService: mockSyncService,
17-
settingsStore: mockSettingsStore
19+
settingsStore: mockSettingsStore,
20+
grdbManager: grdbManager
1821
)
1922
}
2023

@@ -72,9 +75,23 @@ struct POSCatalogSyncCoordinatorTests {
7275

7376
// MARK: - Should Sync Decision Tests
7477

75-
@Test func shouldPerformFullSync_returns_true_when_no_previous_sync() {
76-
// Given - no previous sync date stored
78+
@Test func shouldPerformFullSync_returns_true_when_site_is_not_in_database_with_no_sync_history() {
79+
// Given - site doesn't exist in database AND has no sync history
7780
mockSettingsStore.storedDates = [:]
81+
// Note: NOT creating site in database
82+
83+
// When
84+
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 60 * 60)
85+
86+
// Then - should sync because site doesn't exist in database
87+
#expect(shouldSync == true)
88+
}
89+
90+
@Test func shouldPerformFullSync_returns_true_when_site_is_in_database_with_no_previous_sync() throws {
91+
// Given - no previous sync date stored, but site exists in database
92+
// This is much less likely to happen, but could help at a migration point
93+
mockSettingsStore.storedDates = [:]
94+
try createSiteInDatabase(siteID: sampleSiteID)
7895

7996
// When
8097
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 3600)
@@ -84,10 +101,11 @@ struct POSCatalogSyncCoordinatorTests {
84101
#expect(mockSettingsStore.getPOSLastFullSyncDateCallCount == 1)
85102
}
86103

87-
@Test func shouldPerformFullSync_returns_true_when_sync_is_stale() {
88-
// Given - previous sync was 2 hours ago
104+
@Test func shouldPerformFullSync_returns_true_when_sync_is_stale() throws {
105+
// Given - previous sync was 2 hours ago, and site exists in database
89106
let twoHoursAgo = Date().addingTimeInterval(-2 * 60 * 60)
90107
mockSettingsStore.storedDates[sampleSiteID] = twoHoursAgo
108+
try createSiteInDatabase(siteID: sampleSiteID)
91109

92110
// When - max age is 1 hour
93111
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 60 * 60)
@@ -96,10 +114,11 @@ struct POSCatalogSyncCoordinatorTests {
96114
#expect(shouldSync == true)
97115
}
98116

99-
@Test func shouldPerformFullSync_returns_false_when_sync_is_fresh() {
100-
// Given - previous sync was 30 minutes ago
117+
@Test func shouldPerformFullSync_returns_false_when_sync_is_fresh() throws {
118+
// Given - previous sync was 30 minutes ago, and site exists in database
101119
let thirtyMinutesAgo = Date().addingTimeInterval(-30 * 60)
102120
mockSettingsStore.storedDates[sampleSiteID] = thirtyMinutesAgo
121+
try createSiteInDatabase(siteID: sampleSiteID)
103122

104123
// When - max age is 1 hour
105124
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 60 * 60)
@@ -108,7 +127,7 @@ struct POSCatalogSyncCoordinatorTests {
108127
#expect(shouldSync == false)
109128
}
110129

111-
@Test func shouldPerformFullSync_handles_different_sites_independently() {
130+
@Test func shouldPerformFullSync_handles_different_sites_independently() throws {
112131
// Given
113132
let siteA: Int64 = 123
114133
let siteB: Int64 = 456
@@ -117,6 +136,10 @@ struct POSCatalogSyncCoordinatorTests {
117136
mockSettingsStore.storedDates[siteA] = oneHourAgo // Has previous sync
118137
// siteB has no previous sync
119138

139+
// Create both sites in database to test timing logic
140+
try createSiteInDatabase(siteID: siteA)
141+
try createSiteInDatabase(siteID: siteB)
142+
120143
// When
121144
let shouldSyncA = sut.shouldPerformFullSync(for: siteA, maxAge: 2 * 60 * 60) // 2 hours
122145
let shouldSyncB = sut.shouldPerformFullSync(for: siteB, maxAge: 2 * 60 * 60) // 2 hours
@@ -126,17 +149,55 @@ struct POSCatalogSyncCoordinatorTests {
126149
#expect(shouldSyncB == true) // No previous sync
127150
}
128151

129-
@Test func shouldPerformFullSync_with_zero_maxAge_always_returns_true() {
130-
// Given - previous sync was just now
152+
@Test func shouldPerformFullSync_with_zero_maxAge_always_returns_true() throws {
153+
// Given - previous sync was just now, and site exists in database
131154
let justNow = Date()
132155
mockSettingsStore.storedDates[sampleSiteID] = justNow
156+
try createSiteInDatabase(siteID: sampleSiteID)
133157

134158
// When - max age is 0 (always sync)
135159
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 0)
136160

137161
// Then
138162
#expect(shouldSync == true)
139163
}
164+
165+
// MARK: - Database Check Tests
166+
167+
@Test func shouldPerformFullSync_returns_true_when_site_not_in_database() {
168+
// Given - site does not exist in database, but has recent sync date
169+
let recentSyncDate = Date().addingTimeInterval(-30 * 60) // 30 minutes ago
170+
mockSettingsStore.storedDates[sampleSiteID] = recentSyncDate
171+
// Note: not creating site in database so it won't exist
172+
173+
// When - max age is 1 hour (normally wouldn't sync)
174+
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 60 * 60)
175+
176+
// Then - should sync because site doesn't exist in database
177+
#expect(shouldSync == true)
178+
}
179+
180+
@Test func shouldPerformFullSync_respects_time_when_site_exists_in_database() throws {
181+
// Given - site exists in database with recent sync date
182+
let recentSyncDate = Date().addingTimeInterval(-30 * 60) // 30 minutes ago
183+
mockSettingsStore.storedDates[sampleSiteID] = recentSyncDate
184+
try createSiteInDatabase(siteID: sampleSiteID)
185+
186+
// When - max age is 1 hour
187+
let shouldSync = sut.shouldPerformFullSync(for: sampleSiteID, maxAge: 60 * 60)
188+
189+
// Then - should not sync because site exists and time hasn't passed
190+
#expect(shouldSync == false)
191+
}
192+
193+
// MARK: - Helper Methods
194+
195+
private func createSiteInDatabase(siteID: Int64) throws {
196+
try grdbManager.databaseConnection.write { db in
197+
let site = PersistedSite(id: siteID)
198+
try site.insert(db)
199+
}
200+
}
140201
}
141202

142203
// MARK: - Mock Services

WooCommerce/Classes/ViewRelated/MainTabBarController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ private extension MainTabBarController {
799799
return nil
800800
}
801801

802-
return POSCatalogSyncCoordinator(fullSyncService: fullSyncService)
802+
return POSCatalogSyncCoordinator(fullSyncService: fullSyncService, grdbManager: ServiceLocator.grdbManager)
803803
}
804804

805805
func triggerPOSCatalogSyncIfNeeded(for siteID: Int64) async {

0 commit comments

Comments
 (0)