Skip to content

Commit fe1ce5f

Browse files
committed
Add test cases for POSCatalogFullSyncService.
1 parent 62d8663 commit fe1ce5f

File tree

2 files changed

+234
-1
lines changed

2 files changed

+234
-1
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public final class POSCatalogFullSyncService: POSCatalogFullSyncServiceProtocol
5353
let (products, variations) = try await (productsTask, variationsTask)
5454
let catalog = POSCatalog(products: products, variations: variations)
5555
DDLogInfo("✅ Loaded \(catalog.products.count) products and \(catalog.variations.count) variations for siteID \(siteID)")
56-
return .init(products: products, variations: [])
56+
return .init(products: products, variations: variations)
5757
} catch {
5858
throw error
5959
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import Foundation
2+
import Testing
3+
@testable import Networking
4+
@testable import Yosemite
5+
6+
struct POSCatalogFullSyncServiceTests {
7+
private let sut: POSCatalogFullSyncService
8+
private let mockSyncRemote: MockPOSCatalogSyncRemote
9+
private let sampleSiteID: Int64 = 134
10+
11+
init() {
12+
self.mockSyncRemote = MockPOSCatalogSyncRemote()
13+
self.sut = POSCatalogFullSyncService(syncRemote: mockSyncRemote, batchSize: 2)
14+
}
15+
16+
// MARK: - Full Sync Tests
17+
18+
@Test func startFullSync_loads_products_and_variations() async throws {
19+
// Given
20+
let expectedProducts = [POSProduct.fake(), POSProduct.fake()]
21+
let expectedVariations = [POSProductVariation.fake()]
22+
23+
mockSyncRemote.setProductResult(pageNumber: 1, result: .success(PagedItems(items: expectedProducts, hasMorePages: false, totalItems: 0)))
24+
mockSyncRemote.setVariationResult(pageNumber: 1, result: .success(PagedItems(items: expectedVariations, hasMorePages: false, totalItems: 0)))
25+
26+
// When
27+
let result = try await sut.startFullSync(for: sampleSiteID)
28+
29+
// Then
30+
#expect(result.products.count == expectedProducts.count)
31+
#expect(result.variations.count == expectedVariations.count)
32+
#expect(mockSyncRemote.loadProductsCallCount == 2) // 1 batch of 2 requests
33+
#expect(mockSyncRemote.loadProductVariationsCallCount == 2) // 1 batch of 2 requests
34+
}
35+
36+
@Test func startFullSync_handles_paginated_products_correctly() async throws {
37+
// Given - Multiple pages of products
38+
let page1Products = [POSProduct.fake()]
39+
let page2Products = [POSProduct.fake()]
40+
let page3Products = [POSProduct.fake()]
41+
42+
mockSyncRemote.setProductResults([
43+
PagedItems(items: page1Products, hasMorePages: true, totalItems: 3),
44+
PagedItems(items: page2Products, hasMorePages: true, totalItems: 3),
45+
PagedItems(items: page3Products, hasMorePages: false, totalItems: 3)
46+
])
47+
48+
// When
49+
let result = try await sut.startFullSync(for: sampleSiteID)
50+
51+
// Then
52+
#expect(result.products.count == 3)
53+
#expect(mockSyncRemote.loadProductsCallCount == 4) // 2 batches of 2 requests
54+
#expect(mockSyncRemote.loadProductVariationsCallCount == 2) // 1 batch of 2 requests
55+
}
56+
57+
@Test func startFullSync_handles_paginated_variations_correctly() async throws {
58+
// Given - Multiple pages of variations
59+
let page1Variations = [POSProductVariation.fake(), POSProductVariation.fake()]
60+
let page2Variations = [POSProductVariation.fake()]
61+
let page3Variations = [POSProductVariation.fake()]
62+
63+
mockSyncRemote.setVariationResults([
64+
PagedItems(items: page1Variations, hasMorePages: true, totalItems: 2),
65+
PagedItems(items: page2Variations, hasMorePages: true, totalItems: 2),
66+
PagedItems(items: page3Variations, hasMorePages: false, totalItems: 2)
67+
])
68+
69+
// When
70+
let result = try await sut.startFullSync(for: sampleSiteID)
71+
72+
// Then
73+
#expect(result.variations.count == 4)
74+
#expect(mockSyncRemote.loadProductsCallCount == 2) // 1 batch of 2 requests
75+
#expect(mockSyncRemote.loadProductVariationsCallCount == 4) // 2 batches of 2 requests
76+
}
77+
78+
@Test func startFullSync_stops_pagination_when_no_new_items_returned_and_hasMorePages_is_inaccurate() async throws {
79+
// Given
80+
let page1Products = [POSProduct.fake()]
81+
let emptyPage: [POSProduct] = []
82+
83+
mockSyncRemote.setProductResults([
84+
PagedItems(items: page1Products, hasMorePages: true, totalItems: 1),
85+
PagedItems(items: emptyPage, hasMorePages: true, totalItems: 1)
86+
])
87+
mockSyncRemote.setVariationResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
88+
89+
// When
90+
let result = try await sut.startFullSync(for: sampleSiteID)
91+
92+
// Then - Should stop after empty page
93+
#expect(result.products.count == 1)
94+
#expect(mockSyncRemote.loadProductsCallCount == 4) // The results from the second batch are empty
95+
}
96+
97+
@Test func startFullSync_handles_batch_processing_correctly() async throws {
98+
// Given - Service with batch size 2
99+
let products = (1...5).map { _ in POSProduct.fake() }
100+
101+
mockSyncRemote.setProductResults([
102+
PagedItems(items: [products[0]], hasMorePages: true, totalItems: 5), // Page 1
103+
PagedItems(items: [products[1]], hasMorePages: true, totalItems: 5), // Page 2 (batch 1)
104+
PagedItems(items: [products[2]], hasMorePages: true, totalItems: 5), // Page 3
105+
PagedItems(items: [products[3]], hasMorePages: true, totalItems: 5), // Page 4 (batch 2)
106+
PagedItems(items: [products[4]], hasMorePages: false, totalItems: 5) // Page 5 (batch 3)
107+
])
108+
mockSyncRemote.setVariationResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
109+
110+
// When
111+
let result = try await sut.startFullSync(for: sampleSiteID)
112+
113+
// Then
114+
#expect(result.products.count == 5)
115+
#expect(mockSyncRemote.loadProductsCallCount == 6)
116+
}
117+
118+
@Test func startFullSync_propagates_network_errors() async throws {
119+
// Given
120+
let expectedError = NSError(domain: "network", code: 500, userInfo: [NSLocalizedDescriptionKey: "Network error"])
121+
mockSyncRemote.setProductResult(pageNumber: 1, result: .failure(expectedError))
122+
123+
// When/Then
124+
await #expect(throws: expectedError) {
125+
_ = try await sut.startFullSync(for: sampleSiteID)
126+
}
127+
}
128+
129+
// MARK: - Initialization Tests
130+
131+
@Test func init_with_valid_credentials_creates_service() {
132+
// Given
133+
let credentials = Credentials.wpcom(username: "test", authToken: "token", siteAddress: "site.com")
134+
135+
// When
136+
let service = POSCatalogFullSyncService(credentials: credentials)
137+
138+
// Then
139+
#expect(service != nil)
140+
}
141+
142+
@Test func init_with_nil_credentials_returns_nil() {
143+
// Given/When
144+
let service = POSCatalogFullSyncService(credentials: nil)
145+
146+
// Then
147+
#expect(service == nil)
148+
}
149+
150+
@Test func init_with_custom_batch_size_uses_specified_size() async throws {
151+
// Given
152+
let customBatchSize = 5
153+
154+
// When
155+
let service = POSCatalogFullSyncService(syncRemote: mockSyncRemote, batchSize: customBatchSize)
156+
_ = try await service.startFullSync(for: sampleSiteID)
157+
158+
// Then
159+
#expect(mockSyncRemote.loadProductsCallCount == 5)
160+
#expect(mockSyncRemote.loadProductVariationsCallCount == 5)
161+
}
162+
}
163+
164+
// MARK: - Mock POSCatalogSyncRemote
165+
166+
final class MockPOSCatalogSyncRemote: POSCatalogSyncRemoteProtocol {
167+
// Dictionary mapping pageNumber to Result for products and variations.
168+
private(set) var productResults: [Int: Result<PagedItems<POSProduct>, Error>] = [:]
169+
private(set) var variationResults: [Int: Result<PagedItems<POSProductVariation>, Error>] = [:]
170+
171+
private(set) var loadProductsCallCount = 0
172+
private(set) var loadProductVariationsCallCount = 0
173+
174+
// Fallback result when no specific page result is configured
175+
private let fallbackResult = PagedItems(items: [] as [POSProduct], hasMorePages: false, totalItems: 0)
176+
private let fallbackVariationResult = PagedItems(items: [] as [POSProductVariation], hasMorePages: false, totalItems: 0)
177+
178+
func setProductResult(pageNumber: Int, result: Result<PagedItems<POSProduct>, Error>) {
179+
productResults[pageNumber] = result
180+
}
181+
182+
func setVariationResult(pageNumber: Int, result: Result<PagedItems<POSProductVariation>, Error>) {
183+
variationResults[pageNumber] = result
184+
}
185+
186+
func setProductResults(_ results: [PagedItems<POSProduct>]) {
187+
for (index, pagedItems) in results.enumerated() {
188+
productResults[index + 1] = .success(pagedItems)
189+
}
190+
}
191+
192+
func setVariationResults(_ results: [PagedItems<POSProductVariation>]) {
193+
for (index, pagedItems) in results.enumerated() {
194+
variationResults[index + 1] = .success(pagedItems)
195+
}
196+
}
197+
198+
func loadProducts(modifiedAfter: Date, siteID: Int64, pageNumber: Int) async throws -> PagedItems<POSProduct> {
199+
try await loadProducts(siteID: siteID, pageNumber: pageNumber)
200+
}
201+
202+
func loadProductVariations(modifiedAfter: Date, siteID: Int64, pageNumber: Int) async throws -> PagedItems<POSProductVariation> {
203+
try await loadProductVariations(siteID: siteID, pageNumber: pageNumber)
204+
}
205+
206+
func loadProducts(siteID: Int64, pageNumber: Int) async throws -> PagedItems<POSProduct> {
207+
loadProductsCallCount += 1
208+
209+
if let result = productResults[pageNumber] {
210+
switch result {
211+
case .success(let pagedItems):
212+
return pagedItems
213+
case .failure(let error):
214+
throw error
215+
}
216+
}
217+
return fallbackResult
218+
}
219+
220+
func loadProductVariations(siteID: Int64, pageNumber: Int) async throws -> PagedItems<POSProductVariation> {
221+
loadProductVariationsCallCount += 1
222+
223+
if let result = variationResults[pageNumber] {
224+
switch result {
225+
case .success(let pagedItems):
226+
return pagedItems
227+
case .failure(let error):
228+
throw error
229+
}
230+
}
231+
return fallbackVariationResult
232+
}
233+
}

0 commit comments

Comments
 (0)