Skip to content

Commit 9c2e389

Browse files
committed
Add tests for checking size before sync
1 parent 29370f0 commit 9c2e389

File tree

7 files changed

+587
-6
lines changed

7 files changed

+587
-6
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
115115
throw POSCatalogSyncError.syncAlreadyInProgress(siteID: siteID)
116116
}
117117

118+
guard let catalogSize = try? await catalogSizeChecker.checkCatalogSize(for: siteID) else {
119+
DDLogError("📋 POSCatalogSyncCoordinator: Could not get catalog size for site \(siteID)")
120+
return
121+
}
122+
123+
guard catalogSize.totalCount <= 1000 else {
124+
DDLogInfo("📋 POSCatalogSyncCoordinator: Site \(siteID) has catalog size \(catalogSize.totalCount), greater than 1000, should not perform incremental sync.")
125+
return
126+
}
127+
118128
guard let lastFullSyncDate = await lastFullSyncDate(for: siteID) else {
119129
DDLogInfo("📋 POSCatalogSyncCoordinator: No full sync performed yet for site \(siteID), skipping incremental sync")
120130
return

Modules/Tests/NetworkingTests/Remote/POSCatalogSyncRemoteTests.swift

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,4 +416,178 @@ struct POSCatalogSyncRemoteTests {
416416
#expect(fieldNames.contains("stock_quantity"))
417417
#expect(fieldNames.contains("stock_status"))
418418
}
419+
420+
// MARK: - Count Endpoints Tests
421+
422+
@Test func getProductCount_uses_correct_path() async throws {
423+
// Given
424+
let remote = POSCatalogSyncRemote(network: network)
425+
network.responseHeaders = ["X-WP-Total": "150"]
426+
427+
// When
428+
_ = try? await remote.getProductCount(siteID: sampleSiteID)
429+
430+
// Then - verify correct path was used
431+
let request = try #require(network.requestsForResponseData.last as? JetpackRequest)
432+
#expect(request.path.contains("products"))
433+
}
434+
435+
@Test func getProductCount_returns_count_from_total_header() async throws {
436+
// Given
437+
let remote = POSCatalogSyncRemote(network: network)
438+
let expectedCount = 500
439+
network.responseHeaders = ["X-WP-Total": "\(expectedCount)"]
440+
network.simulateResponse(requestUrlSuffix: "products", filename: "empty-data-array")
441+
442+
// When
443+
let count = try await remote.getProductCount(siteID: sampleSiteID)
444+
445+
// Then
446+
#expect(count == expectedCount)
447+
}
448+
449+
@Test func getProductCount_returns_zero_when_header_missing() async throws {
450+
// Given
451+
let remote = POSCatalogSyncRemote(network: network)
452+
network.responseHeaders = nil
453+
network.simulateResponse(requestUrlSuffix: "products", filename: "empty-data-array")
454+
455+
// When
456+
let count = try await remote.getProductCount(siteID: sampleSiteID)
457+
458+
// Then
459+
#expect(count == 0)
460+
}
461+
462+
@Test func getProductCount_returns_zero_when_header_invalid() async throws {
463+
// Given
464+
let remote = POSCatalogSyncRemote(network: network)
465+
network.responseHeaders = ["X-WP-Total": "invalid-number"]
466+
network.simulateResponse(requestUrlSuffix: "products", filename: "empty-data-array")
467+
468+
// When
469+
let count = try await remote.getProductCount(siteID: sampleSiteID)
470+
471+
// Then
472+
#expect(count == 0)
473+
}
474+
475+
@Test func getProductCount_relays_networking_error() async throws {
476+
// Given
477+
let remote = POSCatalogSyncRemote(network: network)
478+
479+
// When/Then
480+
await #expect(throws: NetworkError.notFound()) {
481+
try await remote.getProductCount(siteID: sampleSiteID)
482+
}
483+
}
484+
485+
@Test func getProductVariationCount_uses_correct_path() async throws {
486+
// Given
487+
let remote = POSCatalogSyncRemote(network: network)
488+
network.responseHeaders = ["X-WP-Total": "75"]
489+
network.simulateResponse(requestUrlSuffix: "variations", filename: "empty-data-array")
490+
491+
// When
492+
_ = try? await remote.getProductVariationCount(siteID: sampleSiteID)
493+
494+
// Then - verify correct path was used
495+
let request = try #require(network.requestsForResponseData.last as? JetpackRequest)
496+
#expect(request.path.contains("variations"))
497+
}
498+
499+
@Test func getProductVariationCount_returns_count_from_total_header() async throws {
500+
// Given
501+
let remote = POSCatalogSyncRemote(network: network)
502+
let expectedCount = 250
503+
network.responseHeaders = ["X-WP-Total": "\(expectedCount)"]
504+
network.simulateResponse(requestUrlSuffix: "variations", filename: "empty-data-array")
505+
506+
// When
507+
let count = try await remote.getProductVariationCount(siteID: sampleSiteID)
508+
509+
// Then
510+
#expect(count == expectedCount)
511+
}
512+
513+
@Test func getProductVariationCount_returns_zero_when_header_missing() async throws {
514+
// Given
515+
let remote = POSCatalogSyncRemote(network: network)
516+
network.responseHeaders = nil
517+
network.simulateResponse(requestUrlSuffix: "variations", filename: "empty-data-array")
518+
519+
// When
520+
let count = try await remote.getProductVariationCount(siteID: sampleSiteID)
521+
522+
// Then
523+
#expect(count == 0)
524+
}
525+
526+
@Test func getProductVariationCount_returns_zero_when_header_invalid() async throws {
527+
// Given
528+
let remote = POSCatalogSyncRemote(network: network)
529+
network.responseHeaders = ["X-WP-Total": "not-a-number"]
530+
network.simulateResponse(requestUrlSuffix: "variations", filename: "empty-data-array")
531+
532+
// When
533+
let count = try await remote.getProductVariationCount(siteID: sampleSiteID)
534+
535+
// Then
536+
#expect(count == 0)
537+
}
538+
539+
@Test func getProductVariationCount_relays_networking_error() async throws {
540+
// Given
541+
let remote = POSCatalogSyncRemote(network: network)
542+
543+
// When/Then
544+
await #expect(throws: NetworkError.notFound()) {
545+
try await remote.getProductVariationCount(siteID: sampleSiteID)
546+
}
547+
}
548+
549+
@Test func getProductCount_handles_very_large_counts() async throws {
550+
// Given
551+
let remote = POSCatalogSyncRemote(network: network)
552+
let largeCount = 999999
553+
network.responseHeaders = ["X-WP-Total": "\(largeCount)"]
554+
network.simulateResponse(requestUrlSuffix: "products", filename: "empty-data-array")
555+
556+
// When
557+
let count = try await remote.getProductCount(siteID: sampleSiteID)
558+
559+
// Then
560+
#expect(count == largeCount)
561+
}
562+
563+
@Test func getProductVariationCount_handles_very_large_counts() async throws {
564+
// Given
565+
let remote = POSCatalogSyncRemote(network: network)
566+
let largeCount = 888888
567+
network.responseHeaders = ["X-WP-Total": "\(largeCount)"]
568+
network.simulateResponse(requestUrlSuffix: "variations", filename: "empty-data-array")
569+
570+
// When
571+
let count = try await remote.getProductVariationCount(siteID: sampleSiteID)
572+
573+
// Then
574+
#expect(count == largeCount)
575+
}
576+
577+
@Test func count_endpoints_use_correct_api_versions() async throws {
578+
// Given
579+
let remote = POSCatalogSyncRemote(network: network)
580+
network.responseHeaders = ["X-WP-Total": "10"]
581+
582+
// When - make both count calls
583+
_ = try? await remote.getProductCount(siteID: sampleSiteID)
584+
_ = try? await remote.getProductVariationCount(siteID: sampleSiteID)
585+
586+
// Then - verify API versions match the data endpoints
587+
// Products should use .mark3, variations should use .wcAnalytics (based on the load endpoints)
588+
// This is verified by checking that the correct paths are called
589+
let requests = network.requestsForResponseData.compactMap { $0 as? JetpackRequest }
590+
#expect(requests.contains { $0.path.contains("products") })
591+
#expect(requests.contains { $0.path.contains("variations") })
592+
}
419593
}

