session-ios/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesS...

716 lines
33 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Quick
import Nimble
@testable import SessionUtilitiesKit
class PersistableRecordUtilitiesSpec: QuickSpec {
override class func spec() {
// MARK: Configuration
@TestState var customWriter: DatabaseQueue! = try! DatabaseQueue()
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: customWriter,
migrationTargets: [
TestTarget.self
]
)
// MARK: - a PersistableRecord
describe("a PersistableRecord") {
// MARK: -- before running the add column migration
context("before running the add column migration") {
// MARK: ---- fails when using the standard insert
it("fails when using the standard insert") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test1", columnB: "Test1B").insert(db)
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard inserted
it("fails when using the standard inserted") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test2", columnB: "Test2B").inserted(db)
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard save and the item does not already exist
it("fails when using the standard save and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test3", columnB: "Test3B").save(db)
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard saved and the item does not already exist
it("fails when using the standard saved and the item does not already exist") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test4", columnB: "Test4B").saved(db)
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard upsert and the item does not already exist
it("fails when using the standard upsert and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test5", columnB: "Test5B").upsert(db)
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard mutable upsert and the item does not already exist
it("fails when using the standard mutable upsert and the item does not already exist") {
mockStorage.write { db in
expect {
var result = MutableTestType(columnA: "Test6", columnB: "Test6B")
try result.upsert(db)
return result
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard upsert and the item already exists
it("fails when using the standard upsert and the item already exists") {
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO TestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test19"])
)
try TestType(columnA: "Test19", columnB: "Test19B").upsert(db)
}
.to(throwError())
}
}
// MARK: ---- fails when using the standard mutable upsert and the item already exists
it("fails when using the standard mutable upsert and the item already exists") {
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test20"])
)
var result = MutableTestType(id: 1, columnA: "Test20", columnB: "Test20B")
try result.upsert(db)
return result
}
.to(throwError())
}
}
// MARK: ---- succeeds when using the migration safe insert
it("succeeds when using the migration safe insert") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test7", columnB: "Test7B").migrationSafeInsert(db)
}
.toNot(throwError())
}
mockStorage.read { db in
expect(try TestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the migration safe inserted
it("succeeds when using the migration safe inserted") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test8", columnB: "Test8B").migrationSafeInserted(db)
}
.toNot(throwError())
expect {
try MutableTestType(columnA: "Test9", columnB: "Test9B")
.migrationSafeInserted(db)
.id
}
.toNot(beNil())
}
mockStorage.read { db in
expect(try MutableTestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the migration safe save and the item does not already exist
it("succeeds when using the migration safe save and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test10", columnB: "Test10B").migrationSafeSave(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe saved and the item does not already exist
it("succeeds when using the migration safe saved and the item does not already exist") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test11", columnB: "Test11B").migrationSafeSaved(db)
}
.toNot(throwError())
expect {
try MutableTestType(columnA: "Test12", columnB: "Test12B")
.migrationSafeSaved(db)
.id
}
.toNot(beNil())
}
mockStorage.read { db in
expect(try MutableTestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the migration safe upsert and the item does not already exist
it("succeeds when using the migration safe upsert and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test13", columnB: "Test13B").migrationSafeUpsert(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe mutable upsert and the item does not already exist
it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
mockStorage.write { db in
expect {
let result = MutableTestType(columnA: "Test14", columnB: "Test14B")
try result.migrationSafeUpsert(db)
return result
}
.toNot(throwError())
}
mockStorage.read { db in
expect(try MutableTestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the standard save and the item already exists
it("succeeds when using the standard save and the item already exists") {
/// **Note:** The built-in 'update' method only updates existing columns so this shouldn't fail
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO TestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test16"])
)
try TestType(columnA: "Test16", columnB: "Test16B").save(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard saved and the item already exists
it("succeeds when using the standard saved and the item already exists") {
/// **Note:** The built-in 'update' method only updates existing columns so this won't fail
/// due to the structure discrepancy but won't update the id as that only happens on insert
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test17"])
)
_ = try MutableTestType(id: 1, columnA: "Test17", columnB: "Test17B").saved(db)
}
.toNot(throwError())
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test18"])
)
return try MutableTestType(id: 2, columnA: "Test18", columnB: "Test18B")
.saved(db)
.id
}
.toNot(beNil())
}
mockStorage.read { db in
let types: [MutableTestType]? = try MutableTestType.fetchAll(db)
expect(types).toNot(beNil())
expect(types?.compactMap { $0.id }.count).to(equal(types?.count))
}
}
}
// MARK: -- after running the add column migration
context("after running the add column migration") {
beforeEach {
var migrator: DatabaseMigrator = DatabaseMigrator()
migrator.registerMigration(
mockStorage,
targetIdentifier: TestAddColumnMigration.target,
migration: TestAddColumnMigration.self
)
expect { try migrator.migrate(customWriter) }
.toNot(throwError())
}
// MARK: ---- succeeds when using the standard insert
it("succeeds when using the standard insert") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test1", columnB: "Test1B").insert(db)
}
.toNot(throwError())
}
mockStorage.read { db in
expect(try TestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the standard inserted
it("succeeds when using the standard inserted") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test2", columnB: "Test2B").inserted(db)
}
.toNot(throwError())
}
mockStorage.read { db in
expect(try MutableTestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the standard save and the item does not already exist
it("succeeds when using the standard save and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test3", columnB: "Test3B").save(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard saved and the item does not already exist
it("succeeds when using the standard saved and the item does not already exist") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test3", columnB: "Test3B").saved(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard save and the item already exists
it("succeeds when using the standard save and the item already exists") {
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO TestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test4"])
)
try TestType(columnA: "Test4", columnB: "Test4B").save(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard saved and the item already exists
it("succeeds when using the standard saved and the item already exists") {
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test5"])
)
_ = try MutableTestType(id: 1, columnA: "Test5", columnB: "Test5B").saved(db)
}
.toNot(throwError())
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test6"])
)
return try MutableTestType(id: 2, columnA: "Test6", columnB: "Test6B")
.saved(db)
.id
}
.toNot(beNil())
}
mockStorage.read { db in
let types: [MutableTestType]? = try MutableTestType.fetchAll(db)
expect(types).toNot(beNil())
expect(types?.compactMap { $0.id }.count).to(equal(types?.count))
}
}
// MARK: ---- succeeds when using the standard upsert and the item does not already exist
it("succeeds when using the standard upsert and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test7", columnB: "Test7B").upsert(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard mutable upsert and the item does not already exist
it("succeeds when using the standard mutable upsert and the item does not already exist") {
mockStorage.write { db in
expect {
var result = MutableTestType(columnA: "Test8", columnB: "Test8B")
try result.upsert(db)
return result
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard upsert and the item already exists
it("succeeds when using the standard upsert and the item already exists") {
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO TestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test9"])
)
try TestType(columnA: "Test9", columnB: "Test9B").upsert(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the standard mutable upsert and the item already exists
it("succeeds when using the standard mutable upsert and the item already exists") {
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test10"])
)
var result = MutableTestType(id: 1, columnA: "Test10", columnB: "Test10B")
try result.upsert(db)
return result
}
.toNot(throwError())
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test11"])
)
var result = MutableTestType(id: 2, columnA: "Test11", columnB: "Test11B")
try result.upsert(db)
return result.id
}
.toNot(beNil())
}
mockStorage.read { db in
let types: [MutableTestType]? = try MutableTestType.fetchAll(db)
expect(types).toNot(beNil())
expect(types?.compactMap { $0.id }.count).to(equal(types?.count))
}
}
// MARK: ---- succeeds when using the migration safe insert
it("succeeds when using the migration safe insert") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test12", columnB: "Test12B").migrationSafeInsert(db)
}
.toNot(throwError())
}
mockStorage.read { db in
expect(try TestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the migration safe inserted
it("succeeds when using the migration safe inserted") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test13", columnB: "Test13B").migrationSafeInserted(db)
}
.toNot(throwError())
expect {
try MutableTestType(columnA: "Test14", columnB: "Test14B")
.migrationSafeInserted(db)
.id
}
.toNot(beNil())
}
mockStorage.read { db in
expect(try MutableTestType.fetchAll(db))
.toNot(beNil())
}
}
// MARK: ---- succeeds when using the migration safe save and the item does not already exist
it("succeeds when using the migration safe save and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test15", columnB: "Test15B").migrationSafeSave(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe saved and the item does not already exist
it("succeeds when using the migration safe saved and the item does not already exist") {
mockStorage.write { db in
expect {
try MutableTestType(columnA: "Test16", columnB: "Test16B").migrationSafeSaved(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe save and the item already exists
it("succeeds when using the migration safe save and the item already exists") {
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO TestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test17"])
)
try TestType(columnA: "Test17", columnB: "Test17B").migrationSafeSave(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe saved and the item already exists
it("succeeds when using the migration safe saved and the item already exists") {
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test18"])
)
_ = try MutableTestType(id: 1, columnA: "Test18", columnB: "Test18B")
.migrationSafeSaved(db)
}
.toNot(throwError())
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test19"])
)
return try MutableTestType(id: 2, columnA: "Test19", columnB: "Test19B")
.migrationSafeSaved(db)
.id
}
.toNot(beNil())
}
mockStorage.read { db in
let types: [MutableTestType]? = try MutableTestType.fetchAll(db)
expect(types).toNot(beNil())
expect(types?.compactMap { $0.id }.count).to(equal(types?.count))
}
}
// MARK: ---- succeeds when using the migration safe upsert and the item does not already exist
it("succeeds when using the migration safe upsert and the item does not already exist") {
mockStorage.write { db in
expect {
try TestType(columnA: "Test20", columnB: "Test20B").migrationSafeUpsert(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe mutable upsert and the item does not already exist
it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
mockStorage.write { db in
expect {
let result = MutableTestType(columnA: "Test21", columnB: "Test21B")
try result.migrationSafeUpsert(db)
return result
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe upsert and the item already exists
it("succeeds when using the migration safe upsert and the item already exists") {
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO TestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test22"])
)
try TestType(columnA: "Test22", columnB: "Test22B").migrationSafeUpsert(db)
}
.toNot(throwError())
}
}
// MARK: ---- succeeds when using the migration safe mutable upsert and the item already exists
it("succeeds when using the migration safe mutable upsert and the item already exists") {
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
mockStorage.write { db in
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test23"])
)
let result = MutableTestType(id: 1, columnA: "Test23", columnB: "Test23B")
try result.migrationSafeUpsert(db)
return result
}
.toNot(throwError())
expect {
try db.execute(
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test24"])
)
let result = MutableTestType(id: 2, columnA: "Test24", columnB: "Test24B")
try result.migrationSafeUpsert(db)
return result.id
}
.toNot(beNil())
}
mockStorage.read { db in
let types: [MutableTestType]? = try MutableTestType.fetchAll(db)
expect(types).toNot(beNil())
expect(types?.compactMap { $0.id }.count).to(equal(types?.count))
}
}
}
}
}
}
// MARK: - Test Types
fileprivate struct TestType: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "TestType" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case columnA
case columnB
}
public let columnA: String
public let columnB: String?
}
fileprivate struct MutableTestType: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "MutableTestType" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case id
case columnA
case columnB
}
public var id: Int64?
public let columnA: String
public let columnB: String?
init(id: Int64? = nil, columnA: String, columnB: String?) {
self.id = id
self.columnA = columnA
self.columnB = columnB
}
mutating func didInsert(_ inserted: InsertionSuccess) {
self.id = inserted.rowID
}
}
fileprivate enum TestInsertTestTypeMigration: Migration {
static let target: TargetMigrations.Identifier = .test
static let identifier: String = "TestInsertTestType"
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0
static let fetchedTables: [(TableRecord & FetchableRecord).Type] = []
static let createdOrAlteredTables: [(FetchableRecord & TableRecord).Type] = [TestType.self, MutableTestType.self]
static func migrate(_ db: Database) throws {
try db.create(table: TestType.self) { t in
t.column(.columnA, .text).primaryKey()
}
try db.create(table: MutableTestType.self) { t in
t.column(.id, .integer).primaryKey(autoincrement: true)
t.column(.columnA, .text).unique()
}
}
}
fileprivate enum TestAddColumnMigration: Migration {
static let target: TargetMigrations.Identifier = .test
static let identifier: String = "TestAddColumn"
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0
static let fetchedTables: [(TableRecord & FetchableRecord).Type] = []
static let createdOrAlteredTables: [(FetchableRecord & TableRecord).Type] = [TestType.self, MutableTestType.self]
static func migrate(_ db: Database) throws {
try db.alter(table: TestType.self) { t in
t.add(.columnB, .text)
}
try db.alter(table: MutableTestType.self) { t in
t.add(.columnB, .text)
}
}
}
fileprivate struct TestTarget: MigratableTarget {
static func migrations() -> TargetMigrations {
return TargetMigrations(
identifier: .test,
migrations: (0..<100)
.map { _ in [] }
.appending([TestInsertTestTypeMigration.self])
)
}
}