@@ -275,7 +275,7 @@ struct POSCatalogPersistenceServiceTests {
275275 }
276276 }
277277
278- @Test func persistIncrementalCatalogData_upserts_attributes_for_updated_product ( ) async throws {
278+ @Test func persistIncrementalCatalogData_replaces_attributes_for_updated_product ( ) async throws {
279279 // Given
280280 let attribute1 = Yosemite . ProductAttribute. fake ( ) . copy ( name: " Color " , options: [ " Indigo " , " Blue " ] )
281281 let attribute2 = Yosemite . ProductAttribute. fake ( ) . copy ( name: " Size " )
@@ -291,17 +291,40 @@ struct POSCatalogPersistenceServiceTests {
291291
292292 // Then
293293 try await grdbManager. databaseConnection. read { db in
294- // Should have 4 attributes: original 2 + updated 2 (upsert adds new ones, keeps old ones )
294+ // Should have 2 attributes - old ones deleted, new ones added (no duplicates )
295295 let attributeCount = try PersistedProductAttribute . fetchCount ( db)
296- #expect( attributeCount == 4 )
296+ #expect( attributeCount == 2 )
297297
298298 let attributes = try PersistedProductAttribute . fetchAll ( db) . sorted ( by: { $0. name < $1. name } )
299299 #expect( attributes [ 0 ] . name == " Color " )
300- #expect( attributes [ 0 ] . options == [ " Indigo " , " Blue " ] ) // Original unchanged
301- #expect( attributes [ 1 ] . name == " Color " )
302- #expect( attributes [ 1 ] . options == [ " Cardinal " , " Blue " ] ) // Updated version
303- #expect( attributes [ 2 ] . name == " Material " ) // New attribute
304- #expect( attributes [ 3 ] . name == " Size " ) // Original unchanged
300+ #expect( attributes [ 0 ] . options == [ " Cardinal " , " Blue " ] ) // Updated version
301+ #expect( attributes [ 1 ] . name == " Material " ) // New attribute
302+ }
303+ }
304+
305+ @Test func persistIncrementalCatalogData_prevents_duplicate_attributes_on_multiple_syncs( ) async throws {
306+ // Given - product with attributes
307+ let attribute1 = Yosemite . ProductAttribute. fake ( ) . copy ( name: " Color " , options: [ " Red " , " Blue " ] )
308+ let attribute2 = Yosemite . ProductAttribute. fake ( ) . copy ( name: " Size " , options: [ " S " , " M " , " L " ] )
309+ let product = POSProduct . fake ( ) . copy ( siteID: sampleSiteID, productID: 1 , attributes: [ attribute1, attribute2] )
310+ try await insertProduct ( product)
311+
312+ // When - perform multiple incremental syncs with the same product/attributes
313+ let catalog = POSCatalog ( products: [ product] , variations: [ ] , syncDate: . now)
314+ try await sut. persistIncrementalCatalogData ( catalog, siteID: sampleSiteID)
315+ try await sut. persistIncrementalCatalogData ( catalog, siteID: sampleSiteID)
316+ try await sut. persistIncrementalCatalogData ( catalog, siteID: sampleSiteID)
317+
318+ // Then - should have exactly 2 attributes, not duplicates
319+ try await grdbManager. databaseConnection. read { db in
320+ let attributeCount = try PersistedProductAttribute . fetchCount ( db)
321+ #expect( attributeCount == 2 )
322+
323+ let attributes = try PersistedProductAttribute . fetchAll ( db) . sorted ( by: { $0. name < $1. name } )
324+ #expect( attributes [ 0 ] . name == " Color " )
325+ #expect( attributes [ 0 ] . options == [ " Red " , " Blue " ] )
326+ #expect( attributes [ 1 ] . name == " Size " )
327+ #expect( attributes [ 1 ] . options == [ " S " , " M " , " L " ] )
305328 }
306329 }
307330
@@ -449,6 +472,36 @@ struct POSCatalogPersistenceServiceTests {
449472 }
450473 }
451474
475+ @Test func persistIncrementalCatalogData_prevents_duplicate_variation_attributes_on_multiple_syncs( ) async throws {
476+ // Given - variation with attributes
477+ let parentProduct = POSProduct . fake ( ) . copy ( siteID: sampleSiteID, productID: 10 )
478+ let attribute1 = Yosemite . ProductVariationAttribute. fake ( ) . copy ( name: " Color " , option: " Red " )
479+ let attribute2 = Yosemite . ProductVariationAttribute. fake ( ) . copy ( name: " Size " , option: " L " )
480+ let variation = POSProductVariation . fake ( ) . copy ( siteID: sampleSiteID, productID: 10 , productVariationID: 1 , attributes: [ attribute1, attribute2] )
481+ try await insertProduct ( parentProduct)
482+ try await insertVariation ( variation)
483+
484+ // When - perform multiple incremental syncs with the same variation/attributes
485+ let catalog = POSCatalog ( products: [ parentProduct] , variations: [ variation] , syncDate: . now)
486+ try await sut. persistIncrementalCatalogData ( catalog, siteID: sampleSiteID)
487+ try await sut. persistIncrementalCatalogData ( catalog, siteID: sampleSiteID)
488+ try await sut. persistIncrementalCatalogData ( catalog, siteID: sampleSiteID)
489+
490+ // Then - should have exactly 2 attributes, not duplicates
491+ try await grdbManager. databaseConnection. read { db in
492+ let attributeCount = try PersistedProductVariationAttribute . fetchCount ( db)
493+ #expect( attributeCount == 2 )
494+
495+ let attributes = try PersistedProductVariationAttribute . fetchAll ( db) . sorted ( by: { $0. name < $1. name } )
496+ #expect( attributes [ 0 ] . name == " Color " )
497+ #expect( attributes [ 0 ] . option == " Red " )
498+ #expect( attributes [ 0 ] . productVariationID == 1 )
499+ #expect( attributes [ 1 ] . name == " Size " )
500+ #expect( attributes [ 1 ] . option == " L " )
501+ #expect( attributes [ 1 ] . productVariationID == 1 )
502+ }
503+ }
504+
452505 @Test func persistIncrementalCatalogData_replaces_image_for_updated_variation( ) async throws {
453506 // Given
454507 let parentProduct = POSProduct . fake ( ) . copy ( siteID: sampleSiteID, productID: 10 )
0 commit comments