Modules/Tests/NetworkingTests/Remote/RemoteTests.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,92 @@ final class RemoteTests: XCTestCase {
10161016
XCTAssertTrue(result.1 as? NetworkError == error)
10171017
}
10181018
}
1019+
1020+
// MARK: - Tests for enqueueWithResponseHeaders
1021+
1022+
/// Verifies that `enqueueWithResponseHeaders` properly wraps up the received request and returns headers
1023+
///
1024+
func test_enqueueWithResponseHeaders_wraps_up_request_and_returns_headers() async throws {
1025+
// Given
1026+
let network = MockNetwork()
1027+
let remote = Remote(network: network)
1028+
let expectedHeaders = ["Content-Type": "application/json", "X-Total-Count": "150"]
1029+
1030+
network.simulateResponse(requestUrlSuffix: "something", filename: "order")
1031+
network.responseHeaders = expectedHeaders
1032+
1033+
// When
1034+
let headers = try await remote.enqueueWithResponseHeaders(request)
1035+
1036+
// Then
1037+
let receivedRequest = try XCTUnwrap(network.requestsForResponseData.first as? JetpackRequest)
1038+
XCTAssertEqual(network.requestsForResponseData.count, 1)
1039+
XCTAssertEqual(receivedRequest.method, request.method)
1040+
XCTAssertEqual(receivedRequest.path, request.path)
1041+
XCTAssertEqual(headers, expectedHeaders)
1042+
}
1043+
1044+
/// Verifies that `enqueueWithResponseHeaders` returns empty dictionary when no headers are provided
1045+
///
1046+
func test_enqueueWithResponseHeaders_returns_empty_dictionary_when_no_headers() async throws {
1047+
// Given
1048+
let network = MockNetwork()
1049+
let remote = Remote(network: network)
1050+
1051+
network.simulateResponse(requestUrlSuffix: "something", filename: "order")
1052+
1053+
// When
1054+
let headers = try await remote.enqueueWithResponseHeaders(request)
1055+
1056+
// Then
1057+
XCTAssertEqual(headers, [:])
1058+
}
1059+
1060+
/// Verifies that `enqueueWithResponseHeaders` propagates NetworkError properly
1061+
///
1062+
func test_enqueueWithResponseHeaders_propagates_NetworkError() async throws {
1063+
// Given
1064+
let network = MockNetwork()
1065+
let remote = Remote(network: network)
1066+
let expectedError = NetworkError.notFound()
1067+
1068+
network.simulateError(requestUrlSuffix: "something", error: expectedError)
1069+
1070+
// When/Then
1071+
do {
1072+
_ = try await remote.enqueueWithResponseHeaders(request)
1073+
XCTFail("Expected error to be thrown")
1074+
} catch {
1075+
XCTAssertTrue(error as? NetworkError == expectedError)
1076+
}
1077+
}
1078+
1079+
/// Verifies that `enqueueWithResponseHeaders` handles various header types correctly
1080+
///
1081+
func test_enqueueWithResponseHeaders_handles_various_header_types() async throws {
1082+
// Given
1083+
let network = MockNetwork()
1084+
let remote = Remote(network: network)
1085+
let expectedHeaders = [
1086+
"Content-Type": "application/json",
1087+
"X-Total-Count": "500",
1088+
"X-WC-Total": "250",
1089+
"Cache-Control": "no-cache",
1090+
"Set-Cookie": "session=abc123"
1091+
]
1092+
1093+
network.simulateResponse(requestUrlSuffix: "something", filename: "order")
1094+
network.responseHeaders = expectedHeaders
1095+
1096+
// When
1097+
let headers = try await remote.enqueueWithResponseHeaders(request)
1098+
1099+
// Then
1100+
XCTAssertEqual(headers.count, expectedHeaders.count)
1101+
for (key, value) in expectedHeaders {
1102+
XCTAssertEqual(headers[key], value, "Header \(key) should match expected value")
1103+
}
1104+
}
10191105
}
10201106

