// // TestDBFactory.swift // DailyNotificationPluginTests // // Created by Matthew Raymer on 2025-12-16 // Copyright © 2025 TimeSafari. All rights reserved. // import Foundation import SQLite3 @testable import DailyNotificationPlugin /** * Test database factory for recovery testing * * Provides utilities to create test databases with intentionally invalid/corrupt data * for testing recovery scenarios. */ class TestDBFactory { /** * Create a clean test database * * @return Tuple of (database, path) */ static func createCleanDatabase() -> (DailyNotificationDatabase, String) { let testDbPath = NSTemporaryDirectory().appending("test_recovery_db_\(UUID().uuidString).sqlite") let database = DailyNotificationDatabase(path: testDbPath) return (database, testDbPath) } /** * Inject invalid notification record directly into database * * @param database Database instance * @param id Notification ID (can be empty for invalid test) * @param scheduledTime Scheduled time (can be invalid/negative) * @param payloadJSON Payload (can be invalid JSON) */ static func injectInvalidNotificationRecord( database: DailyNotificationDatabase, id: String = "", scheduledTime: Int64 = -1, payloadJSON: String = "invalid json {" ) { // Direct SQL injection for testing (using executeSQL which is public) let escapedId = id.replacingOccurrences(of: "'", with: "''") let escapedPayload = payloadJSON.replacingOccurrences(of: "'", with: "''") let sql = """ INSERT INTO \(DailyNotificationDatabase.TABLE_NOTIF_CONTENTS) ( \(DailyNotificationDatabase.COL_CONTENTS_SLOT_ID), \(DailyNotificationDatabase.COL_CONTENTS_PAYLOAD_JSON), \(DailyNotificationDatabase.COL_CONTENTS_FETCHED_AT), \(DailyNotificationDatabase.COL_CONTENTS_ETAG) ) VALUES ('\(escapedId)', '\(escapedPayload)', \(scheduledTime), NULL); """ database.executeSQL(sql) print("TestDBFactory: Injected invalid notification record: id=\(id), time=\(scheduledTime)") } /** * Inject notification with null/empty required fields */ static func injectNotificationWithNullFields(database: DailyNotificationDatabase) { let sql = """ INSERT INTO \(DailyNotificationDatabase.TABLE_NOTIF_CONTENTS) ( \(DailyNotificationDatabase.COL_CONTENTS_SLOT_ID), \(DailyNotificationDatabase.COL_CONTENTS_PAYLOAD_JSON), \(DailyNotificationDatabase.COL_CONTENTS_FETCHED_AT) ) VALUES (NULL, '', 0); """ database.executeSQL(sql) } /** * Inject duplicate notification records (same ID, different times) */ static func injectDuplicateNotifications( database: DailyNotificationDatabase, id: String, times: [Int64] ) { for time in times { injectInvalidNotificationRecord( database: database, id: id, scheduledTime: time, payloadJSON: "{\"title\":\"Test\",\"body\":\"Body\"}" ) } } /** * Reset database (drop and recreate tables) */ static func resetDatabase(database: DailyNotificationDatabase) { database.executeSQL("DROP TABLE IF EXISTS \(DailyNotificationDatabase.TABLE_NOTIF_CONTENTS);") database.executeSQL("DROP TABLE IF EXISTS \(DailyNotificationDatabase.TABLE_NOTIF_DELIVERIES);") database.executeSQL("DROP TABLE IF EXISTS \(DailyNotificationDatabase.TABLE_NOTIF_CONFIG);") // Recreate tables by opening a new connection let _ = DailyNotificationDatabase(path: database.getPath()) } /** * Clean up test database file */ static func cleanupDatabase(path: String) { let fileManager = FileManager.default if fileManager.fileExists(atPath: path) { try? fileManager.removeItem(atPath: path) } } }