@dvai-bridge/ios 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/Package.swift +104 -104
  2. package/ios/Sources/DVAIBridge/BackendKind.swift +23 -23
  3. package/ios/Sources/DVAIBridge/BoundServer.swift +46 -46
  4. package/ios/Sources/DVAIBridge/DVAIBridge.swift +658 -658
  5. package/ios/Sources/DVAIBridge/DVAIBridgeConfig.swift +86 -86
  6. package/ios/Sources/DVAIBridge/DVAIBridgeError.swift +33 -33
  7. package/ios/Sources/DVAIBridge/Internal/BackendSelector.swift +59 -59
  8. package/ios/Sources/DVAIBridge/Internal/ProgressBroadcaster.swift +84 -84
  9. package/ios/Sources/DVAIBridge/License/Audience.swift +133 -133
  10. package/ios/Sources/DVAIBridge/License/Discovery.swift +164 -164
  11. package/ios/Sources/DVAIBridge/License/LicenseValidator.swift +392 -392
  12. package/ios/Sources/DVAIBridge/License/PublicKeys.swift +114 -114
  13. package/ios/Sources/DVAIBridge/License/Types.swift +195 -195
  14. package/ios/Sources/DVAIBridge/Offload/OffloadConfig.swift +118 -118
  15. package/ios/Sources/DVAIBridge/ProgressEvent.swift +34 -34
  16. package/ios/Sources/DVAICoreMLCore/CoreMLBackendError.swift +19 -19
  17. package/ios/Sources/DVAICoreMLCore/CoreMLHandlers.swift +123 -123
  18. package/ios/Sources/DVAICoreMLCore/CoreMLPluginState.swift +130 -130
  19. package/ios/Sources/DVAICoreMLCore/Internal/CoreMLEngine.swift +137 -137
  20. package/ios/Sources/DVAICoreMLCore/Internal/CoreMLGenerator.swift +108 -108
  21. package/ios/Sources/DVAICoreMLCore/Internal/CoreMLSampler.swift +96 -96
  22. package/ios/Sources/DVAICoreMLCore/Internal/CoreMLTokenizer.swift +69 -69
  23. package/ios/Tests/DVAIBridgeTests/BackendSelectorTests.swift +53 -53
  24. package/ios/Tests/DVAIBridgeTests/CoreMLEngineTests.swift +18 -18
  25. package/ios/Tests/DVAIBridgeTests/CoreMLGeneratorShapeTests.swift +11 -11
  26. package/ios/Tests/DVAIBridgeTests/CoreMLHandlersTests.swift +32 -32
  27. package/ios/Tests/DVAIBridgeTests/CoreMLPluginStateTests.swift +41 -41
  28. package/ios/Tests/DVAIBridgeTests/CoreMLSamplerTests.swift +40 -40
  29. package/ios/Tests/DVAIBridgeTests/CoreMLTokenizerTests.swift +19 -19
  30. package/ios/Tests/DVAIBridgeTests/DVAIBridgeAPIShapeTests.swift +37 -37
  31. package/ios/Tests/DVAIBridgeTests/DVAIBridgeConfigTests.swift +52 -52
  32. package/ios/Tests/DVAIBridgeTests/DVAIBridgeErrorTests.swift +33 -33
  33. package/ios/Tests/DVAIBridgeTests/LicenseValidatorTests.swift +658 -658
  34. package/ios/Tests/DVAIBridgeTests/ProgressBroadcasterTests.swift +69 -69
  35. package/ios/Tests/DVAIBridgeTests/ProgressEventTests.swift +25 -25
  36. package/ios/Tests/DVAIBridgeTests/ReactiveStateTests.swift +45 -45
  37. package/ios/Tests/DVAIBridgeTests/RealModelIntegrationTest.swift +385 -359
  38. package/package.json +3 -4
  39. package/DVAIBridge.podspec +0 -120
  40. package/LICENSE +0 -51
  41. package/README.md +0 -199
