@dvai-bridge/ios-llama-core 4.0.0

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.
@@ -0,0 +1,89 @@
1
+ import XCTest
2
+ import CryptoKit
3
+ @testable import DVAILlamaCore
4
+
5
+ final class ModelDownloaderTest: XCTestCase {
6
+ /// Per-test cache dir so tests don't pollute the real App Support folder.
7
+ private var tmpCacheDir: URL!
8
+ private var downloader: ModelDownloader!
9
+
10
+ override func setUp() {
11
+ super.setUp()
12
+ let base = FileManager.default.temporaryDirectory
13
+ tmpCacheDir = base.appendingPathComponent("dvai-modeldownloader-test-\(UUID().uuidString)")
14
+ downloader = ModelDownloader(cacheDirOverride: tmpCacheDir)
15
+ }
16
+
17
+ override func tearDown() {
18
+ if let dir = tmpCacheDir {
19
+ try? FileManager.default.removeItem(at: dir)
20
+ }
21
+ downloader = nil
22
+ tmpCacheDir = nil
23
+ super.tearDown()
24
+ }
25
+
26
+ /// Calling `cacheDirURL()` should create the directory if missing and
27
+ /// return a path that resolves under the override.
28
+ func testCacheDirCreates() async throws {
29
+ let url = try await downloader.cacheDirURL()
30
+ XCTAssertEqual(url.path, tmpCacheDir.path)
31
+ var isDir: ObjCBool = false
32
+ XCTAssertTrue(FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir))
33
+ XCTAssertTrue(isDir.boolValue)
34
+ }
35
+
36
+ /// Cache hit: writing a known file with a known sha256 to the cache dir
37
+ /// then calling `downloadModel(...)` with that sha must return
38
+ /// `cached: true` without ever touching the network — proven by passing
39
+ /// a deliberately broken URL.
40
+ func testCacheHitReturnsCached() async throws {
41
+ let dir = try await downloader.cacheDirURL()
42
+ let filename = "fixture.bin"
43
+ let payload = "hello, dvai cache!".data(using: .utf8)!
44
+ try payload.write(to: dir.appendingPathComponent(filename))
45
+
46
+ let digest = SHA256.hash(data: payload)
47
+ let hex = digest.map { String(format: "%02x", $0) }.joined()
48
+
49
+ // URL is intentionally bogus — a real network call would fail. The
50
+ // cache-hit fast path bypasses network entirely.
51
+ let bogusURL = URL(string: "https://invalid.dvai.test/should-not-fetch.bin")!
52
+ let result = try await downloader.downloadModel(
53
+ url: bogusURL,
54
+ expectedSha256: hex,
55
+ destFilename: filename,
56
+ headers: [:],
57
+ onProgress: { _, _ in }
58
+ )
59
+ XCTAssertTrue(result.cached, "expected cache-hit short-circuit")
60
+ XCTAssertEqual(result.path, dir.appendingPathComponent(filename).path)
61
+ }
62
+
63
+ /// `listCachedModels()` enumerates regular files (skipping `.partial`
64
+ /// and dotfiles) and `deleteCachedModel(...)` removes them.
65
+ func testListAndDelete() async throws {
66
+ let dir = try await downloader.cacheDirURL()
67
+ let a = "alpha".data(using: .utf8)!
68
+ let b = "bravo".data(using: .utf8)!
69
+ try a.write(to: dir.appendingPathComponent("a.gguf"))
70
+ try b.write(to: dir.appendingPathComponent("b.gguf"))
71
+ // Files that must be ignored:
72
+ try Data().write(to: dir.appendingPathComponent("c.gguf.partial"))
73
+ try Data().write(to: dir.appendingPathComponent(".hidden"))
74
+
75
+ let listed = try await downloader.listCachedModels()
76
+ let names = Set(listed.map { $0.filename })
77
+ XCTAssertEqual(names, ["a.gguf", "b.gguf"])
78
+ XCTAssertEqual(listed.count, 2)
79
+ // Bytes + sha255 are populated.
80
+ for info in listed {
81
+ XCTAssertGreaterThan(info.bytes, 0)
82
+ XCTAssertEqual(info.sha256.count, 64)
83
+ }
84
+
85
+ try await downloader.deleteCachedModel(filename: "a.gguf")
86
+ let listed2 = try await downloader.listCachedModels()
87
+ XCTAssertEqual(Set(listed2.map { $0.filename }), ["b.gguf"])
88
+ }
89
+ }
@@ -0,0 +1,51 @@
1
+ import XCTest
2
+ @testable import DVAILlamaCore
3
+
4
+ final class PluginStateTest: XCTestCase {
5
+ func testStartFailsWhenModelPathMissing() async {
6
+ let state = PluginState()
7
+ do {
8
+ _ = try await state.start(opts: [:])
9
+ XCTFail("should have thrown")
10
+ } catch let error as NSError {
11
+ XCTAssertTrue(error.localizedDescription.contains("modelPath is required"))
12
+ }
13
+ }
14
+
15
+ func testStartFailsWhenModelPathEmpty() async {
16
+ let state = PluginState()
17
+ do {
18
+ _ = try await state.start(opts: ["modelPath": ""])
19
+ XCTFail("should have thrown")
20
+ } catch {
21
+ // expected
22
+ }
23
+ }
24
+
25
+ func testStatusInfoReportsNotRunning() async {
26
+ let state = PluginState()
27
+ let info = await state.statusInfo()
28
+ XCTAssertEqual(info["running"] as? Bool, false)
29
+ }
30
+
31
+ /// With the real LlamaCppBridge implementation, loading a non-existent GGUF
32
+ /// fails at `llama_load_model_from_file`. The full `start → server-bind →
33
+ /// success` happy-path needs a real model file and is exercised by the
34
+ /// device-level tests in Task 37's milestone. Here we assert that the
35
+ /// failure surfaces cleanly and the state stays "not running".
36
+ func testStartFailsOnFakeModelPath() async {
37
+ let state = PluginState()
38
+ do {
39
+ _ = try await state.start(opts: [
40
+ "modelPath": "/tmp/definitely-does-not-exist.gguf",
41
+ "httpBasePort": 39200,
42
+ "httpMaxPortAttempts": 4,
43
+ ])
44
+ XCTFail("expected start() to throw for fake model path")
45
+ } catch {
46
+ // expected
47
+ }
48
+ let info = await state.statusInfo()
49
+ XCTAssertEqual(info["running"] as? Bool, false)
50
+ }
51
+ }
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@dvai-bridge/ios-llama-core",
3
+ "version": "4.0.0",
4
+ "description": "DVAI-Bridge iOS llama.cpp core — pure Swift / ObjC++ embedded HTTP server + handlers + bridge. Capacitor-free.",
5
+ "author": "Deep Chakraborty <https://github.com/dk013>",
6
+ "license": "Custom (See LICENSE)",
7
+ "main": "Package.swift",
8
+ "files": [
9
+ "Package.swift",
10
+ "ios",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "publishConfig": {
15
+ "registry": "https://registry.npmjs.org/",
16
+ "access": "public"
17
+ }
18
+ }