10211107

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
import Foundation
22
@testable import Yosemite
33

4-
struct MockPOSCatalogSizeChecker: POSCatalogSizeCheckerProtocol {
4+
final class MockPOSCatalogSizeChecker: POSCatalogSizeCheckerProtocol {
5+
// MARK: - checkCatalogSize tracking
6+
private(set) var checkCatalogSizeCallCount = 0
7+
private(set) var lastCheckedSiteID: Int64?
8+
var checkCatalogSizeResult: Result<POSCatalogSize, Error> = .success(POSCatalogSize(productCount: 100, variationCount: 50)) // 150 total - well under limit
9+
510
func checkCatalogSize(for siteID: Int64) async throws -> POSCatalogSize {
6-
return POSCatalogSize(productCount: 0, variationCount: 0)
11+
checkCatalogSizeCallCount += 1
12+
lastCheckedSiteID = siteID
13+
14+
switch checkCatalogSizeResult {
15+
case .success(let size):
16+
return size
17+
case .failure(let error):
18+
throw error
19+
}
720
}
821
}

Modules/Tests/YosemiteTests/Mocks/MockPOSCatalogSyncRemote.swift

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,48 @@ final class MockPOSCatalogSyncRemote: POSCatalogSyncRemoteProtocol {
127127
}
128128

129129
// MARK: - Protocol Methods - Catalog size
130-
var productCount: Int = 0
130+
131+
// MARK: - getProductCount tracking
132+
private(set) var getProductCountCallCount = 0
133+
private(set) var lastProductCountSiteID: Int64?
134+
var getProductCountResult: Result<Int, Error> = .success(0)
135+
var productCountDelay: UInt64 = 0
136+
137+
// MARK: - getProductVariationCount tracking
138+
private(set) var getProductVariationCountCallCount = 0
139+
private(set) var lastVariationCountSiteID: Int64?
140+
var getProductVariationCountResult: Result<Int, Error> = .success(0)
141+
var variationCountDelay: UInt64 = 0
142+
131143
func getProductCount(siteID: Int64) async throws -> Int {
132-
return productCount
144+
getProductCountCallCount += 1
145+
lastProductCountSiteID = siteID
146+
147+
if productCountDelay > 0 {
148+
try await Task.sleep(nanoseconds: productCountDelay)
149+
}
150+
151+
switch getProductCountResult {
152+
case .success(let count):
153+
return count
154+
case .failure(let error):
155+
throw error
156+
}
133157
}
134158

135-
var variationCount: Int = 0
136159
func getProductVariationCount(siteID: Int64) async throws -> Int {
137-
return variationCount
160+
getProductVariationCountCallCount += 1
161+
lastVariationCountSiteID = siteID
162+
163+
if variationCountDelay > 0 {
164+
try await Task.sleep(nanoseconds: variationCountDelay)
165+
}
166+
167+
switch getProductVariationCountResult {
168+
case .success(let count):
169+
return count
170+
case .failure(let error):
171+
throw error
172+
}
138173
}
139174
}

0 commit comments

Comments
 (0)