@@ -1,18 +1,18 @@
1
- import XCTest
2
- @testable import DVAICoreMLCore
3
-
4
- @available(iOS 18.0, macOS 15.0, *)
5
- final class CoreMLEngineTests: XCTestCase {
6
- func testLoadFailsForMissingFile() {
7
- let bogusURL = URL(fileURLWithPath: "/tmp/definitely-does-not-exist.mlmodelc")
8
- XCTAssertThrowsError(try CoreMLEngine(modelURL: bogusURL, eosTokenId: 0)) { err in
9
- guard case let CoreMLBackendError.modelLoadFailed(reason) = err else {
10
- return XCTFail("wrong error: \(err)")
11
- }
12
- XCTAssertTrue(
13
- reason.contains("error") || reason.contains("Error") || reason.contains("file"),
14
- "reason should mention error details, got: \(reason)"
15
- )
16
- }
17
- }
18
- }
1
+ import XCTest
2
+ @testable import DVAICoreMLCore
3
+
4
+ @available(iOS 18.0, macOS 15.0, *)
5
+ final class CoreMLEngineTests: XCTestCase {
6
+ func testLoadFailsForMissingFile() {
7
+ let bogusURL = URL(fileURLWithPath: "/tmp/definitely-does-not-exist.mlmodelc")
8
+ XCTAssertThrowsError(try CoreMLEngine(modelURL: bogusURL, eosTokenId: 0)) { err in
9
+ guard case let CoreMLBackendError.modelLoadFailed(reason) = err else {
10
+ return XCTFail("wrong error: \(err)")
11
+ }
12
+ XCTAssertTrue(
13
+ reason.contains("error") || reason.contains("Error") || reason.contains("file"),
14
+ "reason should mention error details, got: \(reason)"
15
+ )
16
+ }
17
+ }
18
+ }
@@ -1,11 +1,11 @@
1
- import XCTest
2
- @testable import DVAICoreMLCore
3
-
4
- @available(iOS 18.0, macOS 15.0, *)
5
- final class CoreMLGeneratorShapeTests: XCTestCase {
6
- func testTypesCompile() {
7
- // Ensures the public-internal API compiles correctly at the type level.
8
- // Real generation is tested end-to-end in RealModelIntegrationTest (Task 18).
9
- let _: AsyncThrowingStream<String, Error>.Type = AsyncThrowingStream<String, Error>.self
10
- }
11
- }
1
+ import XCTest
2
+ @testable import DVAICoreMLCore
3
+
4
+ @available(iOS 18.0, macOS 15.0, *)
5
+ final class CoreMLGeneratorShapeTests: XCTestCase {
6
+ func testTypesCompile() {
7
+ // Ensures the public-internal API compiles correctly at the type level.
8
+ // Real generation is tested end-to-end in RealModelIntegrationTest (Task 18).
9
+ let _: AsyncThrowingStream<String, Error>.Type = AsyncThrowingStream<String, Error>.self
10
+ }
11
+ }
@@ -1,32 +1,32 @@
1
- import XCTest
2
- import DVAISharedCore
3
- @testable import DVAICoreMLCore
4
-
5
- @available(iOS 18.0, macOS 15.0, *)
6
- final class CoreMLHandlersTests: XCTestCase {
7
- func testHandleEmbeddingsReturns501() async throws {
8
- // The embeddings endpoint short-circuits without invoking the generator.
9
- // We verify the response shape without needing a real MLModel.
10
- let response: HandlerResponse = .error(501, "embeddings not yet supported by the CoreML backend")
11
- if case let .error(status, msg) = response {
12
- XCTAssertEqual(status, 501)
13
- XCTAssertTrue(msg.contains("embeddings"), "expected 'embeddings' in '\(msg)'")
14
- } else {
15
- XCTFail("expected error response")
16
- }
17
- }
18
-
19
- func testHandleModelsReturnsConfiguredModel() async throws {
20
- let response: HandlerResponse = .json(200, [
21
- "object": "list",
22
- "data": [["id": "test-model", "object": "model", "owned_by": "dvai-bridge"]]
23
- ])
24
- if case let .json(status, body) = response {
25
- XCTAssertEqual(status, 200)
26
- let dict = body as? [String: Any]
27
- XCTAssertEqual(dict?["object"] as? String, "list")
28
- } else {
29
- XCTFail("expected json response")
30
- }
31
- }
32
- }
1
+ import XCTest
2
+ import DVAISharedCore
3
+ @testable import DVAICoreMLCore
4
+
5
+ @available(iOS 18.0, macOS 15.0, *)
6
+ final class CoreMLHandlersTests: XCTestCase {
7
+ func testHandleEmbeddingsReturns501() async throws {
8
+ // The embeddings endpoint short-circuits without invoking the generator.
9
+ // We verify the response shape without needing a real MLModel.
10
+ let response: HandlerResponse = .error(501, "embeddings not yet supported by the CoreML backend")
11
+ if case let .error(status, msg) = response {
12
+ XCTAssertEqual(status, 501)
13
+ XCTAssertTrue(msg.contains("embeddings"), "expected 'embeddings' in '\(msg)'")
14
+ } else {
15
+ XCTFail("expected error response")
16
+ }
17
+ }
18
+
19
+ func testHandleModelsReturnsConfiguredModel() async throws {
20
+ let response: HandlerResponse = .json(200, [
21
+ "object": "list",
22
+ "data": [["id": "test-model", "object": "model", "owned_by": "dvai-bridge"]]
23
+ ])
24
+ if case let .json(status, body) = response {
25
+ XCTAssertEqual(status, 200)
26
+ let dict = body as? [String: Any]
27
+ XCTAssertEqual(dict?["object"] as? String, "list")
28
+ } else {
29
+ XCTFail("expected json response")
30
+ }
31
+ }
32
+ }
@@ -1,41 +1,41 @@
1
- import XCTest
2
- @testable import DVAICoreMLCore
3
-
4
- @available(iOS 18.0, macOS 15.0, *)
5
- final class CoreMLPluginStateTests: XCTestCase {
6
- func testStartFailsWithoutModelPath() async {
7
- let state = CoreMLPluginState()
8
- do {
9
- _ = try await state.start(opts: [:])
10
- XCTFail("Expected throw")
11
- } catch let err as CoreMLBackendError {
12
- guard case .modelLoadFailed = err else { return XCTFail("wrong error: \(err)") }
13
- // Pass — empty opts correctly throws modelLoadFailed
14
- } catch {
15
- XCTFail("wrong error type: \(error)")
16
- }
17
- }
18
-
19
- func testStartFailsWithoutTokenizerPath() async {
20
- let state = CoreMLPluginState()
21
- do {
22
- _ = try await state.start(opts: ["modelPath": "/tmp/x.mlmodelc"])
23
- XCTFail("Expected throw")
24
- } catch let err as CoreMLBackendError {
25
- guard case .tokenizerLoadFailed = err else { return XCTFail("wrong error: \(err)") }
26
- // Pass — missing tokenizerPath correctly throws tokenizerLoadFailed
27
- } catch {
28
- XCTFail("wrong error type: \(error)")
29
- }
30
- }
31
-
32
- func testStopWhenNotStartedIsIdempotent() async throws {
33
- try await CoreMLPluginState().stop()
34
- // Doesn't throw — idempotent stop is required by the API contract
35
- }
36
-
37
- func testStatusInfoReportsNotRunning() async {
38
- let info = await CoreMLPluginState().statusInfo()
39
- XCTAssertEqual(info["running"] as? Bool, false)
40
- }
41
- }
1
+ import XCTest
2
+ @testable import DVAICoreMLCore
3
+
4
+ @available(iOS 18.0, macOS 15.0, *)
5
+ final class CoreMLPluginStateTests: XCTestCase {
6
+ func testStartFailsWithoutModelPath() async {
7
+ let state = CoreMLPluginState()
8
+ do {
9
+ _ = try await state.start(opts: [:])
10
+ XCTFail("Expected throw")
11
+ } catch let err as CoreMLBackendError {
12
+ guard case .modelLoadFailed = err else { return XCTFail("wrong error: \(err)") }
13
+ // Pass — empty opts correctly throws modelLoadFailed
14
+ } catch {
15
+ XCTFail("wrong error type: \(error)")
16
+ }
17
+ }
18
+
19
+ func testStartFailsWithoutTokenizerPath() async {
20
+ let state = CoreMLPluginState()
21
+ do {
22
+ _ = try await state.start(opts: ["modelPath": "/tmp/x.mlmodelc"])
23
+ XCTFail("Expected throw")
24
+ } catch let err as CoreMLBackendError {
25
+ guard case .tokenizerLoadFailed = err else { return XCTFail("wrong error: \(err)") }
26
+ // Pass — missing tokenizerPath correctly throws tokenizerLoadFailed
27
+ } catch {
28
+ XCTFail("wrong error type: \(error)")
29
+ }
30
+ }
31
+
32
+ func testStopWhenNotStartedIsIdempotent() async throws {
33
+ try await CoreMLPluginState().stop()
34
+ // Doesn't throw — idempotent stop is required by the API contract
35
+ }
36
+
37
+ func testStatusInfoReportsNotRunning() async {
38
+ let info = await CoreMLPluginState().statusInfo()
39
+ XCTAssertEqual(info["running"] as? Bool, false)
40
+ }
41
+ }
@@ -1,40 +1,40 @@
1
- import XCTest
2
- import CoreML
3
- @testable import DVAICoreMLCore
4
-
5
- final class CoreMLSamplerTests: XCTestCase {
6
- func makeLogits(_ vals: [Float]) -> MLMultiArray {
7
- let arr = try! MLMultiArray(shape: [NSNumber(value: vals.count)], dataType: .float32)
8
- for (i, v) in vals.enumerated() { arr[i] = NSNumber(value: v) }
9
- return arr
10
- }
11
-
12
- func testGreedyReturnsArgmax() {
13
- let s = CoreMLSampler(temperature: 0, topP: 1.0, topK: 0)
14
- let logits = makeLogits([1.0, 5.0, 2.0, 4.0])
15
- XCTAssertEqual(s.sample(logits: logits), 1) // argmax index
16
- }
17
-
18
- func testTemperatureSamplingNeverThrows() {
19
- let s = CoreMLSampler(temperature: 1.0, topP: 1.0, topK: 0)
20
- let logits = makeLogits([1.0, 2.0, 3.0, 4.0])
21
- for _ in 0 ..< 100 {
22
- let token = s.sample(logits: logits)
23
- XCTAssertGreaterThanOrEqual(token, 0)
24
- XCTAssertLessThan(token, 4)
25
- }
26
- }
27
-
28
- func testTopPTruncationFavorsHighProb() {
29
- // With top_p = 0.5 and a sharply skewed distribution, only the top
30
- // few tokens should ever be selected.
31
- let s = CoreMLSampler(temperature: 1.0, topP: 0.5, topK: 0)
32
- // Logits chosen so that softmax(logits) ≈ [0.0, 0.0, 0.05, 0.95]
33
- let logits = makeLogits([-100.0, -100.0, 1.0, 4.0])
34
- var counts = [0, 0, 0, 0]
35
- for _ in 0 ..< 1000 { counts[s.sample(logits: logits)] += 1 }
36
- XCTAssertEqual(counts[0], 0)
37
- XCTAssertEqual(counts[1], 0)
38
- XCTAssertGreaterThan(counts[3], counts[2]) // 4.0 picked far more often than 1.0
39
- }
40
- }
1
+ import XCTest
2
+ import CoreML
3
+ @testable import DVAICoreMLCore
4
+
5
+ final class CoreMLSamplerTests: XCTestCase {
6
+ func makeLogits(_ vals: [Float]) -> MLMultiArray {
7
+ let arr = try! MLMultiArray(shape: [NSNumber(value: vals.count)], dataType: .float32)
8
+ for (i, v) in vals.enumerated() { arr[i] = NSNumber(value: v) }
9
+ return arr
10
+ }
11
+
12
+ func testGreedyReturnsArgmax() {
13
+ let s = CoreMLSampler(temperature: 0, topP: 1.0, topK: 0)
14
+ let logits = makeLogits([1.0, 5.0, 2.0, 4.0])
15
+ XCTAssertEqual(s.sample(logits: logits), 1) // argmax index
16
+ }
17
+
18
+ func testTemperatureSamplingNeverThrows() {
19
+ let s = CoreMLSampler(temperature: 1.0, topP: 1.0, topK: 0)
20
+ let logits = makeLogits([1.0, 2.0, 3.0, 4.0])
21
+ for _ in 0 ..< 100 {
22
+ let token = s.sample(logits: logits)
23
+ XCTAssertGreaterThanOrEqual(token, 0)
24
+ XCTAssertLessThan(token, 4)
25
+ }
26
+ }
27
+
28
+ func testTopPTruncationFavorsHighProb() {
29
+ // With top_p = 0.5 and a sharply skewed distribution, only the top
30
+ // few tokens should ever be selected.
31
+ let s = CoreMLSampler(temperature: 1.0, topP: 0.5, topK: 0)
32
+ // Logits chosen so that softmax(logits) ≈ [0.0, 0.0, 0.05, 0.95]
33
+ let logits = makeLogits([-100.0, -100.0, 1.0, 4.0])
34
+ var counts = [0, 0, 0, 0]
35
+ for _ in 0 ..< 1000 { counts[s.sample(logits: logits)] += 1 }
36
+ XCTAssertEqual(counts[0], 0)
37
+ XCTAssertEqual(counts[1], 0)
38
+ XCTAssertGreaterThan(counts[3], counts[2]) // 4.0 picked far more often than 1.0
39
+ }
40
+ }
@@ -1,19 +1,19 @@
1
- import XCTest
2
- @testable import DVAICoreMLCore
3
-
4
- final class CoreMLTokenizerTests: XCTestCase {
5
- func testInitFailsForMissingDir() async {
6
- let bogus = URL(fileURLWithPath: "/tmp/no-such-tokenizer-dir-xyz")
7
- do {
8
- _ = try await CoreMLTokenizer(tokenizerDir: bogus)
9
- XCTFail("Expected throw for missing tokenizer dir")
10
- } catch let err as CoreMLBackendError {
11
- guard case .tokenizerLoadFailed = err else {
12
- return XCTFail("wrong error type: \(err)")
13
- }
14
- // Pass — missing dir correctly converts to tokenizerLoadFailed
15
- } catch {
16
- XCTFail("wrong error type: \(error)")
17
- }
18
- }
19
- }
1
+ import XCTest
2
+ @testable import DVAICoreMLCore
3
+
4
+ final class CoreMLTokenizerTests: XCTestCase {
5
+ func testInitFailsForMissingDir() async {
6
+ let bogus = URL(fileURLWithPath: "/tmp/no-such-tokenizer-dir-xyz")
7
+ do {
8
+ _ = try await CoreMLTokenizer(tokenizerDir: bogus)
9
+ XCTFail("Expected throw for missing tokenizer dir")
10
+ } catch let err as CoreMLBackendError {
11
+ guard case .tokenizerLoadFailed = err else {
12
+ return XCTFail("wrong error type: \(err)")
13
+ }
14
+ // Pass — missing dir correctly converts to tokenizerLoadFailed
15
+ } catch {
16
+ XCTFail("wrong error type: \(error)")
17
+ }
18
+ }
19
+ }
@@ -1,37 +1,37 @@
1
- import XCTest
2
- @testable import DVAIBridge
3
-
4
- final class DVAIBridgeAPIShapeTests: XCTestCase {
5
- func testSingletonExists() {
6
- let bridge: DVAIBridge = DVAIBridge.shared
7
- XCTAssertNotNil(bridge)
8
- }
9
-
10
- func testStatusBeforeStartReportsNotRunning() async {
11
- let bridge = DVAIBridge() // fresh instance for test isolation
12
- let info = await bridge.status()
13
- XCTAssertFalse(info.running)
14
- XCTAssertNil(info.backend)
15
- XCTAssertNil(info.baseUrl)
16
- }
17
-
18
- func testStopWhenNotStartedIsIdempotent() async throws {
19
- let bridge = DVAIBridge()
20
- try await bridge.stop()
21
- try await bridge.stop() // no throw
22
- }
23
-
24
- func testStartCoreMLThrowsBackendUnavailable() async {
25
- let bridge = DVAIBridge()
26
- do {
27
- _ = try await bridge.start(.init(backend: .coreml))
28
- XCTFail("Expected throw")
29
- } catch let err as DVAIBridgeError {
30
- if case .backendUnavailable(.coreml, _) = err { /* expected */ } else {
31
- XCTFail("wrong error: \(err)")
32
- }
33
- } catch {
34
- XCTFail("wrong error type: \(error)")
35
- }
36
- }
37
- }
1
+ import XCTest
2
+ @testable import DVAIBridge
3
+
4
+ final class DVAIBridgeAPIShapeTests: XCTestCase {
5
+ func testSingletonExists() {
6
+ let bridge: DVAIBridge = DVAIBridge.shared
7
+ XCTAssertNotNil(bridge)
8
+ }
9
+
10
+ func testStatusBeforeStartReportsNotRunning() async {
11
+ let bridge = DVAIBridge() // fresh instance for test isolation
12
+ let info = await bridge.status()
13
+ XCTAssertFalse(info.running)
14
+ XCTAssertNil(info.backend)
15
+ XCTAssertNil(info.baseUrl)
16
+ }
17
+
18
+ func testStopWhenNotStartedIsIdempotent() async throws {
19
+ let bridge = DVAIBridge()
20
+ try await bridge.stop()
21
+ try await bridge.stop() // no throw
22
+ }
23
+
24
+ func testStartCoreMLThrowsBackendUnavailable() async {
25
+ let bridge = DVAIBridge()
26
+ do {
27
+ _ = try await bridge.start(.init(backend: .coreml))
28
+ XCTFail("Expected throw")
29
+ } catch let err as DVAIBridgeError {
30
+ if case .backendUnavailable(.coreml, _) = err { /* expected */ } else {
31
+ XCTFail("wrong error: \(err)")
32
+ }
33
+ } catch {
34
+ XCTFail("wrong error type: \(error)")
35
+ }
36
+ }
37
+ }
@@ -1,52 +1,52 @@
1
- import XCTest
2
- @testable import DVAIBridge
3
-
4
- final class DVAIBridgeConfigTests: XCTestCase {
5
- func testDefaultsMatchSpec() {
6
- let c = DVAIBridgeConfig()
7
- XCTAssertEqual(c.backend, .auto)
8
- XCTAssertNil(c.modelPath)
9
- XCTAssertEqual(c.gpuLayers, 99)
10
- XCTAssertEqual(c.contextSize, 2048)
11
- XCTAssertEqual(c.threads, 4)
12
- XCTAssertFalse(c.embeddingMode)
13
- XCTAssertEqual(c.httpBasePort, 38883)
14
- XCTAssertEqual(c.httpMaxPortAttempts, 16)
15
- XCTAssertFalse(c.autoUnloadOnLowMemory)
16
- XCTAssertEqual(c.logLevel, "info")
17
- }
18
-
19
- func testToCoreOptsWildcardCors() {
20
- let c = DVAIBridgeConfig(modelPath: "/x.gguf")
21
- let opts = c.toCoreOpts()
22
- XCTAssertEqual(opts["modelPath"] as? String, "/x.gguf")
23
- XCTAssertEqual(opts["corsOrigin"] as? String, "*")
24
- }
25
-
26
- func testToCoreOptsExactCors() {
27
- let c = DVAIBridgeConfig(corsOrigin: .exact("https://example.com"))
28
- XCTAssertEqual(c.toCoreOpts()["corsOrigin"] as? String, "https://example.com")
29
- }
30
-
31
- func testToCoreOptsAllowlistCors() {
32
- let c = DVAIBridgeConfig(corsOrigin: .allowlist(["https://a.com", "https://b.com"]))
33
- XCTAssertEqual(c.toCoreOpts()["corsOrigin"] as? [String], ["https://a.com", "https://b.com"])
34
- }
35
-
36
- func testBoundServerInitFromCoreResult() throws {
37
- let result: [String: Any] = [
38
- "baseUrl": "http://127.0.0.1:38883/v1",
39
- "port": 38883,
40
- "modelId": "test-model"
41
- ]
42
- let server = try BoundServer(coreResult: result, backend: .llama)
43
- XCTAssertEqual(server.baseUrl, "http://127.0.0.1:38883/v1")
44
- XCTAssertEqual(server.port, 38883)
45
- XCTAssertEqual(server.backend, .llama)
46
- XCTAssertEqual(server.modelId, "test-model")
47
- }
48
-
49
- func testBoundServerInitMalformedResult() {
50
- XCTAssertThrowsError(try BoundServer(coreResult: ["baseUrl": "x"], backend: .llama))
51
- }
52
- }
1
+ import XCTest
2
+ @testable import DVAIBridge
3
+
4
+ final class DVAIBridgeConfigTests: XCTestCase {
5
+ func testDefaultsMatchSpec() {
6
+ let c = DVAIBridgeConfig()
7
+ XCTAssertEqual(c.backend, .auto)
8
+ XCTAssertNil(c.modelPath)
9
+ XCTAssertEqual(c.gpuLayers, 99)
10
+ XCTAssertEqual(c.contextSize, 2048)
11
+ XCTAssertEqual(c.threads, 4)
12
+ XCTAssertFalse(c.embeddingMode)
13
+ XCTAssertEqual(c.httpBasePort, 38883)
14
+ XCTAssertEqual(c.httpMaxPortAttempts, 16)
15
+ XCTAssertFalse(c.autoUnloadOnLowMemory)
16
+ XCTAssertEqual(c.logLevel, "info")
17
+ }
18
+
19
+ func testToCoreOptsWildcardCors() {
20
+ let c = DVAIBridgeConfig(modelPath: "/x.gguf")
21
+ let opts = c.toCoreOpts()
22
+ XCTAssertEqual(opts["modelPath"] as? String, "/x.gguf")
23
+ XCTAssertEqual(opts["corsOrigin"] as? String, "*")
24
+ }
25
+
26
+ func testToCoreOptsExactCors() {
27
+ let c = DVAIBridgeConfig(corsOrigin: .exact("https://example.com"))
28
+ XCTAssertEqual(c.toCoreOpts()["corsOrigin"] as? String, "https://example.com")
29
+ }
30
+
31
+ func testToCoreOptsAllowlistCors() {
32
+ let c = DVAIBridgeConfig(corsOrigin: .allowlist(["https://a.com", "https://b.com"]))
33
+ XCTAssertEqual(c.toCoreOpts()["corsOrigin"] as? [String], ["https://a.com", "https://b.com"])
34
+ }
35
+
36
+ func testBoundServerInitFromCoreResult() throws {
37
+ let result: [String: Any] = [
38
+ "baseUrl": "http://127.0.0.1:38883/v1",
39
+ "port": 38883,
40
+ "modelId": "test-model"
41
+ ]
42
+ let server = try BoundServer(coreResult: result, backend: .llama)
43
+ XCTAssertEqual(server.baseUrl, "http://127.0.0.1:38883/v1")
44
+ XCTAssertEqual(server.port, 38883)
45
+ XCTAssertEqual(server.backend, .llama)
46
+ XCTAssertEqual(server.modelId, "test-model")
47
+ }
48
+
49
+ func testBoundServerInitMalformedResult() {
50
+ XCTAssertThrowsError(try BoundServer(coreResult: ["baseUrl": "x"], backend: .llama))
51
+ }
52
+ }
@@ -1,33 +1,33 @@
1
- import XCTest
2
- @testable import DVAIBridge
3
-
4
- final class DVAIBridgeErrorTests: XCTestCase {
5
- func testErrorDescriptionsAreUserFacing() {
6
- let cases: [(DVAIBridgeError, String)] = [
7
- (.notStarted, "has not been started"),
8
- (.alreadyStarted(currentBackend: .llama, baseUrl: "http://127.0.0.1:38883/v1"), "already running"),
9
- (.configurationInvalid(reason: "x"), "invalid"),
10
- (.backendUnavailable(.foundation, reason: "iOS 26+ required"), "unavailable"),
11
- (.modelLoadFailed(reason: "x"), "Model load failed"),
12
- (.downloadFailed(reason: "x"), "Download failed"),
13
- (.checksumMismatch, "SHA-256"),
14
- (.backendError(underlying: "x"), "Backend error"),
15
- ]
16
- for (err, expectedFragment) in cases {
17
- XCTAssertNotNil(err.errorDescription, "error has no description: \(err)")
18
- XCTAssertTrue(
19
- err.errorDescription!.contains(expectedFragment),
20
- "expected '\(expectedFragment)' in '\(err.errorDescription!)'"
21
- )
22
- }
23
- }
24
-
25
- func testBackendKindAllCases() {
26
- XCTAssertEqual(BackendKind.allCases.count, 5)
27
- XCTAssertTrue(BackendKind.allCases.contains(.auto))
28
- XCTAssertTrue(BackendKind.allCases.contains(.llama))
29
- XCTAssertTrue(BackendKind.allCases.contains(.foundation))
30
- XCTAssertTrue(BackendKind.allCases.contains(.coreml))
31
- XCTAssertTrue(BackendKind.allCases.contains(.mlx))
32
- }
33
- }
1
+ import XCTest
2
+ @testable import DVAIBridge
3
+
4
+ final class DVAIBridgeErrorTests: XCTestCase {
5
+ func testErrorDescriptionsAreUserFacing() {
6
+ let cases: [(DVAIBridgeError, String)] = [
7
+ (.notStarted, "has not been started"),
8
+ (.alreadyStarted(currentBackend: .llama, baseUrl: "http://127.0.0.1:38883/v1"), "already running"),
9
+ (.configurationInvalid(reason: "x"), "invalid"),
10
+ (.backendUnavailable(.foundation, reason: "iOS 26+ required"), "unavailable"),
11
+ (.modelLoadFailed(reason: "x"), "Model load failed"),
12
+ (.downloadFailed(reason: "x"), "Download failed"),
13
+ (.checksumMismatch, "SHA-256"),
14
+ (.backendError(underlying: "x"), "Backend error"),
15
+ ]
16
+ for (err, expectedFragment) in cases {
17
+ XCTAssertNotNil(err.errorDescription, "error has no description: \(err)")
18
+ XCTAssertTrue(
19
+ err.errorDescription!.contains(expectedFragment),
20
+ "expected '\(expectedFragment)' in '\(err.errorDescription!)'"
21
+ )
22
+ }
23
+ }
24
+
25
+ func testBackendKindAllCases() {
26
+ XCTAssertEqual(BackendKind.allCases.count, 5)
27
+ XCTAssertTrue(BackendKind.allCases.contains(.auto))
28
+ XCTAssertTrue(BackendKind.allCases.contains(.llama))
29
+ XCTAssertTrue(BackendKind.allCases.contains(.foundation))
30
+ XCTAssertTrue(BackendKind.allCases.contains(.coreml))
31
+ XCTAssertTrue(BackendKind.allCases.contains(.mlx))
32
+ }
33
+ }