Skip to content

Commit d17e8a4

Browse files
authored
[Woo POS][Local Catalog] Delete data from all grdb tables on logout (#16114)
2 parents 4f7da41 + e931cbe commit d17e8a4

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

Modules/Sources/Storage/GRDB/GRDBManager.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import GRDB
33

44
public protocol GRDBManagerProtocol {
55
var databaseConnection: GRDBDatabaseConnection { get }
6+
func reset() throws
67
}
78

89
public protocol GRDBDatabaseConnection: DatabaseReader & DatabaseWriter {}
@@ -27,6 +28,31 @@ public final class GRDBManager: GRDBManagerProtocol {
2728
self.databaseConnection = try DatabaseQueue()
2829
try migrateIfNeeded()
2930
}
31+
32+
/// Resets the database by deleting all data from all tables
33+
/// Used when user logs out to ensure no data leaks between sessions
34+
public func reset() throws {
35+
try databaseConnection.write { db in
36+
// Disable foreign key constraints temporarily to avoid dependency issues
37+
try db.execute(sql: "PRAGMA foreign_keys = OFF")
38+
39+
// Get all user tables (excluding sqlite internal tables)
40+
let tableNames = try String.fetchAll(db, sql: """
41+
SELECT name FROM sqlite_master
42+
WHERE type = 'table'
43+
AND name NOT LIKE 'sqlite_%'
44+
AND name NOT LIKE 'grdb_%'
45+
""")
46+
47+
// Delete all data from each table
48+
for tableName in tableNames {
49+
try db.execute(sql: "DELETE FROM \(tableName)")
50+
}
51+
52+
// Re-enable foreign key constraints
53+
try db.execute(sql: "PRAGMA foreign_keys = ON")
54+
}
55+
}
3056
}
3157

3258
private extension GRDBManager {

Modules/Tests/StorageTests/GRDB/GRDBManagerTests.swift

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,146 @@ struct GRDBManagerTests {
502502
}
503503
}
504504
}
505+
506+
struct ResetTests {
507+
let manager: GRDBManager
508+
let sampleSiteID: Int64 = 1
509+
510+
init() throws {
511+
self.manager = try GRDBManager()
512+
try manager.databaseConnection.write { db in
513+
let record = TestSite(id: sampleSiteID)
514+
try record.insert(db)
515+
}
516+
}
517+
518+
@Test("Reset clears all data from database")
519+
func test_reset_clears_all_data_from_database() throws {
520+
// Given - Insert comprehensive test data
521+
try manager.databaseConnection.write { db in
522+
// Insert second site for more comprehensive test
523+
let site2 = TestSite(id: 2)
524+
try site2.insert(db)
525+
526+
// Insert products for both sites
527+
for siteID in [sampleSiteID, Int64(2)] {
528+
for i in 1...2 {
529+
let product = TestProduct(
530+
siteID: siteID,
531+
id: Int64(i + (siteID == 1 ? 0 : 10)),
532+
name: "Product \(i) Site \(siteID)",
533+
productTypeKey: "variable",
534+
price: "\(i * 10).00",
535+
downloadable: false,
536+
parentID: 0,
537+
manageStock: false,
538+
stockStatusKey: ""
539+
)
540+
try product.insert(db)
541+
542+
// Insert variations
543+
let variation = TestProductVariation(
544+
siteID: siteID,
545+
id: Int64(200 + i + (siteID == 1 ? 0 : 10)),
546+
productID: product.id,
547+
price: "\(i * 12).00",
548+
downloadable: false,
549+
manageStock: false,
550+
stockStatusKey: ""
551+
)
552+
try variation.insert(db)
553+
554+
// Insert product attributes
555+
let productAttribute = TestProductAttribute(
556+
productID: product.id,
557+
name: "Color \(i)",
558+
position: i,
559+
visible: true,
560+
variation: true,
561+
options: ["Red", "Blue", "Green"]
562+
)
563+
try productAttribute.insert(db)
564+
565+
// Insert variation attributes
566+
let variationAttribute = TestProductVariationAttribute(
567+
productVariationID: variation.id,
568+
name: "Size \(i)",
569+
option: "Large"
570+
)
571+
try variationAttribute.insert(db)
572+
}
573+
}
574+
}
575+
576+
// Verify data exists before reset
577+
let countsBefore = try manager.databaseConnection.read { db in
578+
return (
579+
sites: try TestSite.fetchCount(db),
580+
products: try TestProduct.fetchCount(db),
581+
variations: try TestProductVariation.fetchCount(db),
582+
productAttributes: try TestProductAttribute.fetchCount(db),
583+
variationAttributes: try TestProductVariationAttribute.fetchCount(db)
584+
)
585+
}
586+
587+
#expect(countsBefore.sites == 2) // Original site + new site
588+
#expect(countsBefore.products == 4)
589+
#expect(countsBefore.variations == 4)
590+
#expect(countsBefore.productAttributes == 4)
591+
#expect(countsBefore.variationAttributes == 4)
592+
593+
// When - Reset database
594+
try manager.reset()
595+
596+
// Then - All data should be cleared
597+
let countsAfter = try manager.databaseConnection.read { db in
598+
return (
599+
sites: try TestSite.fetchCount(db),
600+
products: try TestProduct.fetchCount(db),
601+
variations: try TestProductVariation.fetchCount(db),
602+
productAttributes: try TestProductAttribute.fetchCount(db),
603+
variationAttributes: try TestProductVariationAttribute.fetchCount(db)
604+
)
605+
}
606+
607+
#expect(countsAfter.sites == 0)
608+
#expect(countsAfter.products == 0)
609+
#expect(countsAfter.variations == 0)
610+
#expect(countsAfter.productAttributes == 0)
611+
#expect(countsAfter.variationAttributes == 0)
612+
}
613+
614+
@Test("Reset can be called multiple times without error")
615+
func test_reset_can_be_called_multiple_times() throws {
616+
// Given - Some test data
617+
try manager.databaseConnection.write { db in
618+
let product = TestProduct(
619+
siteID: sampleSiteID,
620+
id: 100,
621+
name: "Test Product",
622+
productTypeKey: "simple",
623+
price: "10.00",
624+
downloadable: false,
625+
parentID: 0,
626+
manageStock: false,
627+
stockStatusKey: ""
628+
)
629+
try product.insert(db)
630+
}
631+
632+
// When - Reset multiple times
633+
try manager.reset()
634+
try manager.reset()
635+
try manager.reset()
636+
637+
// Then - Should not throw and database should be empty
638+
let productCount = try manager.databaseConnection.read { db in
639+
try TestProduct.fetchCount(db)
640+
}
641+
642+
#expect(productCount == 0)
643+
}
644+
}
505645
}
506646

507647
// MARK: - Test Models

WooCommerce/Classes/Yosemite/DefaultStoresManager.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,15 @@ class DefaultStoresManager: StoresManager {
281281
ServiceLocator.analytics.refreshUserData()
282282
ZendeskProvider.shared.reset()
283283
ServiceLocator.storageManager.reset()
284+
285+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleLocalCatalogi1) {
286+
do {
287+
try ServiceLocator.grdbManager.reset()
288+
} catch {
289+
DDLogError("Could not reset GRDB database: \(error)")
290+
}
291+
}
292+
284293
ServiceLocator.productImageUploader.reset()
285294

286295
updateAndReloadWidgetInformation(with: nil)

0 commit comments

Comments
 (0)