-
Notifications
You must be signed in to change notification settings - Fork 121
[POS][Local Catalog] Add top level catalog sync coordinator #16098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
e4abd4d
93f5692
bbd8d73
cbfea01
dfd3601
ffe3aeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import Foundation | ||
| import Storage | ||
|
|
||
| public protocol POSCatalogSyncCoordinatorProtocol { | ||
| /// Performs a full catalog sync for the specified site | ||
| /// - Parameter siteID: The site ID to sync catalog for | ||
| /// - Returns: The synced catalog containing products and variations | ||
| func performFullSync(for siteID: Int64) async throws -> POSCatalog | ||
|
|
||
| /// Determines if a full sync should be performed based on the age of the last sync | ||
| /// - Parameters: | ||
| /// - siteID: The site ID to check | ||
| /// - maxAge: Maximum age before a sync is considered stale | ||
| /// - Returns: True if a sync should be performed | ||
| func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) -> Bool | ||
| } | ||
|
|
||
| public final class POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol { | ||
| private let syncService: POSCatalogFullSyncServiceProtocol | ||
| private let settingsStore: SiteSpecificAppSettingsStoreMethodsProtocol | ||
|
|
||
| public init(syncService: POSCatalogFullSyncServiceProtocol, | ||
| settingsStore: SiteSpecificAppSettingsStoreMethodsProtocol? = nil) { | ||
| self.syncService = syncService | ||
| self.settingsStore = settingsStore ?? SiteSpecificAppSettingsStoreMethods(fileStorage: PListFileStorage()) | ||
| } | ||
|
|
||
| public func performFullSync(for siteID: Int64) async throws -> POSCatalog { | ||
|
||
| DDLogInfo("🔄 POSCatalogSyncCoordinator starting full sync for site \(siteID)") | ||
|
|
||
| let catalog = try await syncService.startFullSync(for: siteID) | ||
|
|
||
| // Record the sync timestamp | ||
| settingsStore.setPOSLastFullSyncDate(Date(), for: siteID) | ||
|
|
||
| DDLogInfo("✅ POSCatalogSyncCoordinator completed full sync for site \(siteID)") | ||
| return catalog | ||
| } | ||
|
|
||
| public func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) -> Bool { | ||
| guard let lastSyncDate = lastFullSyncDate(for: siteID) else { | ||
| DDLogInfo("📋 POSCatalogSyncCoordinator: No previous sync found for site \(siteID), sync needed") | ||
| return true | ||
| } | ||
|
|
||
| let age = Date().timeIntervalSince(lastSyncDate) | ||
| let shouldSync = age > maxAge | ||
|
|
||
| if shouldSync { | ||
| DDLogInfo("📋 POSCatalogSyncCoordinator: Last sync for site \(siteID) was \(Int(age))s ago (max: \(Int(maxAge))s), sync needed") | ||
| } else { | ||
| DDLogInfo("📋 POSCatalogSyncCoordinator: Last sync for site \(siteID) was \(Int(age))s ago (max: \(Int(maxAge))s), sync not needed") | ||
| } | ||
|
|
||
| return shouldSync | ||
| } | ||
|
|
||
| // MARK: - Private | ||
|
|
||
| private func lastFullSyncDate(for siteID: Int64) -> Date? { | ||
| return settingsStore.getPOSLastFullSyncDate(for: siteID) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,6 +229,144 @@ struct SiteSpecificAppSettingsStoreMethodsTests { | |
| #expect(retrievedVariationTerms == variationTerms) | ||
| #expect(retrievedCouponTerms == couponTerms) | ||
| } | ||
|
|
||
|
|
||
|
||
|
|
||
| // MARK: - POS Last Full Sync Date Tests | ||
|
|
||
| @Test func getPOSLastFullSyncDate_returns_nil_when_no_date_exists() { | ||
| // When | ||
| let syncDate = sut.getPOSLastFullSyncDate(for: siteID) | ||
|
|
||
| // Then | ||
| #expect(syncDate == nil) | ||
| } | ||
|
|
||
| @Test func getPOSLastFullSyncDate_returns_saved_date() throws { | ||
| // Given | ||
| let expectedDate = Date() | ||
| let storeSettings = GeneralStoreSettings(posLastFullSyncDate: expectedDate) | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [siteID: storeSettings]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| // When | ||
| let syncDate = sut.getPOSLastFullSyncDate(for: siteID) | ||
|
|
||
| // Then | ||
| #expect(syncDate == expectedDate) | ||
| } | ||
|
|
||
| @Test func setPOSLastFullSyncDate_saves_date_successfully() throws { | ||
| // Given | ||
| let dateToSave = Date() | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [siteID: GeneralStoreSettings()]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| // When | ||
| sut.setPOSLastFullSyncDate(dateToSave, for: siteID) | ||
|
|
||
| // Then | ||
| let savedData: GeneralStoreSettingsBySite = try fileStorage.data(for: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
| #expect(savedData.storeSettingsBySite[siteID]?.posLastFullSyncDate == dateToSave) | ||
| } | ||
|
|
||
| @Test func setPOSLastFullSyncDate_can_set_nil_date() throws { | ||
| // Given | ||
| let existingDate = Date() | ||
| let storeSettings = GeneralStoreSettings(posLastFullSyncDate: existingDate) | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [siteID: storeSettings]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| // When | ||
| sut.setPOSLastFullSyncDate(nil, for: siteID) | ||
|
|
||
| // Then | ||
| let savedData: GeneralStoreSettingsBySite = try fileStorage.data(for: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
| #expect(savedData.storeSettingsBySite[siteID]?.posLastFullSyncDate == nil) | ||
| } | ||
|
|
||
| @Test func setPOSLastFullSyncDate_preserves_other_settings() throws { | ||
| // Given | ||
| let existingStoreID = "existing-store" | ||
| let existingTerms = ["existing", "terms"] | ||
| let storeSettings = GeneralStoreSettings( | ||
| storeID: existingStoreID, | ||
| searchTermsByKey: ["product_search_terms": existingTerms] | ||
| ) | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [siteID: storeSettings]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| let dateToSave = Date() | ||
|
|
||
| // When | ||
| sut.setPOSLastFullSyncDate(dateToSave, for: siteID) | ||
|
|
||
| // Then | ||
| let savedData: GeneralStoreSettingsBySite = try fileStorage.data(for: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
| let savedSettings = savedData.storeSettingsBySite[siteID] | ||
| #expect(savedSettings?.posLastFullSyncDate == dateToSave) | ||
| #expect(savedSettings?.storeID == existingStoreID) | ||
| #expect(savedSettings?.searchTermsByKey["product_search_terms"] == existingTerms) | ||
| } | ||
|
|
||
| @Test func setPOSLastFullSyncDate_preserves_dates_for_other_sites() throws { | ||
| // Given | ||
| let otherSiteID: Int64 = 456 | ||
| let otherSiteDate = Date().addingTimeInterval(-3600) | ||
| let otherSiteSettings = GeneralStoreSettings(posLastFullSyncDate: otherSiteDate) | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [otherSiteID: otherSiteSettings]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| let newDate = Date() | ||
|
|
||
| // When | ||
| sut.setPOSLastFullSyncDate(newDate, for: siteID) | ||
|
|
||
| // Then | ||
| let savedData: GeneralStoreSettingsBySite = try fileStorage.data(for: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
| #expect(savedData.storeSettingsBySite[siteID]?.posLastFullSyncDate == newDate) | ||
| #expect(savedData.storeSettingsBySite[otherSiteID]?.posLastFullSyncDate == otherSiteDate) | ||
| } | ||
|
|
||
| @Test func getPOSLastFullSyncDate_handles_different_sites_independently() throws { | ||
| // Given | ||
| let siteA: Int64 = 123 | ||
| let siteB: Int64 = 456 | ||
| let dateA = Date() | ||
| let dateB = Date().addingTimeInterval(-3600) | ||
|
|
||
| let storeSettingsA = GeneralStoreSettings(posLastFullSyncDate: dateA) | ||
| let storeSettingsB = GeneralStoreSettings(posLastFullSyncDate: dateB) | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [ | ||
| siteA: storeSettingsA, | ||
| siteB: storeSettingsB | ||
| ]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| // When | ||
| let retrievedDateA = sut.getPOSLastFullSyncDate(for: siteA) | ||
| let retrievedDateB = sut.getPOSLastFullSyncDate(for: siteB) | ||
|
|
||
| // Then | ||
| #expect(retrievedDateA == dateA) | ||
| #expect(retrievedDateB == dateB) | ||
| } | ||
|
|
||
| @Test func resetStoreSettings_clears_pos_sync_date() throws { | ||
| // Given | ||
| let syncDate = Date() | ||
| let storeSettings = GeneralStoreSettings(posLastFullSyncDate: syncDate) | ||
| let existingData = GeneralStoreSettingsBySite(storeSettingsBySite: [siteID: storeSettings]) | ||
| try fileStorage.write(existingData, to: SiteSpecificAppSettingsStoreMethods.defaultGeneralStoreSettingsFileURL) | ||
|
|
||
| // When | ||
| sut.resetStoreSettings() | ||
|
|
||
| // Then | ||
| #expect(fileStorage.deleteIsHit == true) | ||
| let retrievedDate = sut.getPOSLastFullSyncDate(for: siteID) | ||
| #expect(retrievedDate == nil) | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Mock FileStorage | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could be named
fullSyncServiceto differentiate from incremental sync when both syncs are supported.