import XCTest import SwiftUI import MacToolsPluginKit @testable import MacTools @MainActor final class DynamicPluginLoaderTests: XCTestCase { func testValidateLoadedPluginsAcceptsSinglePluginMatchingManifestID() throws { let record = makeRecord(id: "com.example.demo") let plugin = MockLoadedPlugin(id: "com.example.demo") XCTAssertNoThrow(try DynamicPluginLoader.validateLoadedPlugins([plugin], for: record)) } func testValidateLoadedPluginsRejectsEmptyPluginList() { let record = makeRecord(id: "com.example.demo") XCTAssertThrowsError(try DynamicPluginLoader.validateLoadedPlugins([], for: record)) { error in XCTAssertEqual( error as? DynamicPluginLoaderError, .invalidPluginCount(expected: "com.example.demo", actual: 0) ) } } func testValidateLoadedPluginsRejectsMultiplePluginsFromOnePackage() { let record = makeRecord(id: "com.example.demo") let plugins = [ MockLoadedPlugin(id: "com.example.demo"), MockLoadedPlugin(id: "com.example.extra") ] XCTAssertThrowsError(try DynamicPluginLoader.validateLoadedPlugins(plugins, for: record)) { error in XCTAssertEqual( error as? DynamicPluginLoaderError, .invalidPluginCount(expected: "com.example.demo", actual: 2) ) } } func testValidateLoadedPluginsRejectsRuntimeIdentifierMismatch() { let record = makeRecord(id: "com.example.demo") let plugin = MockLoadedPlugin(id: "com.example.other") XCTAssertThrowsError(try DynamicPluginLoader.validateLoadedPlugins([plugin], for: record)) { error in XCTAssertEqual( error as? DynamicPluginLoaderError, .pluginIdentifierMismatch(expected: "com.example.demo", actual: "com.example.other") ) } } private func makeRecord(id: String) -> PluginPackageRecord { let packageURL = URL(fileURLWithPath: "/tmp/\(id).mactoolsplugin", isDirectory: true) return PluginPackageRecord( id: id, manifest: PluginPackageManifest( id: id, displayName: "Demo", version: "1.0.0", minHostVersion: "0.15.2", bundleRelativePath: "Demo.bundle" ), packageURL: packageURL, bundleURL: packageURL.appendingPathComponent("Demo.bundle", isDirectory: true), state: .enabled, requiresRestartToFullyUnload: false ) } } @MainActor private final class MockLoadedPlugin: MacToolsPlugin { let metadata: PluginMetadata var onStateChange: (() -> Void)? var requestPermissionGuidance: ((String) -> Void)? var shortcutBindingResolver: ((String) -> ShortcutBinding?)? init(id: String) { metadata = PluginMetadata( id: id, title: "Demo", iconName: "shippingbox", iconTint: .blue, order: 1, defaultDescription: "Demo" ) } }