Files
OmniRoute/tests/unit/shared/machineId.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

228 lines
8.3 KiB
TypeScript

import assert from "node:assert/strict";
import test, { mock } from "node:test";
import os from "node:os";
import path from "node:path";
import { createRequire, syncBuiltinESMExports } from "node:module";
import { pathToFileURL } from "node:url";
const require = createRequire(import.meta.url);
const fs = require("node:fs");
const childProcess = require("node:child_process");
const modulePath = path.join(process.cwd(), "src/shared/utils/machineId.ts");
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Force Strategies 1-3 to fail so the fallback chain reaches os.hostname().
* - Strategy 1 (REG.exe): override SystemRoot so the exe path doesn't exist.
* - Strategy 2 (ioreg): only runs on darwin — skipped on Linux/Windows.
* - Strategy 3 (Linux files): stub readFileSync to throw for machine-id paths.
*
* Returns a restore function.
* NOTE: call syncBuiltinESMExports() AFTER this so ESM imports see the updates.
*/
function disableWindowsRegistryStrategy(): () => void {
const origSysRoot = process.env.SystemRoot;
const origWindir = process.env.windir;
process.env.SystemRoot = "Z:\\NonExistent";
process.env.windir = "Z:\\NonExistent";
const origReadFileSync = fs.readFileSync;
fs.readFileSync = (filePath: string, encoding: string) => {
if (filePath === "/etc/machine-id" || filePath === "/var/lib/dbus/machine-id") {
throw new Error("ENOENT: mocked machine-id file not found");
}
return origReadFileSync(filePath, encoding);
};
const origExecSync = childProcess.execSync;
childProcess.execSync = ((cmd: Parameters<typeof childProcess.execSync>[0], opts: Parameters<typeof childProcess.execSync>[1]) => {
if (String(cmd ?? "").includes("ioreg")) {
throw new Error("ENOENT: mocked ioreg not available");
}
return origExecSync(cmd, opts);
}) as typeof childProcess.execSync;
return () => {
if (origSysRoot !== undefined) {
process.env.SystemRoot = origSysRoot;
} else {
delete process.env.SystemRoot;
}
if (origWindir !== undefined) {
process.env.windir = origWindir;
} else {
delete process.env.windir;
}
fs.readFileSync = origReadFileSync;
childProcess.execSync = origExecSync;
};
}
/**
* Load the machineId module with a cache-busting query param so each
* call gets a fresh instance (needed when monkey-patching built-ins).
*/
async function loadMachineId(label: string) {
return import(`${pathToFileURL(modulePath).href}?case=${label}-${Date.now()}`);
}
// ===========================================================================
// Tests — basic validity
// ===========================================================================
test("getRawMachineId returns a non-empty string", async () => {
const machineId = await loadMachineId("non-empty");
machineId.resetMachineIdCache();
const id = await machineId.getRawMachineId();
assert.ok(id, "machine ID should not be empty");
assert.ok(id.length > 0, "machine ID should have length > 0");
});
test("getRawMachineId caches result after first call", async () => {
const machineId = await loadMachineId("cache");
machineId.resetMachineIdCache();
const id1 = await machineId.getRawMachineId();
const id2 = await machineId.getRawMachineId();
assert.equal(id1, id2, "second call should return cached result");
});
test("getRawMachineId with mocked os.hostname(): caches, does not re-call", async () => {
const restoreEnv = disableWindowsRegistryStrategy();
syncBuiltinESMExports();
const mockHostname = mock.method(os, "hostname", () => "sisyphus-test-pc");
const machineId = await loadMachineId("mock-hostname-cache");
machineId.resetMachineIdCache();
const id1 = await machineId.getRawMachineId();
const calledAfterFirst = mockHostname.mock.callCount();
const id2 = await machineId.getRawMachineId();
assert.equal(id1, id2, "cached result should match first call");
assert.equal(
mockHostname.mock.callCount(),
calledAfterFirst,
"os.hostname() should NOT be called again on cached access"
);
mockHostname.mock.restore();
restoreEnv();
syncBuiltinESMExports();
});
test("resetMachineIdCache clears cached value", async () => {
const restoreEnv = disableWindowsRegistryStrategy();
syncBuiltinESMExports();
const mockHostname = mock.method(os, "hostname", () => "first-pc");
const machineId = await loadMachineId("reset-cache");
machineId.resetMachineIdCache();
const id1 = await machineId.getRawMachineId();
machineId.resetMachineIdCache();
mockHostname.mock.mockImplementation(() => "second-pc");
const id2 = await machineId.getRawMachineId();
assert.equal(
id2,
"second-pc",
"after reset, os.hostname() should be called again with new value"
);
assert.notEqual(id1, id2, "different hostname after reset returns different ID");
mockHostname.mock.restore();
restoreEnv();
syncBuiltinESMExports();
});
// ===========================================================================
// Tests — strategy fallback order
// ===========================================================================
test("os.hostname() (Strategy 4) is tried before execSync hostname (Strategy 5)", async () => {
const restoreEnv = disableWindowsRegistryStrategy();
syncBuiltinESMExports();
const mockHostname = mock.method(os, "hostname", () => "preferred-hostname");
const machineId = await loadMachineId("strategy-order");
machineId.resetMachineIdCache();
const id = await machineId.getRawMachineId();
assert.equal(
id,
"preferred-hostname",
"os.hostname() result should be used before execSync fallback"
);
assert.ok(mockHostname.mock.callCount() >= 1, "os.hostname() was called");
mockHostname.mock.restore();
restoreEnv();
syncBuiltinESMExports();
});
test("Strategy 5 (execSync hostname) is used when os.hostname() fails", async () => {
const restoreEnv = disableWindowsRegistryStrategy();
syncBuiltinESMExports();
const mockHostname = mock.method(os, "hostname", () => {
throw new Error("E_UNAVAIL");
});
const machineId = await loadMachineId("execsync-fallback");
machineId.resetMachineIdCache();
const id = await machineId.getRawMachineId();
// execSync("hostname") succeeds on any real OS
assert.ok(id, "fallback hostname should return a non-empty string");
assert.ok(id.length > 0, "fallback hostname should have length > 0");
// Must NOT be the mocked throw value
assert.notEqual(id, "E_UNAVAIL");
mockHostname.mock.restore();
restoreEnv();
syncBuiltinESMExports();
});
// ===========================================================================
// Tests — getConsistentMachineId
// ===========================================================================
test("getConsistentMachineId returns a 16-character hex string", async () => {
const machineId = await loadMachineId("consistent-length");
machineId.resetMachineIdCache();
const id = await machineId.getConsistentMachineId();
assert.equal(id.length, 16, "consistent machine ID should be 16 characters");
assert.match(id, /^[0-9a-f]{16}$/, "consistent machine ID should be lowercase hex");
});
test("getConsistentMachineId is deterministic for same salt", async () => {
const machineId = await loadMachineId("deterministic-salt");
machineId.resetMachineIdCache();
const id1 = await machineId.getConsistentMachineId("test-salt");
const id2 = await machineId.getConsistentMachineId("test-salt");
assert.equal(id1, id2, "same salt should produce the same machine ID");
});
test("getConsistentMachineId produces different IDs for different salts", async () => {
const machineId = await loadMachineId("different-salts");
machineId.resetMachineIdCache();
const id1 = await machineId.getConsistentMachineId("salt-a");
const id2 = await machineId.getConsistentMachineId("salt-b");
assert.notEqual(id1, id2, "different salts should produce different machine IDs");
});
test("getConsistentMachineId uses env var MACHINE_ID_SALT when no salt given", async () => {
const machineId = await loadMachineId("env-salt");
machineId.resetMachineIdCache();
process.env.MACHINE_ID_SALT = "env-salt-override";
const id = await machineId.getConsistentMachineId();
assert.equal(id.length, 16, "should still return a valid 16-char hex ID");
delete process.env.MACHINE_ID_SALT;
});