Expand regression-focused test coverage

This commit is contained in:
John Burwell 2026-05-29 19:50:16 -05:00
parent 5a2eea1a3d
commit 5de6567924

View File

@ -6,7 +6,9 @@
// //
import Foundation import Foundation
import SQLite3
import Testing import Testing
import UniformTypeIdentifiers
@testable import World_Manager_for_Minecraft @testable import World_Manager_for_Minecraft
@MainActor @MainActor
@ -125,7 +127,7 @@ struct World_Manager_for_MinecraftTests {
#expect(first.isSuspicious == false) #expect(first.isSuspicious == false)
} }
@Test func minecraftSourceItemsUseLogicalPackRepresentative() async throws { @Test func minecraftSourceResolvedPackReferencesUseLogicalPackRepresentative() async throws {
let sourceURL = URL(fileURLWithPath: "/tmp/source") let sourceURL = URL(fileURLWithPath: "/tmp/source")
let worldURL = sourceURL.appendingPathComponent("minecraftWorlds/WorldA", isDirectory: true) let worldURL = sourceURL.appendingPathComponent("minecraftWorlds/WorldA", isDirectory: true)
let topLevelPackURL = sourceURL.appendingPathComponent("behavior_packs/PackA", isDirectory: true) let topLevelPackURL = sourceURL.appendingPathComponent("behavior_packs/PackA", isDirectory: true)
@ -161,14 +163,6 @@ struct World_Manager_for_MinecraftTests {
var source = MinecraftSource(folderURL: sourceURL) var source = MinecraftSource(folderURL: sourceURL)
source.rawItems = [world, topLevelPack, embeddedPack] source.rawItems = [world, topLevelPack, embeddedPack]
source.logicalWorlds = [
LogicalWorld(
id: world.id,
itemID: world.id,
usedPackIDs: [packID],
unresolvedReferences: []
)
]
source.logicalPacks = [ source.logicalPacks = [
LogicalPack( LogicalPack(
id: packID, id: packID,
@ -181,13 +175,133 @@ struct World_Manager_for_MinecraftTests {
isSuspicious: false isSuspicious: false
) )
] ]
source.worldPackRelationships = [
WorldPackRelationship(
worldItemID: world.id,
logicalPackID: packID,
reference: ContentPackReference(
name: "Embedded Copy",
type: .behaviorPack,
uuid: "pack-a",
version: "1.0.0",
source: .embeddedInWorld
)
)
]
let displayedItems = source.items let references = source.resolvedPackReferences(for: world.id, type: .behaviorPack)
#expect(displayedItems.count == 2) #expect(references.count == 1)
#expect(displayedItems.contains(where: { $0.id == world.id })) #expect(references.first?.name == "Pack A")
#expect(displayedItems.contains(where: { $0.id == topLevelPack.id })) #expect(references.first?.uuid == "pack-a")
#expect(displayedItems.contains(where: { $0.id == embeddedPack.id }) == false) #expect(references.first?.version == "1.0.0")
#expect(references.first?.iconURL == topLevelPack.iconURL)
}
@Test func minecraftSourceRelationshipHelpersAndCacheStateReflectCurrentData() async throws {
let sourceURL = URL(fileURLWithPath: "/tmp/source")
let worldA = MinecraftContentItem(
folderURL: sourceURL.appendingPathComponent("minecraftWorlds/WorldA", isDirectory: true),
folderName: "WorldA",
contentType: .world,
collectionRootURL: sourceURL.appendingPathComponent("minecraftWorlds", isDirectory: true),
displayName: "Alpha"
)
let worldB = MinecraftContentItem(
folderURL: sourceURL.appendingPathComponent("minecraftWorlds/WorldB", isDirectory: true),
folderName: "WorldB",
contentType: .world,
collectionRootURL: sourceURL.appendingPathComponent("minecraftWorlds", isDirectory: true),
displayName: "Beta"
)
let packID = PackIdentity(
type: .behaviorPack,
uuid: "pack-a",
version: "1.0.0",
fallbackName: "Pack A",
fallbackLocationHint: "behavior_packs/PackA"
)
let packItem = MinecraftContentItem(
folderURL: sourceURL.appendingPathComponent("behavior_packs/PackA", isDirectory: true),
folderName: "PackA",
contentType: .behaviorPack,
collectionRootURL: sourceURL.appendingPathComponent("behavior_packs", isDirectory: true),
displayName: "Pack A"
)
var source = MinecraftSource(folderURL: sourceURL, availability: .disconnected)
source.displayName = "Source"
source.displayItems = [worldA]
source.rawItems = [worldB, packItem, worldA]
source.logicalPacks = [
LogicalPack(
id: packID,
contentType: .behaviorPack,
displayName: "Pack A",
uuid: "pack-a",
version: "1.0.0",
representativeItemID: packItem.id,
instanceItemIDs: [packItem.id],
isSuspicious: false
)
]
source.logicalWorlds = [
LogicalWorld(
id: worldA.id,
itemID: worldA.id,
usedPackIDs: [packID],
unresolvedReferences: []
)
]
source.packInstances = [
PackInstance(
id: packItem.id,
itemID: packItem.id,
sourceID: source.id,
logicalPackID: packID,
origin: .foundInCollection,
hostWorldItemID: nil
)
]
source.worldPackRelationships = [
WorldPackRelationship(
worldItemID: worldB.id,
logicalPackID: packID,
reference: ContentPackReference(name: "Pack A", type: .behaviorPack, uuid: "pack-a", version: "1.0.0", source: .foundInCollection)
),
WorldPackRelationship(
worldItemID: worldA.id,
logicalPackID: packID,
reference: ContentPackReference(name: "Pack A", type: .behaviorPack, uuid: "pack-a", version: "1.0.0", source: .foundInCollection)
),
WorldPackRelationship(
worldItemID: worldA.id,
logicalPackID: packID,
reference: ContentPackReference(name: "Pack A", type: .behaviorPack, uuid: "pack-a", version: "1.0.0", source: .foundInCollection)
)
]
source.lastScanDate = Date(timeIntervalSince1970: 123)
#expect(source.itemCount == 1)
#expect(source.hasCachedContent)
#expect(source.isOfflineCached)
#expect(source.items == [worldA])
#expect(source.rawItem(withID: worldA.id) == worldA)
#expect(source.logicalPack(forRepresentativeItemID: packItem.id)?.id == packID)
#expect(source.logicalWorld(forItemID: worldA.id)?.id == worldA.id)
#expect(source.packInstances(for: packID).count == 1)
let worldsUsingPack = source.worldsUsingPack(packID)
#expect(worldsUsingPack == [worldA, worldB])
let sourceRecord = source.sourceRecord
#expect(sourceRecord.id == source.id)
#expect(sourceRecord.displayName == "Source")
#expect(sourceRecord.rootURL == source.folderURL)
#expect(sourceRecord.origin == source.origin)
#expect(sourceRecord.accessDescriptor == source.accessDescriptor)
#expect(sourceRecord.availability == .disconnected)
#expect(sourceRecord.lastRefreshDate == source.lastScanDate)
} }
@Test func worldScannerResolvesReferencedPackFromIndexedCollection() async throws { @Test func worldScannerResolvesReferencedPackFromIndexedCollection() async throws {
@ -243,6 +357,83 @@ struct World_Manager_for_MinecraftTests {
#expect(enrichedWorld.packReferences.first?.version == "1.0.0") #expect(enrichedWorld.packReferences.first?.version == "1.0.0")
} }
@Test func worldScannerDiscoversItemsAcrossCollectionsAndEmbeddedPacks() async throws {
let fileManager = FileManager.default
let sourceURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let worldURL = sourceURL.appendingPathComponent("minecraftWorlds/WorldA", isDirectory: true)
let invalidWorldURL = sourceURL.appendingPathComponent("minecraftWorlds/NotAWorld", isDirectory: true)
let embeddedBehaviorPackURL = worldURL.appendingPathComponent("behavior_packs/EmbeddedBehavior", isDirectory: true)
let embeddedResourcePackURL = worldURL.appendingPathComponent("resource_packs/EmbeddedResource", isDirectory: true)
let topLevelBehaviorPackURL = sourceURL.appendingPathComponent("behavior_packs/TopBehavior", isDirectory: true)
let topLevelResourcePackURL = sourceURL.appendingPathComponent("RESOURCE_PACKS/TopResource", isDirectory: true)
defer { try? fileManager.removeItem(at: sourceURL) }
try fileManager.createDirectory(at: worldURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: invalidWorldURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: embeddedBehaviorPackURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: embeddedResourcePackURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: topLevelBehaviorPackURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: topLevelResourcePackURL, withIntermediateDirectories: true)
try Data().write(to: worldURL.appendingPathComponent("level.dat"))
try "{}".write(to: embeddedBehaviorPackURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try "{}".write(to: embeddedResourcePackURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try "{}".write(to: topLevelBehaviorPackURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try Data().write(to: topLevelResourcePackURL.appendingPathComponent("pack_icon.png"))
let discoveredRecorder = LockedRecorder<URL>()
let discovered = try WorldScanner.discoverItems(in: sourceURL) { item in
discoveredRecorder.append(item.id)
}
#expect(discovered.count == 5)
#expect(Set(discovered.map(\.id)) == Set(discoveredRecorder.values))
#expect(discovered.contains { $0.folderURL.standardizedFileURL == worldURL.standardizedFileURL && $0.contentType == .world })
#expect(discovered.contains { $0.folderURL.standardizedFileURL == embeddedBehaviorPackURL.standardizedFileURL && $0.contentType == .behaviorPack })
#expect(discovered.contains { $0.folderURL.standardizedFileURL == embeddedResourcePackURL.standardizedFileURL && $0.contentType == .resourcePack })
#expect(discovered.contains { $0.folderURL.standardizedFileURL == topLevelBehaviorPackURL.standardizedFileURL && $0.contentType == .behaviorPack })
#expect(discovered.contains { $0.folderURL.standardizedFileURL == topLevelResourcePackURL.standardizedFileURL && $0.contentType == .resourcePack })
#expect(discovered.contains { $0.folderURL.standardizedFileURL == invalidWorldURL.standardizedFileURL } == false)
}
@Test func worldScannerCollectionRootDiscoverySnapshotsAndSizeLoading() async throws {
let fileManager = FileManager.default
let sourceURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let worldsURL = sourceURL.appendingPathComponent("minecraftWorlds", isDirectory: true)
let worldAURL = worldsURL.appendingPathComponent("WorldA", isDirectory: true)
let worldBURL = worldsURL.appendingPathComponent("WorldB", isDirectory: true)
let packsURL = sourceURL.appendingPathComponent("behavior_packs", isDirectory: true)
let packURL = packsURL.appendingPathComponent("PackA", isDirectory: true)
defer { try? fileManager.removeItem(at: sourceURL) }
try fileManager.createDirectory(at: worldAURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: worldBURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: packURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: worldAURL.appendingPathComponent("db", isDirectory: true), withIntermediateDirectories: true)
try Data([1, 2, 3]).write(to: worldAURL.appendingPathComponent("level.dat"))
try "World B".write(to: worldBURL.appendingPathComponent("levelname.txt"), atomically: true, encoding: .utf8)
try Data([4, 5]).write(to: worldAURL.appendingPathComponent("db/chunk.bin"), options: .atomic)
try "{}".write(to: packURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try "ignore".write(to: worldsURL.appendingPathComponent("notes.txt"), atomically: true, encoding: .utf8)
let callbackRecorder = LockedRecorder<Int>()
let worlds = try WorldScanner.discoverItems(inCollectionRootURL: worldsURL, contentType: .world) { _ in
callbackRecorder.append(1)
}
let snapshots = WorldScanner.collectionSnapshots(in: sourceURL)
let sizedWorld = WorldScanner.loadSize(for: worlds[0])
await WorldScanner.endScanSession(for: sourceURL)
#expect(worlds.count == 2)
#expect(callbackRecorder.values.count == 2)
#expect(snapshots.count == 2)
#expect(snapshots.contains { $0.folderName == "minecraftWorlds" && $0.childDirectoryCount == 2 })
#expect(snapshots.contains { $0.folderName == "behavior_packs" && $0.childDirectoryCount == 1 })
#expect(snapshots.first(where: { $0.folderName == "minecraftWorlds" })?.fingerprint.contains("WorldA@") == true)
#expect(sizedWorld.sizeLoaded)
#expect(sizedWorld.sizeBytes == 5)
}
@Test func worldScannerDecodesBedrockLevelMetadata() async throws { @Test func worldScannerDecodesBedrockLevelMetadata() async throws {
let fileManager = FileManager.default let fileManager = FileManager.default
let sourceURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) let sourceURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
@ -339,6 +530,81 @@ struct World_Manager_for_MinecraftTests {
#expect(enrichedPack.packMetadataDetails?.minimumEngineVersion == "1.19.50") #expect(enrichedPack.packMetadataDetails?.minimumEngineVersion == "1.19.50")
} }
@Test func minecraftContentMetadataReaderPrefersExpectedNamesIconsAndVersions() async throws {
let fileManager = FileManager.default
let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let worldURL = rootURL.appendingPathComponent("WorldA", isDirectory: true)
let packURL = rootURL.appendingPathComponent("PackA", isDirectory: true)
defer { try? fileManager.removeItem(at: rootURL) }
try fileManager.createDirectory(at: worldURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: packURL, withIntermediateDirectories: true)
try " My World ".write(to: worldURL.appendingPathComponent("levelname.txt"), atomically: true, encoding: .utf8)
try Data([1]).write(to: worldURL.appendingPathComponent("world_icon.jpg"))
try Data([2]).write(to: worldURL.appendingPathComponent("world_icon.png"))
let manifest = """
{
"header": {
"name": " Fancy Pack ",
"uuid": "ABC-123",
"version": [1, "2", 3],
"min_engine_version": "1.21.0"
}
}
"""
try manifest.write(to: packURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try Data([3]).write(to: packURL.appendingPathComponent("pack_icon.jpeg"))
try Data([4]).write(to: packURL.appendingPathComponent("pack_icon.jpg"))
#expect(MinecraftContentMetadataReader.displayName(for: worldURL, contentType: .world, fallbackName: "Fallback") == "My World")
#expect(MinecraftContentMetadataReader.displayName(for: packURL, contentType: .behaviorPack, fallbackName: "Fallback") == "Fancy Pack")
#expect(MinecraftContentMetadataReader.iconURL(for: worldURL, contentType: .world) == worldURL.appendingPathComponent("world_icon.jpg"))
#expect(MinecraftContentMetadataReader.iconURL(for: packURL, contentType: .behaviorPack) == packURL.appendingPathComponent("pack_icon.jpeg"))
#expect(MinecraftContentMetadataReader.packIconURL(in: packURL) == packURL.appendingPathComponent("pack_icon.jpeg"))
#expect(MinecraftContentMetadataReader.manifestMetadata(in: packURL)?.uuid == "abc-123")
#expect(MinecraftContentMetadataReader.manifestMetadata(in: packURL)?.version == "1.2.3")
#expect(MinecraftContentMetadataReader.manifestMetadata(in: packURL)?.minimumEngineVersion == "1.21.0")
#expect(MinecraftContentMetadataReader.versionString(from: "") == nil)
#expect(MinecraftContentMetadataReader.versionString(from: [1, "2", 3]) == "1.2.3")
#expect(MinecraftContentMetadataReader.versionString(from: [[:]]) == nil)
}
@Test func minecraftContentMetadataReaderInfersPackTypesAndFallbacks() async throws {
let fileManager = FileManager.default
let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let resourceURL = rootURL.appendingPathComponent("ResourcePack", isDirectory: true)
let skinURL = rootURL.appendingPathComponent("SkinPack", isDirectory: true)
let behaviorURL = rootURL.appendingPathComponent("BehaviorPack", isDirectory: true)
let fallbackURL = rootURL.appendingPathComponent("FallbackPack", isDirectory: true)
defer { try? fileManager.removeItem(at: rootURL) }
try fileManager.createDirectory(at: resourceURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: skinURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: behaviorURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: fallbackURL, withIntermediateDirectories: true)
try """
{ "modules": [{ "type": "resources" }] }
""".write(to: resourceURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try """
{ "metadata": { "product_type": "skin_pack" } }
""".write(to: skinURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try """
{ "modules": [{ "type": "script" }] }
""".write(to: behaviorURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try """
{ "header": { "name": " " } }
""".write(to: fallbackURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
#expect(MinecraftContentMetadataReader.inferredPackContentType(for: resourceURL) == .resourcePack)
#expect(MinecraftContentMetadataReader.inferredPackContentType(for: skinURL) == .skinPack)
#expect(MinecraftContentMetadataReader.inferredPackContentType(for: behaviorURL) == .behaviorPack)
#expect(MinecraftContentMetadataReader.inferredPackContentType(for: rootURL.appendingPathComponent("Missing", isDirectory: true)) == .behaviorPack)
#expect(MinecraftContentMetadataReader.displayName(for: fallbackURL, contentType: .behaviorPack, fallbackName: "Ignored") == "FallbackPack")
}
@Test func minecraftPackageInspectorReadsMcworldMetadata() async throws { @Test func minecraftPackageInspectorReadsMcworldMetadata() async throws {
let fileManager = FileManager.default let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
@ -457,6 +723,58 @@ struct World_Manager_for_MinecraftTests {
#expect(inspection.displayName == "Nested Behavior Pack") #expect(inspection.displayName == "Nested Behavior Pack")
} }
@Test func minecraftPackageInspectorRejectsUnsupportedExtension() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let archiveURL = workingURL.appendingPathComponent("Invalid.zip", isDirectory: false)
defer { try? fileManager.removeItem(at: workingURL) }
try fileManager.createDirectory(at: workingURL, withIntermediateDirectories: true)
try Data().write(to: archiveURL)
do {
_ = try MinecraftPackageInspector.inspectArchive(at: archiveURL)
Issue.record("Expected unsupported file type error.")
} catch let error as MinecraftPackageInspector.InspectionError {
switch error {
case .unsupportedFileType(let pathExtension):
#expect(pathExtension == "zip")
default:
Issue.record("Expected unsupported file type error but received \(error).")
}
#expect(error.errorDescription == "Unsupported Minecraft package type: .zip")
}
}
@Test func minecraftPackageInspectorRejectsAmbiguousNestedArchiveLayout() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let archiveRootURL = workingURL.appendingPathComponent("ArchiveRoot", isDirectory: true)
let firstPackURL = archiveRootURL.appendingPathComponent("PackOne", isDirectory: true)
let secondPackURL = archiveRootURL.appendingPathComponent("PackTwo", isDirectory: true)
let archiveURL = workingURL.appendingPathComponent("Ambiguous.mcpack", isDirectory: false)
defer { try? fileManager.removeItem(at: workingURL) }
try fileManager.createDirectory(at: firstPackURL, withIntermediateDirectories: true)
try fileManager.createDirectory(at: secondPackURL, withIntermediateDirectories: true)
try "{}".write(to: firstPackURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try "{}".write(to: secondPackURL.appendingPathComponent("manifest.json"), atomically: true, encoding: .utf8)
try makeArchive(from: archiveRootURL, to: archiveURL)
do {
_ = try MinecraftPackageInspector.inspectArchive(at: archiveURL)
Issue.record("Expected invalid archive layout error.")
} catch let error as MinecraftPackageInspector.InspectionError {
switch error {
case .invalidArchiveLayout:
break
default:
Issue.record("Expected invalid archive layout error but received \(error).")
}
#expect(error.errorDescription == "The Minecraft package did not contain a valid world or pack layout.")
}
}
@Test func sourcePersistenceStoreRoundTripsCachedSource() async throws { @Test func sourcePersistenceStoreRoundTripsCachedSource() async throws {
let fileManager = FileManager.default let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
@ -513,10 +831,130 @@ struct World_Manager_for_MinecraftTests {
#expect(restored.first?.folderURL == sourceURL.standardizedFileURL) #expect(restored.first?.folderURL == sourceURL.standardizedFileURL)
#expect(restored.first?.displayName == "Source") #expect(restored.first?.displayName == "Source")
#expect(restored.first?.rawItems == [item]) #expect(restored.first?.rawItems == [item])
#expect(restored.first?.snapshot == snapshot) #expect(restored.first?.snapshot?.sourceID == snapshot.sourceID)
#expect(restored.first?.snapshot?.rootModifiedDate == snapshot.rootModifiedDate)
#expect(restored.first?.snapshot?.collectionSnapshots == snapshot.collectionSnapshots)
#expect(restored.first?.snapshot?.itemSnapshots.count == snapshot.itemSnapshots.count)
#expect(
normalizedTestFileURLPath(restored.first?.snapshot?.itemSnapshots.first?.id)
== normalizedTestFileURLPath(snapshot.itemSnapshots.first?.id)
)
#expect(restored.first?.snapshot?.itemSnapshots.first?.relativePath == snapshot.itemSnapshots.first?.relativePath)
#expect(restored.first?.snapshot?.itemSnapshots.first?.modifiedDate == snapshot.itemSnapshots.first?.modifiedDate)
#expect(restored.first?.snapshot?.itemSnapshots.first?.sizeBytes == snapshot.itemSnapshots.first?.sizeBytes)
#expect(restored.first?.snapshot?.itemSnapshots.first?.packUUID == snapshot.itemSnapshots.first?.packUUID)
#expect(restored.first?.snapshot?.itemSnapshots.first?.packVersion == snapshot.itemSnapshots.first?.packVersion)
#expect(restored.first?.lastScanDate == source.lastScanDate) #expect(restored.first?.lastScanDate == source.lastScanDate)
} }
@Test func sourcePersistenceStoreDeletesSavedSource() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let databaseURL = workingURL.appendingPathComponent("cache.sqlite", isDirectory: false)
let sourceURL = workingURL.appendingPathComponent("Source", isDirectory: true)
defer { try? fileManager.removeItem(at: workingURL) }
let store = SourcePersistenceStore(databaseURL: databaseURL)
try await store.save(source: MinecraftSource(folderURL: sourceURL))
#expect(try await store.loadSources().count == 1)
try await store.deleteSource(withID: sourceURL)
#expect(try await store.loadSources().isEmpty)
}
@Test func sourcePersistenceStoreRepairsLegacyPayloadsLeniently() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let databaseURL = workingURL.appendingPathComponent("cache.sqlite", isDirectory: false)
let sourceURL = workingURL.appendingPathComponent("Source", isDirectory: true)
defer { try? fileManager.removeItem(at: workingURL) }
let store = SourcePersistenceStore(databaseURL: databaseURL)
try await store.save(source: MinecraftSource(folderURL: sourceURL))
let validItem = MinecraftContentItem(
folderURL: sourceURL.appendingPathComponent("minecraftWorlds/WorldA", isDirectory: true),
folderName: "WorldA",
contentType: .world,
collectionRootURL: sourceURL.appendingPathComponent("minecraftWorlds", isDirectory: true),
displayName: "World A"
)
let mixedRawItems = try JSONSerialization.data(withJSONObject: [
try jsonObject(for: validItem),
5
])
try withSQLiteDatabase(at: databaseURL) { database in
try sqliteExec(
"""
UPDATE source_cache
SET origin_json = ?,
access_descriptor_json = ?,
raw_items_json = ?,
snapshot_json = ?,
availability_state = 'bogus'
WHERE folder_path = ?;
""",
on: database,
bindings: [
.blob(Data("{".utf8)),
.blob(Data("{".utf8)),
.blob(mixedRawItems),
.blob(Data("{".utf8)),
.text(sourceURL.path)
]
)
}
let repaired = try await store.loadSources()
#expect(repaired.count == 1)
#expect(repaired[0].needsRepair)
#expect(repaired[0].origin.kind == .localFolder)
#expect(repaired[0].accessDescriptor.kind == .localFolder)
#expect(repaired[0].accessDescriptor.refreshStrategy == .eagerFullScan)
#expect(repaired[0].availability == .unknown)
#expect(repaired[0].rawItems == [validItem])
#expect(repaired[0].snapshot == nil)
}
@Test func sourcePersistenceStoreRepairPersistsNormalizedRecord() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let databaseURL = workingURL.appendingPathComponent("cache.sqlite", isDirectory: false)
let sourceURL = workingURL.appendingPathComponent("Source", isDirectory: true)
defer { try? fileManager.removeItem(at: workingURL) }
let legacyRecord = PersistedSourceRecord(
sourceID: sourceURL,
folderURL: sourceURL,
origin: .localFolder(bookmarkData: nil),
accessDescriptor: SourceAccessDescriptor(
accessorIdentifier: LocalFolderSourceAccess().accessorIdentifier,
kind: .localFolder,
refreshStrategy: .eagerFullScan
),
availability: .available,
bookmarkData: nil,
displayName: "Repaired Source",
rawItems: [],
snapshot: nil,
lastScanDate: Date(timeIntervalSince1970: 999),
needsRepair: true
)
let store = SourcePersistenceStore(databaseURL: databaseURL)
try await store.repair(record: legacyRecord)
let restored = try await store.loadSources()
#expect(restored.count == 1)
#expect(restored[0].displayName == "Repaired Source")
#expect(restored[0].sourceID == sourceURL.standardizedFileURL)
#expect(restored[0].availability == .available)
#expect(restored[0].lastScanDate == legacyRecord.lastScanDate)
}
@Test func connectedDeviceSourceFactoryCreatesStableSyntheticIdentifier() async throws { @Test func connectedDeviceSourceFactoryCreatesStableSyntheticIdentifier() async throws {
let device = ConnectedDevice( let device = ConnectedDevice(
udid: "00008110-001234560E90001E", udid: "00008110-001234560E90001E",
@ -542,40 +980,144 @@ struct World_Manager_for_MinecraftTests {
#expect(source.displayName == "John's iPhone • Minecraft") #expect(source.displayName == "John's iPhone • Minecraft")
} }
@Test func ifuseDeviceServicesParsesListAppsOutputAcrossCommonFormats() async throws { @Test func connectedDeviceDiscoveryCachePolicyHonorsTransportSpecificTTL() async throws {
let output = """ let usbDevice = ConnectedDevice(
com.mojang.minecraftpe - Minecraft udid: "usb-device",
VLC (org.videolan.vlc-ios) name: "USB Device",
Documents\tcom.readdle.ReaddleDocs productType: nil,
com.apple.Pages osVersion: nil,
""" connection: .usb,
trustState: .trusted
)
let networkDevice = ConnectedDevice(
udid: "network-device",
name: "Network Device",
productType: nil,
osVersion: nil,
connection: .network,
trustState: .trusted
)
let cache: [String: CachedConnectedDeviceDiscovery] = [
usbDevice.udid: ConnectedDeviceDiscoveryCachePolicy.cacheDiscovery(
for: usbDevice,
containers: [],
discoveryErrorDescription: nil,
now: Date(timeIntervalSince1970: 100)
),
networkDevice.udid: ConnectedDeviceDiscoveryCachePolicy.cacheDiscovery(
for: networkDevice,
containers: [],
discoveryErrorDescription: nil,
now: Date(timeIntervalSince1970: 100)
)
]
let containers = IFuseDeviceServices.parseAppContainers( let usbFresh = ConnectedDeviceDiscoveryCachePolicy.cachedDiscovery(
from: output, for: usbDevice,
deviceUDID: "device-1" cache: cache,
isActivelyScanning: false,
now: Date(timeIntervalSince1970: 159)
)
let usbExpired = ConnectedDeviceDiscoveryCachePolicy.cachedDiscovery(
for: usbDevice,
cache: cache,
isActivelyScanning: false,
now: Date(timeIntervalSince1970: 161)
)
let networkFresh = ConnectedDeviceDiscoveryCachePolicy.cachedDiscovery(
for: networkDevice,
cache: cache,
isActivelyScanning: false,
now: Date(timeIntervalSince1970: 279)
)
let networkExpired = ConnectedDeviceDiscoveryCachePolicy.cachedDiscovery(
for: networkDevice,
cache: cache,
isActivelyScanning: false,
now: Date(timeIntervalSince1970: 281)
) )
#expect(containers.count == 4) #expect(usbFresh != nil)
#expect(containers.contains { $0.appID == "com.mojang.minecraftpe" && $0.appName == "Minecraft" }) #expect(usbExpired == nil)
#expect(containers.contains { $0.appID == "org.videolan.vlc-ios" && $0.appName == "VLC" }) #expect(networkFresh != nil)
#expect(containers.contains { $0.appID == "com.readdle.ReaddleDocs" && $0.appName == "Documents" }) #expect(networkExpired == nil)
#expect(containers.contains { $0.appID == "com.apple.Pages" && $0.appName == "com.apple.Pages" })
} }
@Test func ifuseDeviceServicesParsesIdeviceInfoKeyValueOutput() async throws { @Test func connectedDeviceSourcePolicyDerivesAvailabilityAndPreferredName() async throws {
let output = """ #expect(ConnectedDeviceSourcePolicy.availability(for: .init(
DeviceName: John's iPad udid: "trusted",
ProductType: iPad14,5 name: "John's iPhone",
ProductVersion: 18.1 productType: nil,
ConnectionType: USB osVersion: nil,
""" connection: .usb,
trustState: .trusted
), hasMinecraftContainer: true) == .available)
#expect(ConnectedDeviceSourcePolicy.availability(for: .init(
udid: "locked",
name: "John's iPhone",
productType: nil,
osVersion: nil,
connection: .usb,
trustState: .locked
), hasMinecraftContainer: true) == .limited)
#expect(ConnectedDeviceSourcePolicy.availability(for: .init(
udid: "missing",
name: "John's iPhone",
productType: nil,
osVersion: nil,
connection: .usb,
trustState: .trusted
), hasMinecraftContainer: false) == .unavailable)
let values = IFuseDeviceServices.parseKeyValueOutput(output) #expect(ConnectedDeviceSourcePolicy.preferredDeviceName(
currentName: " John's iPad ",
fallbackDeviceName: "Backup Name",
fallbackDisplayName: "Display Name • Minecraft"
) == "John's iPad")
#expect(ConnectedDeviceSourcePolicy.preferredDeviceName(
currentName: "Unknown Device",
fallbackDeviceName: " John's Switch ",
fallbackDisplayName: "Ignored • Minecraft"
) == "John's Switch")
#expect(ConnectedDeviceSourcePolicy.preferredDeviceName(
currentName: "Unknown Device",
fallbackDeviceName: "",
fallbackDisplayName: "Bedroom iPad • Minecraft"
) == "Bedroom iPad")
}
#expect(values["DeviceName"] == "John's iPad") @Test func connectedDeviceSourcePolicyDetectsRefreshDebt() async throws {
#expect(values["ProductType"] == "iPad14,5") let device = ConnectedDevice(
#expect(values["ProductVersion"] == "18.1") udid: "device",
#expect(values["ConnectionType"] == "USB") name: "Device",
productType: nil,
osVersion: nil,
connection: .usb,
trustState: .trusted
)
let container = DeviceAppContainer(
deviceUDID: device.udid,
appID: "com.mojang.minecraftpe",
appName: "Minecraft",
accessMode: .documents,
minecraftFolderRelativePath: "Documents/games/com.mojang"
)
var source = ConnectedDeviceSourceFactory().makeSource(device: device, container: container)
#expect(ConnectedDeviceSourcePolicy.hasRefreshDebt(source))
source.rawItems = [
MinecraftContentItem(
folderURL: source.folderURL.appendingPathComponent("minecraftWorlds/WorldA", isDirectory: true),
folderName: "WorldA",
contentType: .world,
collectionRootURL: source.folderURL.appendingPathComponent("minecraftWorlds", isDirectory: true)
)
]
source.previewLoadedCount = 1
source.sizeLoadedCount = 1
#expect(ConnectedDeviceSourcePolicy.hasRefreshDebt(source) == false)
} }
@Test func scanNotificationServiceFormatsCompletionMessage() async throws { @Test func scanNotificationServiceFormatsCompletionMessage() async throws {
@ -724,6 +1266,84 @@ private enum ArchiveTestError: LocalizedError {
} }
} }
private func normalizedTestFileURLPath(_ url: URL?) -> String? {
guard let path = url?.standardizedFileURL.path else {
return nil
}
if path.count > 1, path.hasSuffix("/") {
return String(path.dropLast())
}
return path
}
private enum SQLiteBinding {
case text(String)
case blob(Data)
}
private final class LockedRecorder<Value>: @unchecked Sendable {
private let lock = NSLock()
private var storage: [Value] = []
func append(_ value: Value) {
lock.lock()
storage.append(value)
lock.unlock()
}
var values: [Value] {
lock.lock()
let values = storage
lock.unlock()
return values
}
}
private func withSQLiteDatabase(at url: URL, perform body: (OpaquePointer?) throws -> Void) throws {
var database: OpaquePointer?
guard sqlite3_open(url.path, &database) == SQLITE_OK else {
defer { sqlite3_close(database) }
throw NSError(domain: "TestSQLite", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to open database."])
}
defer { sqlite3_close(database) }
try body(database)
}
private func sqliteExec(_ sql: String, on database: OpaquePointer?, bindings: [SQLiteBinding] = []) throws {
var statement: OpaquePointer?
guard sqlite3_prepare_v2(database, sql, -1, &statement, nil) == SQLITE_OK else {
throw NSError(domain: "TestSQLite", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to prepare SQL statement."])
}
defer { sqlite3_finalize(statement) }
let transientDestructor = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
for (index, binding) in bindings.enumerated() {
switch binding {
case .text(let value):
guard sqlite3_bind_text(statement, Int32(index + 1), value, -1, transientDestructor) == SQLITE_OK else {
throw NSError(domain: "TestSQLite", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to bind text parameter."])
}
case .blob(let value):
let result = value.withUnsafeBytes { rawBuffer in
sqlite3_bind_blob(statement, Int32(index + 1), rawBuffer.baseAddress, Int32(value.count), transientDestructor)
}
guard result == SQLITE_OK else {
throw NSError(domain: "TestSQLite", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to bind blob parameter."])
}
}
}
guard sqlite3_step(statement) == SQLITE_DONE else {
throw NSError(domain: "TestSQLite", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to execute SQL statement."])
}
}
private func jsonObject<T: Encodable>(for value: T) throws -> Any {
try JSONSerialization.jsonObject(with: JSONEncoder().encode(value))
}
private func appendLE<T: FixedWidthInteger>(_ value: T, to data: inout Data) { private func appendLE<T: FixedWidthInteger>(_ value: T, to data: inout Data) {
var value = value.littleEndian var value = value.littleEndian
withUnsafeBytes(of: &value) { bytes in withUnsafeBytes(of: &value) { bytes in