Skip to content

Commit c8889d7

Browse files
committed
Delete data from all grdb tables on logout
1 parent 581525c commit c8889d7

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-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
@@ -461,6 +461,146 @@ struct GRDBManagerTests {
461461
#expect(variations.allSatisfy { $0.siteID == sampleSiteID })
462462
#expect(variations.allSatisfy { $0.productID == 100 })
463463
}
464+
}
465+
466+
struct ResetTests {
467+
let manager: GRDBManager
468+
let sampleSiteID: Int64 = 1
469+
470+
init() throws {
471+
self.manager = try GRDBManager()
472+
try manager.databaseConnection.write { db in
473+
let record = TestSite(id: sampleSiteID)
474+
try record.insert(db)
475+
}
476+
}
477+
478+
@Test("Reset clears all data from database")
479+
func test_reset_clears_all_data_from_database() throws {
480+
// Given - Insert comprehensive test data
481+
try manager.databaseConnection.write { db in
482+
// Insert second site for more comprehensive test
483+
let site2 = TestSite(id: 2)
484+
try site2.insert(db)
485+
486+
// Insert products for both sites
487+
for siteID in [sampleSiteID, Int64(2)] {
488+
for i in 1...2 {
489+
let product = TestProduct(
490+
siteID: siteID,
491+
id: Int64(i + (siteID == 1 ? 0 : 10)),
492+
name: "Product \(i) Site \(siteID)",
493+
productTypeKey: "variable",
494+
price: "\(i * 10).00",
495+
downloadable: false,
496+
parentID: 0,
497+
manageStock: false,
498+
stockStatusKey: ""
499+
)
500+
try product.insert(db)
501+
502+
// Insert variations
503+
let variation = TestProductVariation(
504+
siteID: siteID,
505+
id: Int64(200 + i + (siteID == 1 ? 0 : 10)),
506+
productID: product.id,
507+
price: "\(i * 12).00",
508+
downloadable: false,
509+
manageStock: false,
510+
stockStatusKey: ""
511+
)
512+
try variation.insert(db)
513+
514+
// Insert product attributes
515+
let productAttribute = TestProductAttribute(
516+
productID: product.id,
517+
name: "Color \(i)",
518+
position: i,
519+
visible: true,
520+
variation: true,
521+
options: ["Red", "Blue", "Green"]
522+
)
523+
try productAttribute.insert(db)
524+
525+
// Insert variation attributes
526+
let variationAttribute = TestProductVariationAttribute(
527+
productVariationID: variation.id,
528+
name: "Size \(i)",
529+
option: "Large"
530+
)
531+
try variationAttribute.insert(db)
532+
}
533+
}
534+
}
535+
536+
// Verify data exists before reset
537+
let countsBefore = try manager.databaseConnection.read { db in
538+
return (
539+
sites: try TestSite.fetchCount(db),
540+
products: try TestProduct.fetchCount(db),
541+
variations: try TestProductVariation.fetchCount(db),
542+
productAttributes: try TestProductAttribute.fetchCount(db),
543+
variationAttributes: try TestProductVariationAttribute.fetchCount(db)
544+
)
545+
}
546+
547+
#expect(countsBefore.sites == 2) // Original site + new site
548+
#expect(countsBefore.products == 4)
549+
#expect(countsBefore.variations == 4)
550+
#expect(countsBefore.productAttributes == 4)
551+
#expect(countsBefore.variationAttributes == 4)
552+
553+
// When - Reset database
554+
try manager.reset()
555+
556+
// Then - All data should be cleared
557+
let countsAfter = try manager.databaseConnection.read { db in
558+
return (
559+
sites: try TestSite.fetchCount(db),
560+
products: try TestProduct.fetchCount(db),
561+
variations: try TestProductVariation.fetchCount(db),
562+
productAttributes: try TestProductAttribute.fetchCount(db),
563+
variationAttributes: try TestProductVariationAttribute.fetchCount(db)
564+
)
565+
}
566+
567+
#expect(countsAfter.sites == 0)
568+
#expect(countsAfter.products == 0)
569+
#expect(countsAfter.variations == 0)
570+
#expect(countsAfter.productAttributes == 0)
571+
#expect(countsAfter.variationAttributes == 0)
572+
}
573+
574+
@Test("Reset can be called multiple times without error")
575+
func test_reset_can_be_called_multiple_times() throws {
576+
// Given - Some test data
577+
try manager.databaseConnection.write { db in
578+
let product = TestProduct(
579+
siteID: sampleSiteID,
580+
id: 100,
581+
name: "Test Product",
582+
productTypeKey: "simple",
583+
price: "10.00",
584+
downloadable: false,
585+
parentID: 0,
586+
manageStock: false,
587+
stockStatusKey: ""
588+
)
589+
try product.insert(db)
590+
}
591+
592+
// When - Reset multiple times
593+
try manager.reset()
594+
try manager.reset()
595+
try manager.reset()
596+
597+
// Then - Should not throw and database should be empty
598+
let productCount = try manager.databaseConnection.read { db in
599+
try TestProduct.fetchCount(db)
600+
}
601+
602+
#expect(productCount == 0)
603+
}
464604

465605
@Test("Cannot insert product without valid site")
466606
func test_cannot_insert_product_without_valid_site() throws {

WooCommerce/Classes/Yosemite/DefaultStoresManager.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ class DefaultStoresManager: StoresManager {
269269
ServiceLocator.analytics.refreshUserData()
270270
ZendeskProvider.shared.reset()
271271
ServiceLocator.storageManager.reset()
272+
do {
273+
try ServiceLocator.grdbManager.reset()
274+
} catch {
275+
DDLogError("Could not reset GRDB database: \(error)")
276+
}
272277
ServiceLocator.productImageUploader.reset()
273278

274279
updateAndReloadWidgetInformation(with: nil)

0 commit comments

Comments
 (0)