PersistenceController tests
This commit is contained in:
parent
cde82d956b
commit
b8b7e1f913
4 changed files with 112 additions and 7 deletions
|
|
@ -115,6 +115,7 @@
|
||||||
5F9D7B0E27AF6FCA00A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
5F9D7B0E27AF6FCA00A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
5F9D7B0F27AF6FD200A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
5F9D7B0F27AF6FD200A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
8A4716692F5EF56900C7A64D /* AppKeychainTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4716682F5EF56900C7A64D /* AppKeychainTest.swift */; };
|
8A4716692F5EF56900C7A64D /* AppKeychainTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4716682F5EF56900C7A64D /* AppKeychainTest.swift */; };
|
||||||
|
8A4716712F5EF7A900C7A64D /* PersistenceControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A47166F2F5EF7A900C7A64D /* PersistenceControllerTest.swift */; };
|
||||||
8AD8EBF32F5E2723007475AB /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 8AD8EBF22F5E268D007475AB /* Fixtures */; };
|
8AD8EBF32F5E2723007475AB /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 8AD8EBF22F5E268D007475AB /* Fixtures */; };
|
||||||
9A1D1CE526E5D1CE0052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE426E5D1CE0052028E /* OneTimePassword */; };
|
9A1D1CE526E5D1CE0052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE426E5D1CE0052028E /* OneTimePassword */; };
|
||||||
9A1D1CE726E5D2230052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE626E5D2230052028E /* OneTimePassword */; };
|
9A1D1CE726E5D2230052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE626E5D2230052028E /* OneTimePassword */; };
|
||||||
|
|
@ -425,6 +426,7 @@
|
||||||
30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = "<group>"; };
|
30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = "<group>"; };
|
||||||
5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoTokenKit.framework; path = System/Library/Frameworks/CryptoTokenKit.framework; sourceTree = SDKROOT; };
|
5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoTokenKit.framework; path = System/Library/Frameworks/CryptoTokenKit.framework; sourceTree = SDKROOT; };
|
||||||
8A4716682F5EF56900C7A64D /* AppKeychainTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKeychainTest.swift; sourceTree = "<group>"; };
|
8A4716682F5EF56900C7A64D /* AppKeychainTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKeychainTest.swift; sourceTree = "<group>"; };
|
||||||
|
8A47166F2F5EF7A900C7A64D /* PersistenceControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceControllerTest.swift; sourceTree = "<group>"; };
|
||||||
8AD8EBF22F5E268D007475AB /* Fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fixtures; sourceTree = "<group>"; };
|
8AD8EBF22F5E268D007475AB /* Fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fixtures; sourceTree = "<group>"; };
|
||||||
9A1EF0B324C50DD80074FEAC /* passBeta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBeta.entitlements; sourceTree = "<group>"; };
|
9A1EF0B324C50DD80074FEAC /* passBeta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBeta.entitlements; sourceTree = "<group>"; };
|
||||||
9A1EF0B424C50E780074FEAC /* passBetaAutoFillExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaAutoFillExtension.entitlements; sourceTree = "<group>"; };
|
9A1EF0B424C50E780074FEAC /* passBetaAutoFillExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaAutoFillExtension.entitlements; sourceTree = "<group>"; };
|
||||||
|
|
@ -766,6 +768,14 @@
|
||||||
path = Crypto;
|
path = Crypto;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
8A4716702F5EF7A900C7A64D /* Controllers */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8A47166F2F5EF7A900C7A64D /* PersistenceControllerTest.swift */,
|
||||||
|
);
|
||||||
|
path = Controllers;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9A58664F25AADB66006719C2 /* Services */ = {
|
9A58664F25AADB66006719C2 /* Services */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -884,6 +894,7 @@
|
||||||
A26075861EEC6F34005DB03E /* passKitTests */ = {
|
A26075861EEC6F34005DB03E /* passKitTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
8A4716702F5EF7A900C7A64D /* Controllers */,
|
||||||
DC64745A2D29BD43004B4BBC /* CoreData */,
|
DC64745A2D29BD43004B4BBC /* CoreData */,
|
||||||
30A86F93230F235800F821A4 /* Crypto */,
|
30A86F93230F235800F821A4 /* Crypto */,
|
||||||
30BAC8C322E3BA4300438475 /* Testbase */,
|
30BAC8C322E3BA4300438475 /* Testbase */,
|
||||||
|
|
@ -1639,6 +1650,7 @@
|
||||||
30A86F95230F237000F821A4 /* CryptoFrameworkTest.swift in Sources */,
|
30A86F95230F237000F821A4 /* CryptoFrameworkTest.swift in Sources */,
|
||||||
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
|
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
|
||||||
30DAFD4C240985E3002456E7 /* Array+SlicesTest.swift in Sources */,
|
30DAFD4C240985E3002456E7 /* Array+SlicesTest.swift in Sources */,
|
||||||
|
8A4716712F5EF7A900C7A64D /* PersistenceControllerTest.swift in Sources */,
|
||||||
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
|
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
|
||||||
9ADC954124418A5F0005402E /* PasswordStoreTest.swift in Sources */,
|
9ADC954124418A5F0005402E /* PasswordStoreTest.swift in Sources */,
|
||||||
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */,
|
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,19 @@ public class PersistenceController {
|
||||||
|
|
||||||
let container: NSPersistentContainer
|
let container: NSPersistentContainer
|
||||||
|
|
||||||
init(isUnitTest: Bool = false) {
|
init(storeURL: URL? = nil) {
|
||||||
self.container = NSPersistentContainer(name: Self.modelName, managedObjectModel: .sharedModel)
|
self.container = NSPersistentContainer(name: Self.modelName, managedObjectModel: .sharedModel)
|
||||||
let description = container.persistentStoreDescriptions.first
|
let description = container.persistentStoreDescriptions.first
|
||||||
description?.shouldMigrateStoreAutomatically = false
|
description?.shouldMigrateStoreAutomatically = false
|
||||||
description?.shouldInferMappingModelAutomatically = false
|
description?.shouldInferMappingModelAutomatically = false
|
||||||
if isUnitTest {
|
description?.url = storeURL ?? URL(fileURLWithPath: Globals.dbPath)
|
||||||
description?.url = URL(fileURLWithPath: "/dev/null")
|
|
||||||
} else {
|
|
||||||
description?.url = URL(fileURLWithPath: Globals.dbPath)
|
|
||||||
}
|
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func forUnitTests() -> PersistenceController {
|
||||||
|
PersistenceController(storeURL: URL(fileURLWithPath: "/dev/null"))
|
||||||
|
}
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
container.loadPersistentStores { _, error in
|
container.loadPersistentStores { _, error in
|
||||||
if error != nil {
|
if error != nil {
|
||||||
|
|
|
||||||
93
passKitTests/Controllers/PersistenceControllerTest.swift
Normal file
93
passKitTests/Controllers/PersistenceControllerTest.swift
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
//
|
||||||
|
// PersistenceControllerTest.swift
|
||||||
|
// passKitTests
|
||||||
|
//
|
||||||
|
// Created by Lysann Tranvouez on 9/3/26.
|
||||||
|
// Copyright © 2026 Bob Sun. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import passKit
|
||||||
|
|
||||||
|
final class PersistenceControllerTest: XCTestCase {
|
||||||
|
func testModelLoads() {
|
||||||
|
let controller = PersistenceController.forUnitTests()
|
||||||
|
let context = controller.viewContext()
|
||||||
|
|
||||||
|
let entityNames = context.persistentStoreCoordinator!.managedObjectModel.entities.map(\.name)
|
||||||
|
XCTAssertEqual(entityNames, ["PasswordEntity"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInsertAndFetch() {
|
||||||
|
let controller = PersistenceController.forUnitTests()
|
||||||
|
let context = controller.viewContext()
|
||||||
|
XCTAssertEqual(PasswordEntity.fetchAll(in: context).count, 0)
|
||||||
|
|
||||||
|
PasswordEntity.insert(name: "test", path: "test.gpg", isDir: false, into: context)
|
||||||
|
try? context.save()
|
||||||
|
|
||||||
|
XCTAssertEqual(PasswordEntity.fetchAll(in: context).count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReinitializePersistentStoreClearsData() {
|
||||||
|
let controller = PersistenceController.forUnitTests()
|
||||||
|
let context = controller.viewContext()
|
||||||
|
|
||||||
|
PasswordEntity.insert(name: "test1", path: "test1.gpg", isDir: false, into: context)
|
||||||
|
PasswordEntity.insert(name: "test2", path: "test2.gpg", isDir: false, into: context)
|
||||||
|
try? context.save()
|
||||||
|
XCTAssertEqual(PasswordEntity.fetchAll(in: context).count, 2)
|
||||||
|
|
||||||
|
controller.reinitializePersistentStore()
|
||||||
|
|
||||||
|
// After reinitialize, old data should be gone
|
||||||
|
// (reinitializePersistentStore calls initPasswordEntityCoreData with the default repo URL,
|
||||||
|
// which won't exist in tests, so the result should be an empty store)
|
||||||
|
let remaining = PasswordEntity.fetchAll(in: context)
|
||||||
|
XCTAssertEqual(remaining.count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMultipleControllersAreIndependent() {
|
||||||
|
let controller1 = PersistenceController.forUnitTests()
|
||||||
|
let controller2 = PersistenceController.forUnitTests()
|
||||||
|
|
||||||
|
let context1 = controller1.viewContext()
|
||||||
|
let context2 = controller2.viewContext()
|
||||||
|
|
||||||
|
PasswordEntity.insert(name: "only-in-1", path: "only-in-1.gpg", isDir: false, into: context1)
|
||||||
|
try? context1.save()
|
||||||
|
|
||||||
|
XCTAssertEqual(PasswordEntity.fetchAll(in: context1).count, 1)
|
||||||
|
XCTAssertEqual(PasswordEntity.fetchAll(in: context2).count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSaveAndLoadFromFile() throws {
|
||||||
|
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||||
|
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
||||||
|
defer { try? FileManager.default.removeItem(at: tempDir) }
|
||||||
|
let storeURL = tempDir.appendingPathComponent("test.sqlite")
|
||||||
|
|
||||||
|
// Write
|
||||||
|
let controller1 = PersistenceController(storeURL: storeURL)
|
||||||
|
let context1 = controller1.viewContext()
|
||||||
|
PasswordEntity.insert(name: "saved", path: "saved.gpg", isDir: false, into: context1)
|
||||||
|
PasswordEntity.insert(name: "dir", path: "dir", isDir: true, into: context1)
|
||||||
|
controller1.save()
|
||||||
|
|
||||||
|
// Load in a fresh controller from the same file
|
||||||
|
let controller2 = PersistenceController(storeURL: storeURL)
|
||||||
|
let context2 = controller2.viewContext()
|
||||||
|
let allEntities = PasswordEntity.fetchAll(in: context2)
|
||||||
|
|
||||||
|
XCTAssertEqual(allEntities.count, 2)
|
||||||
|
XCTAssertNotNil(allEntities.first { $0.name == "saved" && !$0.isDir })
|
||||||
|
XCTAssertNotNil(allEntities.first { $0.name == "dir" && $0.isDir })
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSaveError() throws {
|
||||||
|
// NOTE: save() calls fatalError on Core Data save failures, so error propagation
|
||||||
|
// cannot be tested without refactoring save() to throw...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ class CoreDataTestCase: XCTestCase {
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
try super.setUpWithError()
|
try super.setUpWithError()
|
||||||
|
|
||||||
controller = PersistenceController(isUnitTest: true)
|
controller = PersistenceController.forUnitTests()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue