Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

288 lines
9.1 KiB
JavaScript

/**
* Unit tests for the physical-iPhone deploy ledger and the deploy-side
* renderer-freshness compare (issue #14337). Exercises the real read/write/
* update round-trip against a temp JSONL file and the pure stamp comparison
* against fresh/stale manifest fixtures — no device, no simulator. Runs in the
* packages/app vitest suite (`bun run --cwd packages/app test`, root
* test:client lane).
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
appendDeployRecord,
buildDeployRecord,
deployStatusForDevice,
evaluateStagedRendererFreshness,
latestDeployByUdid,
parseDeployLedger,
readDeployLedger,
resolveDeployLedgerPath,
resolveLedgerStateDir,
} from "./ios-deploy-ledger.mjs";
import {
freshRendererManifestPath,
readRendererManifest,
rendererManifestPathFromAppPath,
} from "./ios-renderer-stamp.mjs";
let tmpDir;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "deploy-ledger-"));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
describe("resolveLedgerStateDir precedence", () => {
const home = () => "/home/tester";
it("prefers ELIZA_DEVICES_STATUS_DIR over every other source", () => {
expect(
resolveLedgerStateDir({
env: {
ELIZA_DEVICES_STATUS_DIR: "/d/state",
MILADY_STATE_DIR: "/m/state",
ELIZA_STATE_DIR: "/e/state",
},
homedir: home,
}),
).toBe("/d/state");
});
it("prefers MILADY_STATE_DIR over ELIZA_STATE_DIR", () => {
expect(
resolveLedgerStateDir({
env: { MILADY_STATE_DIR: "/m/state", ELIZA_STATE_DIR: "/e/state" },
homedir: home,
}),
).toBe("/m/state");
});
it("falls back to ELIZA_STATE_DIR", () => {
expect(
resolveLedgerStateDir({
env: { ELIZA_STATE_DIR: "/e/state" },
homedir: home,
}),
).toBe("/e/state");
});
it("honors XDG_STATE_HOME + namespace", () => {
expect(
resolveLedgerStateDir({
env: { XDG_STATE_HOME: "/xdg", ELIZA_NAMESPACE: "milady" },
homedir: home,
}),
).toBe(path.join("/xdg", "milady"));
});
it("defaults to ~/.local/state/eliza", () => {
expect(resolveLedgerStateDir({ env: {}, homedir: home })).toBe(
path.join("/home/tester", ".local", "state", "eliza"),
);
});
it("resolveDeployLedgerPath appends the canonical ledger filename", () => {
expect(
resolveDeployLedgerPath({
env: { ELIZA_STATE_DIR: "/e/state" },
homedir: home,
}),
).toBe(path.join("/e/state", "ios-device-deploy-ledger.jsonl"));
});
});
describe("buildDeployRecord validation", () => {
it("throws without a udid", () => {
expect(() => buildDeployRecord({ buildId: "abc" })).toThrow(
/udid is required/,
);
});
it("throws without a buildId", () => {
expect(() => buildDeployRecord({ udid: "UDID-1" })).toThrow(
/buildId is required/,
);
});
it("normalizes optionals and stamps deployedAt", () => {
const record = buildDeployRecord({
udid: " UDID-1 ",
buildId: " build-abc ",
name: " iPhone 16 ",
commit: " ",
skippedAppexes: true,
});
expect(record.udid).toBe("UDID-1");
expect(record.buildId).toBe("build-abc");
expect(record.name).toBe("iPhone 16");
expect(record.commit).toBeNull();
expect(record.skippedAppexes).toBe(true);
expect(typeof record.deployedAt).toBe("string");
expect(record.schema).toBe("elizaos.device.deploy-ledger/v1");
});
});
describe("ledger write → read → update round-trip", () => {
it("appends, reads back, and collapses to the latest per device", () => {
const ledgerPath = path.join(tmpDir, "state", "device-deploy-ledger.jsonl");
expect(readDeployLedger(ledgerPath)).toEqual([]); // missing file = empty
appendDeployRecord(
ledgerPath,
buildDeployRecord({
udid: "UDID-A",
buildId: "build-1",
commit: "commit-1",
deployedAt: "2026-01-01T00:00:00.000Z",
}),
);
appendDeployRecord(
ledgerPath,
buildDeployRecord({
udid: "UDID-B",
buildId: "build-9",
deployedAt: "2026-01-01T01:00:00.000Z",
}),
);
// Re-deploy to UDID-A with a newer build — the update path.
appendDeployRecord(
ledgerPath,
buildDeployRecord({
udid: "UDID-A",
buildId: "build-2",
commit: "commit-2",
deployedAt: "2026-01-02T00:00:00.000Z",
}),
);
const records = readDeployLedger(ledgerPath);
expect(records).toHaveLength(3);
const latest = latestDeployByUdid(records);
expect(latest.get("UDID-A").buildId).toBe("build-2");
expect(latest.get("UDID-A").commit).toBe("commit-2");
expect(latest.get("UDID-B").buildId).toBe("build-9");
});
it("creates the state dir if absent", () => {
const ledgerPath = path.join(tmpDir, "nested", "deep", "ledger.jsonl");
appendDeployRecord(
ledgerPath,
buildDeployRecord({ udid: "UDID-C", buildId: "b" }),
);
expect(fs.existsSync(ledgerPath)).toBe(true);
});
});
describe("deployStatusForDevice honest unknown path", () => {
it("reports unknown for a device with no ledger entry", () => {
const records = [buildDeployRecord({ udid: "UDID-A", buildId: "b1" })];
const status = deployStatusForDevice(records, "UDID-UNSEEN");
expect(status.known).toBe(false);
expect(status.reason).toMatch(/unknown — no ledger entry/);
});
it("reports the latest record for a known device", () => {
const records = [
buildDeployRecord({
udid: "UDID-A",
buildId: "b1",
deployedAt: "2026-01-01T00:00:00Z",
}),
buildDeployRecord({
udid: "UDID-A",
buildId: "b2",
deployedAt: "2026-01-02T00:00:00Z",
}),
];
const status = deployStatusForDevice(records, "UDID-A");
expect(status.known).toBe(true);
expect(status.record.buildId).toBe("b2");
});
it("throws on an empty udid", () => {
expect(() => deployStatusForDevice([], " ")).toThrow(/udid is required/);
});
});
describe("parseDeployLedger fail-closed on corruption", () => {
it("skips blank lines only", () => {
const text =
'{"udid":"A","buildId":"b"}\n\n \n{"udid":"B","buildId":"c"}\n';
expect(parseDeployLedger(text)).toHaveLength(2);
});
it("throws with a line number on malformed JSONL", () => {
const text = '{"udid":"A","buildId":"b"}\nnot-json\n';
expect(() => parseDeployLedger(text)).toThrow(/line 2/);
});
});
describe("evaluateStagedRendererFreshness", () => {
it("passes when buildIds match", () => {
const v = evaluateStagedRendererFreshness(
{ buildId: "build-abc" },
{ buildId: "build-abc" },
);
expect(v.fresh).toBe(true);
expect(v.reason).toMatch(/matches freshly built/);
});
it("fails on a stale staged buildId with an actionable message", () => {
const v = evaluateStagedRendererFreshness(
{ buildId: "build-OLD0" },
{ buildId: "build-NEW9" },
);
expect(v.fresh).toBe(false);
expect(v.reason).toMatch(/STALE UI/);
expect(v.reason).toMatch(/#9309/);
});
it("fails when the staged bundle has no buildId", () => {
const v = evaluateStagedRendererFreshness(null, { buildId: "build-NEW9" });
expect(v.fresh).toBe(false);
expect(v.reason).toMatch(/no renderer buildId/);
});
it("fails when the fresh dist has no buildId", () => {
const v = evaluateStagedRendererFreshness({ buildId: "build-abc" }, null);
expect(v.fresh).toBe(false);
expect(v.reason).toMatch(/freshly built dist has no renderer buildId/);
});
});
describe("evaluateStagedRendererFreshness against real stamp fixtures", () => {
function writeManifest(dir, manifest) {
const target = path.join(dir, "eliza-renderer-build.json");
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(target, JSON.stringify(manifest));
return target;
}
// Reads real staged + fresh stamps through the shared ios-renderer-stamp lib,
// then runs the ledger's freshness verdict over them — the end-to-end deploy
// path (read staged, read fresh, decide) without a device.
it("verdicts fresh when a real staged stamp matches the fresh dist stamp", () => {
const appPublic = path.join(tmpDir, "App.app", "public");
const dist = path.join(tmpDir, "dist");
const stagedPath = writeManifest(appPublic, {
buildId: "build-XYZ",
commit: "deadbeef",
variant: "device",
runtimeMode: "local",
builtAt: "2026-01-01T00:00:00Z",
});
writeManifest(dist, {
buildId: "build-XYZ",
commit: "deadbeef",
variant: "device",
runtimeMode: "local",
});
expect(rendererManifestPathFromAppPath(path.join(tmpDir, "App.app"))).toBe(
stagedPath,
);
const staged = readRendererManifest(stagedPath, "staged");
const fresh = readRendererManifest(
freshRendererManifestPath({
repoRoot: "/unused-repo-root",
rendererDist: dist,
}),
"fresh",
);
expect(staged.buildId).toBe("build-XYZ");
expect(staged.commit).toBe("deadbeef");
expect(evaluateStagedRendererFreshness(staged, fresh).fresh).toBe(true);
});
});