@unerr-ai/unerr 0.1.6 → 0.1.7
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.
- package/README.md +70 -194
- package/dist/cli.js +39149 -36991
- package/package.json +9 -2
- package/dist/__tests__/architecture-guard.test.js +0 -122
- package/dist/__tests__/arg-validator.test.js +0 -205
- package/dist/__tests__/ast-extractor.test.js +0 -203
- package/dist/__tests__/auto-bootstrap.test.js +0 -280
- package/dist/__tests__/background-indexer.test.js +0 -228
- package/dist/__tests__/blast-radius-engine.test.js +0 -200
- package/dist/__tests__/bridge-isolation.test.js +0 -37
- package/dist/__tests__/budget-enforcer.test.js +0 -53
- package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
- package/dist/__tests__/change-narrative.test.js +0 -190
- package/dist/__tests__/check-commit.test.js +0 -258
- package/dist/__tests__/checksum.test.js +0 -34
- package/dist/__tests__/commit-watcher.test.js +0 -154
- package/dist/__tests__/community-detection.test.js +0 -179
- package/dist/__tests__/community-tools.test.js +0 -299
- package/dist/__tests__/components.test.js +0 -449
- package/dist/__tests__/compression-log.test.js +0 -174
- package/dist/__tests__/compression-quality-monitor.test.js +0 -40
- package/dist/__tests__/config-healer.test.js +0 -165
- package/dist/__tests__/context-ledger.test.js +0 -58
- package/dist/__tests__/convention-detector.test.js +0 -99
- package/dist/__tests__/convention-learner.test.js +0 -86
- package/dist/__tests__/correction-detector.test.js +0 -330
- package/dist/__tests__/daemon-autostart-install.test.js +0 -283
- package/dist/__tests__/daemon-bridge.test.js +0 -222
- package/dist/__tests__/daemon-dashboard.test.js +0 -202
- package/dist/__tests__/daemon-registry.test.js +0 -240
- package/dist/__tests__/daemon-supervisor.test.js +0 -318
- package/dist/__tests__/daemon-version-check.test.js +0 -275
- package/dist/__tests__/decision-point-detector.test.js +0 -98
- package/dist/__tests__/deep-link.test.js +0 -143
- package/dist/__tests__/disallowed-tools.test.js +0 -115
- package/dist/__tests__/drift-tracker.test.js +0 -582
- package/dist/__tests__/durability-scorer.test.js +0 -152
- package/dist/__tests__/efficiency-tracker.test.js +0 -65
- package/dist/__tests__/enrich.test.js +0 -144
- package/dist/__tests__/entity-rewind.test.js +0 -248
- package/dist/__tests__/ephemeral.test.js +0 -111
- package/dist/__tests__/exploration-cost.test.js +0 -93
- package/dist/__tests__/fact-generator.test.js +0 -197
- package/dist/__tests__/file-l0-graph.test.js +0 -244
- package/dist/__tests__/file-logger.test.js +0 -82
- package/dist/__tests__/file-outline.test.js +0 -141
- package/dist/__tests__/file-read-protocol.test.js +0 -188
- package/dist/__tests__/format-encoder.test.js +0 -233
- package/dist/__tests__/git-attribution.test.js +0 -259
- package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
- package/dist/__tests__/health-grade-enhanced.test.js +0 -138
- package/dist/__tests__/health-map-data.test.js +0 -173
- package/dist/__tests__/helpers/mcp-harness.js +0 -45
- package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
- package/dist/__tests__/hook-dedup.test.js +0 -112
- package/dist/__tests__/hook-runner.test.js +0 -253
- package/dist/__tests__/indexer-cfg.test.js +0 -185
- package/dist/__tests__/indexer-cross-file.test.js +0 -172
- package/dist/__tests__/indexer-extraction.test.js +0 -245
- package/dist/__tests__/indexer-incremental.test.js +0 -232
- package/dist/__tests__/indexer-language-expansion.test.js +0 -165
- package/dist/__tests__/init-push.test.js +0 -131
- package/dist/__tests__/instruction-writer.test.js +0 -179
- package/dist/__tests__/intelligence-integration.test.js +0 -217
- package/dist/__tests__/intent-correlator.test.js +0 -175
- package/dist/__tests__/intent-detector.test.js +0 -235
- package/dist/__tests__/intent-encoder.test.js +0 -167
- package/dist/__tests__/java-build-tool-detection.test.js +0 -174
- package/dist/__tests__/layer3-sprint-q.test.js +0 -160
- package/dist/__tests__/layer3-sprint-r.test.js +0 -91
- package/dist/__tests__/layer3-sprint-s.test.js +0 -183
- package/dist/__tests__/layer3-sprint-t.test.js +0 -201
- package/dist/__tests__/layer3-sprint-u.test.js +0 -174
- package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
- package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
- package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
- package/dist/__tests__/ledger-chains.test.js +0 -162
- package/dist/__tests__/lifecycle-machine.test.js +0 -226
- package/dist/__tests__/local-chat-provider.test.js +0 -170
- package/dist/__tests__/local-convention-detector.test.js +0 -308
- package/dist/__tests__/local-embeddings.test.js +0 -422
- package/dist/__tests__/local-graph.test.js +0 -540
- package/dist/__tests__/local-indexer.test.js +0 -228
- package/dist/__tests__/local-intelligence-l3.test.js +0 -332
- package/dist/__tests__/local-llm.test.js +0 -253
- package/dist/__tests__/local-mode-offline.test.js +0 -187
- package/dist/__tests__/local-mode-stats.test.js +0 -273
- package/dist/__tests__/local-mode-tui.test.js +0 -343
- package/dist/__tests__/local-parse.test.js +0 -199
- package/dist/__tests__/log-tailer.test.js +0 -208
- package/dist/__tests__/loop-breaker.test.js +0 -276
- package/dist/__tests__/loop-miner.test.js +0 -226
- package/dist/__tests__/mcp-config.test.js +0 -126
- package/dist/__tests__/mcp-content-json.test.js +0 -10
- package/dist/__tests__/mcp-envelope.test.js +0 -124
- package/dist/__tests__/metrics-store.test.js +0 -223
- package/dist/__tests__/native-watcher.test.js +0 -191
- package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
- package/dist/__tests__/negative-knowledge.test.js +0 -116
- package/dist/__tests__/network-boundary.test.js +0 -190
- package/dist/__tests__/network-firewall.test.js +0 -112
- package/dist/__tests__/nudge-invariants.test.js +0 -160
- package/dist/__tests__/nudge-v2.test.js +0 -225
- package/dist/__tests__/offline-rewind.test.js +0 -251
- package/dist/__tests__/open-threads.test.js +0 -89
- package/dist/__tests__/output-compressor.test.js +0 -93
- package/dist/__tests__/pending-violations.test.js +0 -112
- package/dist/__tests__/persistence-effectiveness.test.js +0 -143
- package/dist/__tests__/provider-factory.test.js +0 -42
- package/dist/__tests__/providers.test.js +0 -24
- package/dist/__tests__/proxy.test.js +0 -314
- package/dist/__tests__/query-router.test.js +0 -1018
- package/dist/__tests__/reasoning-quality-route.test.js +0 -138
- package/dist/__tests__/redactor.test.js +0 -120
- package/dist/__tests__/resource-monitor.test.js +0 -57
- package/dist/__tests__/response-envelope.test.js +0 -100
- package/dist/__tests__/risk-classifier.test.js +0 -101
- package/dist/__tests__/risk-signal-scope.test.js +0 -75
- package/dist/__tests__/rule-evaluator.test.js +0 -280
- package/dist/__tests__/scip-decoder.test.js +0 -49
- package/dist/__tests__/scip-downloader.test.js +0 -201
- package/dist/__tests__/scip-merger.test.js +0 -103
- package/dist/__tests__/search-index.test.js +0 -422
- package/dist/__tests__/semantic-enrichment.test.js +0 -360
- package/dist/__tests__/session-brief-builder.test.js +0 -187
- package/dist/__tests__/session-context.test.js +0 -221
- package/dist/__tests__/session-continuity.test.js +0 -144
- package/dist/__tests__/session-dedup.test.js +0 -74
- package/dist/__tests__/session-event-wiring.test.js +0 -206
- package/dist/__tests__/session-events.test.js +0 -149
- package/dist/__tests__/session-legend.test.js +0 -20
- package/dist/__tests__/session-persistence.test.js +0 -131
- package/dist/__tests__/session-resume-block.test.js +0 -107
- package/dist/__tests__/session-resume.test.js +0 -97
- package/dist/__tests__/session-summary-writer.test.js +0 -134
- package/dist/__tests__/shadow-ledger.test.js +0 -203
- package/dist/__tests__/shell-classifier.test.js +0 -151
- package/dist/__tests__/shell-compression-floor.test.js +0 -189
- package/dist/__tests__/shell-compression-v2.test.js +0 -339
- package/dist/__tests__/shell-compressor.test.js +0 -35
- package/dist/__tests__/shell-hooks.test.js +0 -128
- package/dist/__tests__/shell-strategies.test.js +0 -644
- package/dist/__tests__/shell-tee.test.js +0 -133
- package/dist/__tests__/signal-dedup.test.js +0 -158
- package/dist/__tests__/signal-reinforcer.test.js +0 -77
- package/dist/__tests__/signal-scorer.test.js +0 -251
- package/dist/__tests__/signal-show-store.test.js +0 -108
- package/dist/__tests__/smart-truncate.test.js +0 -215
- package/dist/__tests__/snapshot-v2.test.js +0 -113
- package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
- package/dist/__tests__/sprint-l10-boot.test.js +0 -220
- package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
- package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
- package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
- package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
- package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
- package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
- package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
- package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
- package/dist/__tests__/sprint-s8-value.test.js +0 -167
- package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
- package/dist/__tests__/sprint3-intelligence.test.js +0 -297
- package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
- package/dist/__tests__/startup-display.test.js +0 -302
- package/dist/__tests__/startup-log-file.test.js +0 -97
- package/dist/__tests__/stash-manager.test.js +0 -229
- package/dist/__tests__/state-detector.test.js +0 -92
- package/dist/__tests__/status-dashboard.test.js +0 -142
- package/dist/__tests__/temporal-facts.test.js +0 -292
- package/dist/__tests__/temporal-routes.test.js +0 -142
- package/dist/__tests__/test-detector.test.js +0 -174
- package/dist/__tests__/theme.test.js +0 -72
- package/dist/__tests__/timeline-agents.test.js +0 -122
- package/dist/__tests__/timeline-bootstrap.test.js +0 -176
- package/dist/__tests__/timeline-filters.test.js +0 -193
- package/dist/__tests__/timeline-markers.test.js +0 -151
- package/dist/__tests__/timeline-routes.test.js +0 -156
- package/dist/__tests__/timeline-store.test.js +0 -171
- package/dist/__tests__/token-counter.test.js +0 -86
- package/dist/__tests__/token-estimator.test.js +0 -96
- package/dist/__tests__/token-flow-api.test.js +0 -239
- package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
- package/dist/__tests__/token-flow-persistence.test.js +0 -356
- package/dist/__tests__/token-flow-routes.test.js +0 -199
- package/dist/__tests__/token-flow.test.js +0 -695
- package/dist/__tests__/tool-clusters.test.js +0 -177
- package/dist/__tests__/transport-mux.test.js +0 -283
- package/dist/__tests__/turn-segmenter.test.js +0 -166
- package/dist/__tests__/uninstall.test.js +0 -141
- package/dist/__tests__/warm-start-policy.test.js +0 -271
- package/dist/__tests__/wire-cap-nudge.test.js +0 -77
- package/dist/__tests__/worker-pool.test.js +0 -101
- package/dist/ui/assets/index-7gl3mIuY.css +0 -1
- package/dist/ui/assets/index-CX4FCWGT.js +0 -10
- package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
- package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
- package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
- package/dist/ui/icon-wordmark.png +0 -0
- package/dist/ui/icon-wordmark.svg +0 -30
- package/dist/ui/icon.png +0 -0
- package/dist/ui/icon.svg +0 -25
- package/dist/ui/index.html +0 -15
- package/dist/ui/prototype-sandbox/index.html +0 -257
- package/dist/ui/screenshots/activity.png +0 -0
- package/dist/ui/screenshots/code-base-intelligence.png +0 -0
- package/dist/ui/screenshots/dashboard.png +0 -0
- package/dist/ui/screenshots/project-memory.png +0 -0
- package/dist/ui/screenshots/reasoning-quality.png +0 -0
- package/dist/ui/screenshots/reasoning-session.png +0 -0
- package/dist/ui/screenshots/token-session.png +0 -0
- package/dist/ui/screenshots/token-trace-main.png +0 -0
- package/dist/ui/screenshots/token-turn.png +0 -0
- package/dist/ui/unerr-wordmark.png +0 -0
- package/dist/ui/unerr-wordmark.svg +0 -9
- package/dist/ui/unerr.png +0 -0
- package/dist/ui/unerr.svg +0 -25
- package/dist/ui/web-app-manifest-192x192.png +0 -0
- package/dist/ui/web-app-manifest-512x512.png +0 -0
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DM-2: Daemon supervisor tests.
|
|
3
|
-
*
|
|
4
|
-
* Tests cover:
|
|
5
|
-
* - ProcessManager spawn/stop/IPC/idle sweep
|
|
6
|
-
* - Daemon entrypoint PID lock + UDS protocol
|
|
7
|
-
* - Orphan detection in daemon-child mode
|
|
8
|
-
* - Protocol message handling
|
|
9
|
-
*/
|
|
10
|
-
import { mkdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
|
-
// ── ProcessManager unit tests ──────────────────────────────────────
|
|
15
|
-
describe("ProcessManager", () => {
|
|
16
|
-
let testDir;
|
|
17
|
-
let testCounter = 0;
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
testCounter++;
|
|
20
|
-
testDir = join(tmpdir(), `dm2-pm-test-${Date.now()}-${testCounter}`);
|
|
21
|
-
mkdirSync(testDir, { recursive: true });
|
|
22
|
-
// Set up a mock global dir so registry doesn't touch real home
|
|
23
|
-
vi.stubEnv("UNERR_HOME", testDir);
|
|
24
|
-
// Create registry file
|
|
25
|
-
const globalUnerr = join(testDir, ".unerr");
|
|
26
|
-
mkdirSync(globalUnerr, { recursive: true });
|
|
27
|
-
writeFileSync(join(globalUnerr, "repos.json"), JSON.stringify({ version: 1, repos: [] }));
|
|
28
|
-
});
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
vi.unstubAllEnvs();
|
|
31
|
-
try {
|
|
32
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
/* windows race */
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
it("creates a ProcessManager instance with no repos", async () => {
|
|
39
|
-
const { ProcessManager } = await import("../daemon/process-manager.js");
|
|
40
|
-
const pm = new ProcessManager();
|
|
41
|
-
const status = pm.getStatus();
|
|
42
|
-
expect(status).toEqual([]);
|
|
43
|
-
});
|
|
44
|
-
it("getManaged returns undefined for unknown repo", async () => {
|
|
45
|
-
const { ProcessManager } = await import("../daemon/process-manager.js");
|
|
46
|
-
const pm = new ProcessManager();
|
|
47
|
-
expect(pm.getManaged("/nonexistent")).toBeUndefined();
|
|
48
|
-
});
|
|
49
|
-
it("startIdleSweep + stopIdleSweep lifecycle", async () => {
|
|
50
|
-
const { ProcessManager } = await import("../daemon/process-manager.js");
|
|
51
|
-
const pm = new ProcessManager();
|
|
52
|
-
pm.startIdleSweep();
|
|
53
|
-
// Calling again is a no-op
|
|
54
|
-
pm.startIdleSweep();
|
|
55
|
-
pm.stopIdleSweep();
|
|
56
|
-
// Calling again after stop is fine
|
|
57
|
-
pm.stopIdleSweep();
|
|
58
|
-
});
|
|
59
|
-
it("shutdownAll gracefully handles empty state", async () => {
|
|
60
|
-
const { ProcessManager } = await import("../daemon/process-manager.js");
|
|
61
|
-
const pm = new ProcessManager();
|
|
62
|
-
await pm.shutdownAll();
|
|
63
|
-
});
|
|
64
|
-
it("connect/disconnect on unknown repo is a no-op", async () => {
|
|
65
|
-
const { ProcessManager } = await import("../daemon/process-manager.js");
|
|
66
|
-
const pm = new ProcessManager();
|
|
67
|
-
pm.connect("/unknown");
|
|
68
|
-
pm.disconnect("/unknown");
|
|
69
|
-
pm.recordActivity("/unknown");
|
|
70
|
-
});
|
|
71
|
-
it("event handler receives lifecycle events", async () => {
|
|
72
|
-
const { ProcessManager } = await import("../daemon/process-manager.js");
|
|
73
|
-
const pm = new ProcessManager();
|
|
74
|
-
const events = [];
|
|
75
|
-
pm.setEventHandler((event) => {
|
|
76
|
-
events.push(event);
|
|
77
|
-
});
|
|
78
|
-
// No actual events will fire without spawning, but handler should be set
|
|
79
|
-
expect(events).toEqual([]);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
// ── Protocol message handling ──────────────────────────────────────
|
|
83
|
-
describe("Daemon protocol types", () => {
|
|
84
|
-
it("DaemonRequest union covers all commands", async () => {
|
|
85
|
-
const proto = await import("../daemon/protocol.js");
|
|
86
|
-
// Verify the type constants exist as expected
|
|
87
|
-
expect(proto.DEFAULT_IDLE_TIMEOUT_S).toBe(1800);
|
|
88
|
-
expect(proto.DEFAULT_WARM_START_BUDGET).toBe(3);
|
|
89
|
-
expect(proto.DEFAULT_WARM_START_DELAY_MS).toBe(30_000);
|
|
90
|
-
expect(proto.DEFAULT_WARM_START_IDLE_DAYS).toBe(14);
|
|
91
|
-
});
|
|
92
|
-
it("ChildMessage types are all present", async () => {
|
|
93
|
-
// This is a compile-time check — if ChildMessage changes, this test file won't compile
|
|
94
|
-
const _ = {
|
|
95
|
-
ready: { type: "ready", sock: "/tmp/test.sock" },
|
|
96
|
-
activity: { type: "activity" },
|
|
97
|
-
stats: { type: "stats", entities: 100, edges: 200, memory: 50 },
|
|
98
|
-
needs_input: { type: "needs_input", signals: [] },
|
|
99
|
-
};
|
|
100
|
-
expect(_.ready.type).toBe("ready");
|
|
101
|
-
expect(_.activity.type).toBe("activity");
|
|
102
|
-
expect(_.stats.type).toBe("stats");
|
|
103
|
-
expect(_.needs_input.type).toBe("needs_input");
|
|
104
|
-
});
|
|
105
|
-
it("ParentMessage types are all present", async () => {
|
|
106
|
-
const _ = {
|
|
107
|
-
shutdown: { type: "shutdown" },
|
|
108
|
-
getStats: { type: "get-stats" },
|
|
109
|
-
};
|
|
110
|
-
expect(_.shutdown.type).toBe("shutdown");
|
|
111
|
-
expect(_.getStats.type).toBe("get-stats");
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
// ── Daemon entrypoint isolation tests ──────────────────────────────
|
|
115
|
-
describe("Daemon entrypoint", () => {
|
|
116
|
-
it("daemon.ts imports only from daemon/, proxy/pid-lock, utils/", async () => {
|
|
117
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
118
|
-
const { resolve: res } = await import("node:path");
|
|
119
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/daemon.ts"), "utf-8");
|
|
120
|
-
// Must not import from intelligence/, behaviors/, tracking/
|
|
121
|
-
const forbidden = [
|
|
122
|
-
/from\s+["']\.\.\/intelligence\//,
|
|
123
|
-
/from\s+["']\.\.\/behaviors\//,
|
|
124
|
-
/from\s+["']\.\.\/tracking\//,
|
|
125
|
-
];
|
|
126
|
-
for (const pattern of forbidden) {
|
|
127
|
-
expect(content).not.toMatch(pattern);
|
|
128
|
-
}
|
|
129
|
-
// Must import from daemon/ (ProcessManager, protocol, registry)
|
|
130
|
-
expect(content).toMatch(/from\s+["']\.\.\/daemon\/process-manager/);
|
|
131
|
-
expect(content).toMatch(/from\s+["']\.\.\/daemon\/protocol/);
|
|
132
|
-
expect(content).toMatch(/from\s+["']\.\.\/daemon\/registry/);
|
|
133
|
-
});
|
|
134
|
-
it("process-manager.ts imports only from daemon/ and node builtins", async () => {
|
|
135
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
136
|
-
const { resolve: res } = await import("node:path");
|
|
137
|
-
const content = readSync(res(process.cwd(), "src/daemon/process-manager.ts"), "utf-8");
|
|
138
|
-
const forbidden = [
|
|
139
|
-
/from\s+["']\.\.\/intelligence\//,
|
|
140
|
-
/from\s+["']\.\.\/behaviors\//,
|
|
141
|
-
/from\s+["']\.\.\/tracking\//,
|
|
142
|
-
/from\s+["']\.\.\/proxy\//,
|
|
143
|
-
];
|
|
144
|
-
for (const pattern of forbidden) {
|
|
145
|
-
expect(content).not.toMatch(pattern);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
// ── CLI --daemon-child flag ────────────────────────────────────────
|
|
150
|
-
describe("CLI --daemon-child flag", () => {
|
|
151
|
-
it("cli.ts contains --daemon-child option", async () => {
|
|
152
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
153
|
-
const { resolve: res } = await import("node:path");
|
|
154
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/cli.ts"), "utf-8");
|
|
155
|
-
expect(content).toContain("--daemon-child");
|
|
156
|
-
expect(content).toContain("daemonChildBoot");
|
|
157
|
-
});
|
|
158
|
-
it("daemonChildBoot implements orphan detection", async () => {
|
|
159
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
160
|
-
const { resolve: res } = await import("node:path");
|
|
161
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/cli.ts"), "utf-8");
|
|
162
|
-
expect(content).toContain("process.ppid");
|
|
163
|
-
expect(content).toContain("orphanTimer");
|
|
164
|
-
expect(content).toContain("originalPpid");
|
|
165
|
-
});
|
|
166
|
-
it("daemonChildBoot sends IPC ready message", async () => {
|
|
167
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
168
|
-
const { resolve: res } = await import("node:path");
|
|
169
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/cli.ts"), "utf-8");
|
|
170
|
-
expect(content).toContain('{ type: "ready", sock: sockPath }');
|
|
171
|
-
expect(content).toContain("process.send");
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
// ── Idle sweep logic ───────────────────────────────────────────────
|
|
175
|
-
describe("Idle sweep logic", () => {
|
|
176
|
-
it("process-manager.ts checks connections and lastActivity", async () => {
|
|
177
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
178
|
-
const { resolve: res } = await import("node:path");
|
|
179
|
-
const content = readSync(res(process.cwd(), "src/daemon/process-manager.ts"), "utf-8");
|
|
180
|
-
expect(content).toContain("runIdleSweep");
|
|
181
|
-
expect(content).toContain("repo.connections > 0");
|
|
182
|
-
expect(content).toContain("repo.idleTimeout");
|
|
183
|
-
expect(content).toContain("idleMs >= timeoutMs");
|
|
184
|
-
});
|
|
185
|
-
it("idle sweep interval is 60 seconds", async () => {
|
|
186
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
187
|
-
const { resolve: res } = await import("node:path");
|
|
188
|
-
const content = readSync(res(process.cwd(), "src/daemon/process-manager.ts"), "utf-8");
|
|
189
|
-
expect(content).toContain("IDLE_SWEEP_INTERVAL_MS = 60_000");
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
// ── PID lock in daemon.ts ──────────────────────────────────────────
|
|
193
|
-
describe("Daemon PID lock", () => {
|
|
194
|
-
let testDir;
|
|
195
|
-
let testCounter = 0;
|
|
196
|
-
beforeEach(() => {
|
|
197
|
-
testCounter++;
|
|
198
|
-
testDir = join(tmpdir(), `dm2-pid-test-${Date.now()}-${testCounter}`);
|
|
199
|
-
mkdirSync(testDir, { recursive: true });
|
|
200
|
-
});
|
|
201
|
-
afterEach(() => {
|
|
202
|
-
try {
|
|
203
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
/* cleanup */
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
it("daemon.ts writes unerrd.pid with process.pid", async () => {
|
|
210
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
211
|
-
const { resolve: res } = await import("node:path");
|
|
212
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/daemon.ts"), "utf-8");
|
|
213
|
-
expect(content).toContain("unerrd.pid");
|
|
214
|
-
expect(content).toContain("unerrd.sock");
|
|
215
|
-
expect(content).toContain("acquirePidLock");
|
|
216
|
-
expect(content).toContain("releasePidLock");
|
|
217
|
-
});
|
|
218
|
-
it("daemon.ts handles stale socket cleanup", async () => {
|
|
219
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
220
|
-
const { resolve: res } = await import("node:path");
|
|
221
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/daemon.ts"), "utf-8");
|
|
222
|
-
expect(content).toContain("cleanStaleSocket");
|
|
223
|
-
expect(content).toContain("createConnection");
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
// ── Signal handling ────────────────────────────────────────────────
|
|
227
|
-
describe("Signal handling", () => {
|
|
228
|
-
it("daemon.ts handles SIGTERM and SIGINT", async () => {
|
|
229
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
230
|
-
const { resolve: res } = await import("node:path");
|
|
231
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/daemon.ts"), "utf-8");
|
|
232
|
-
expect(content).toContain("SIGTERM");
|
|
233
|
-
expect(content).toContain("SIGINT");
|
|
234
|
-
expect(content).toContain("shutdownAll");
|
|
235
|
-
});
|
|
236
|
-
it("daemon-child handles SIGTERM for graceful shutdown", async () => {
|
|
237
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
238
|
-
const { resolve: res } = await import("node:path");
|
|
239
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/cli.ts"), "utf-8");
|
|
240
|
-
// Child process listens for SIGTERM
|
|
241
|
-
expect(content).toContain("SIGTERM");
|
|
242
|
-
// And calls shutdownProxy
|
|
243
|
-
expect(content).toContain("shutdownProxy");
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
// ── No interactive prompts in daemon-child ─────────────────────────
|
|
247
|
-
describe("No interactive prompts in daemon-child", () => {
|
|
248
|
-
it("process.stdin.isTTY is never checked in daemon modules", async () => {
|
|
249
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
250
|
-
const { resolve: res } = await import("node:path");
|
|
251
|
-
const files = [
|
|
252
|
-
"src/daemon/process-manager.ts",
|
|
253
|
-
"src/daemon/protocol.ts",
|
|
254
|
-
"src/daemon/registry.ts",
|
|
255
|
-
"src/daemon/settings-schema.ts",
|
|
256
|
-
"src/entrypoints/daemon.ts",
|
|
257
|
-
];
|
|
258
|
-
for (const file of files) {
|
|
259
|
-
const content = readSync(res(process.cwd(), file), "utf-8");
|
|
260
|
-
expect(content).not.toContain("process.stdin.isTTY");
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
// ── UDS protocol ───────────────────────────────────────────────────
|
|
265
|
-
describe("UDS protocol", () => {
|
|
266
|
-
it("daemon.ts implements newline-delimited JSON protocol", async () => {
|
|
267
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
268
|
-
const { resolve: res } = await import("node:path");
|
|
269
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/daemon.ts"), "utf-8");
|
|
270
|
-
// Newline-delimited JSON framing
|
|
271
|
-
expect(content).toContain('buffer.indexOf("\\n")');
|
|
272
|
-
expect(content).toContain("JSON.parse");
|
|
273
|
-
expect(content).toContain("JSON.stringify");
|
|
274
|
-
});
|
|
275
|
-
it("handles all DaemonRequest commands", async () => {
|
|
276
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
277
|
-
const { resolve: res } = await import("node:path");
|
|
278
|
-
const content = readSync(res(process.cwd(), "src/entrypoints/daemon.ts"), "utf-8");
|
|
279
|
-
const commands = [
|
|
280
|
-
"ensure",
|
|
281
|
-
"connect",
|
|
282
|
-
"disconnect",
|
|
283
|
-
"activity",
|
|
284
|
-
"status",
|
|
285
|
-
"add",
|
|
286
|
-
"remove",
|
|
287
|
-
"stop",
|
|
288
|
-
"shutdown",
|
|
289
|
-
"dashboard-state",
|
|
290
|
-
"repo-detail",
|
|
291
|
-
];
|
|
292
|
-
for (const cmd of commands) {
|
|
293
|
-
expect(content).toContain(`"${cmd}"`);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
// ── ProxyOptions daemonChild flag ──────────────────────────────────
|
|
298
|
-
describe("ProxyOptions daemonChild", () => {
|
|
299
|
-
it("proxy.ts includes daemonChild in ProxyOptions", async () => {
|
|
300
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
301
|
-
const { resolve: res } = await import("node:path");
|
|
302
|
-
const content = readSync(res(process.cwd(), "src/proxy/proxy.ts"), "utf-8");
|
|
303
|
-
expect(content).toContain("daemonChild?: boolean");
|
|
304
|
-
expect(content).toContain("opts.daemonChild");
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
// ── daemon.ts CLI start/stop commands ──────────────────────────────
|
|
308
|
-
describe("Daemon CLI commands", () => {
|
|
309
|
-
it("daemon.ts registers start and stop subcommands", async () => {
|
|
310
|
-
const { readFileSync: readSync } = await import("node:fs");
|
|
311
|
-
const { resolve: res } = await import("node:path");
|
|
312
|
-
const content = readSync(res(process.cwd(), "src/commands/daemon.ts"), "utf-8");
|
|
313
|
-
expect(content).toContain('.command("start")');
|
|
314
|
-
expect(content).toContain('.command("stop")');
|
|
315
|
-
expect(content).toContain("--background");
|
|
316
|
-
expect(content).toContain("startDaemon");
|
|
317
|
-
});
|
|
318
|
-
});
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for DM-6 version check system:
|
|
3
|
-
* - Version comparison logic
|
|
4
|
-
* - Cache read/write
|
|
5
|
-
* - Throttled checks (max 1/day)
|
|
6
|
-
* - Notification gating (>2 minor versions)
|
|
7
|
-
* - Dismiss logic
|
|
8
|
-
* - No auto-apply
|
|
9
|
-
*/
|
|
10
|
-
import { existsSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
14
|
-
// Save and restore version.json between tests to prevent cross-test pollution
|
|
15
|
-
const versionCachePath = join(homedir(), ".unerr", "version.json");
|
|
16
|
-
let savedVersionCache = null;
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
try {
|
|
19
|
-
if (existsSync(versionCachePath)) {
|
|
20
|
-
savedVersionCache = readFileSync(versionCachePath, "utf-8");
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
savedVersionCache = null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
savedVersionCache = null;
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
try {
|
|
32
|
-
if (savedVersionCache !== null) {
|
|
33
|
-
writeFileSync(versionCachePath, savedVersionCache, "utf-8");
|
|
34
|
-
}
|
|
35
|
-
else if (existsSync(versionCachePath)) {
|
|
36
|
-
rmSync(versionCachePath);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Best-effort
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
import { dismissVersion, getCachedUpdateInfo, getInstalledVersion, isNewer, minorsBehind, readVersionCache, shouldNotify, writeVersionCache, } from "../daemon/version-checker.js";
|
|
44
|
-
describe("version comparison — isNewer", () => {
|
|
45
|
-
it("detects newer patch", () => {
|
|
46
|
-
expect(isNewer("0.2.2", "0.2.1")).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
it("detects newer minor", () => {
|
|
49
|
-
expect(isNewer("0.3.0", "0.2.1")).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
it("detects newer major", () => {
|
|
52
|
-
expect(isNewer("1.0.0", "0.9.9")).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
it("returns false for same version", () => {
|
|
55
|
-
expect(isNewer("0.2.1", "0.2.1")).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
it("returns false for older version", () => {
|
|
58
|
-
expect(isNewer("0.1.0", "0.2.1")).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
it("handles v prefix", () => {
|
|
61
|
-
expect(isNewer("v0.3.0", "v0.2.1")).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
it("handles pre-release suffix", () => {
|
|
64
|
-
expect(isNewer("0.3.0-beta.1", "0.2.1")).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
it("returns false for invalid versions", () => {
|
|
67
|
-
expect(isNewer("invalid", "0.2.1")).toBe(false);
|
|
68
|
-
expect(isNewer("0.2.1", "bad")).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
describe("version comparison — minorsBehind", () => {
|
|
72
|
-
it("0 for same version", () => {
|
|
73
|
-
expect(minorsBehind("0.2.1", "0.2.1")).toBe(0);
|
|
74
|
-
});
|
|
75
|
-
it("1 for one minor ahead", () => {
|
|
76
|
-
expect(minorsBehind("0.3.0", "0.2.1")).toBe(1);
|
|
77
|
-
});
|
|
78
|
-
it("3 for three minors ahead", () => {
|
|
79
|
-
expect(minorsBehind("0.5.0", "0.2.1")).toBe(3);
|
|
80
|
-
});
|
|
81
|
-
it("handles major version difference", () => {
|
|
82
|
-
const result = minorsBehind("2.1.0", "0.9.0");
|
|
83
|
-
expect(result).toBeGreaterThan(2);
|
|
84
|
-
});
|
|
85
|
-
it("0 for older latest", () => {
|
|
86
|
-
expect(minorsBehind("0.1.0", "0.2.1")).toBe(0);
|
|
87
|
-
});
|
|
88
|
-
it("handles invalid input gracefully", () => {
|
|
89
|
-
expect(minorsBehind("bad", "0.2.1")).toBe(0);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
describe("version cache", () => {
|
|
93
|
-
it("readVersionCache returns defaults when file missing", () => {
|
|
94
|
-
// Ensure no cache file exists for this test
|
|
95
|
-
try {
|
|
96
|
-
rmSync(versionCachePath);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
/* ok */
|
|
100
|
-
}
|
|
101
|
-
const cache = readVersionCache();
|
|
102
|
-
expect(cache.checkInterval).toBe(86_400);
|
|
103
|
-
expect(cache.dismissed).toEqual([]);
|
|
104
|
-
expect(cache.installedVersion).toBeTruthy();
|
|
105
|
-
});
|
|
106
|
-
it("writeVersionCache persists and reads back", () => {
|
|
107
|
-
const cache = {
|
|
108
|
-
lastChecked: "2026-05-14T10:00:00Z",
|
|
109
|
-
latestVersion: "0.5.0",
|
|
110
|
-
installedVersion: "0.2.1",
|
|
111
|
-
dismissed: ["0.3.0"],
|
|
112
|
-
checkInterval: 86_400,
|
|
113
|
-
};
|
|
114
|
-
writeVersionCache(cache);
|
|
115
|
-
const read = readVersionCache();
|
|
116
|
-
expect(read.lastChecked).toBe(cache.lastChecked);
|
|
117
|
-
expect(read.latestVersion).toBe(cache.latestVersion);
|
|
118
|
-
expect(read.dismissed).toContain("0.3.0");
|
|
119
|
-
expect(read.checkInterval).toBe(86_400);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
describe("getCachedUpdateInfo", () => {
|
|
123
|
-
it("returns available=false when no cache", () => {
|
|
124
|
-
const info = getCachedUpdateInfo();
|
|
125
|
-
// Without a real newer version in cache, it should be false
|
|
126
|
-
expect(typeof info.available).toBe("boolean");
|
|
127
|
-
expect(info.current).toBeTruthy();
|
|
128
|
-
});
|
|
129
|
-
it("returns available=true when cache has newer version", () => {
|
|
130
|
-
writeVersionCache({
|
|
131
|
-
lastChecked: new Date().toISOString(),
|
|
132
|
-
latestVersion: "99.0.0",
|
|
133
|
-
installedVersion: "0.0.1",
|
|
134
|
-
dismissed: [],
|
|
135
|
-
checkInterval: 86_400,
|
|
136
|
-
});
|
|
137
|
-
const info = getCachedUpdateInfo();
|
|
138
|
-
expect(info.available).toBe(true);
|
|
139
|
-
expect(info.latest).toBe("99.0.0");
|
|
140
|
-
expect(info.behindMinor).toBeGreaterThan(0);
|
|
141
|
-
});
|
|
142
|
-
it("reports dismissed correctly", () => {
|
|
143
|
-
writeVersionCache({
|
|
144
|
-
lastChecked: new Date().toISOString(),
|
|
145
|
-
latestVersion: "99.0.0",
|
|
146
|
-
installedVersion: "0.0.1",
|
|
147
|
-
dismissed: ["99.0.0"],
|
|
148
|
-
checkInterval: 86_400,
|
|
149
|
-
});
|
|
150
|
-
const info = getCachedUpdateInfo();
|
|
151
|
-
expect(info.available).toBe(true);
|
|
152
|
-
expect(info.dismissed).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
describe("dismissVersion", () => {
|
|
156
|
-
it("adds version to dismissed list", () => {
|
|
157
|
-
writeVersionCache({
|
|
158
|
-
lastChecked: new Date().toISOString(),
|
|
159
|
-
latestVersion: "1.0.0",
|
|
160
|
-
installedVersion: "0.0.1",
|
|
161
|
-
dismissed: [],
|
|
162
|
-
checkInterval: 86_400,
|
|
163
|
-
});
|
|
164
|
-
dismissVersion("1.0.0");
|
|
165
|
-
const cache = readVersionCache();
|
|
166
|
-
expect(cache.dismissed).toContain("1.0.0");
|
|
167
|
-
});
|
|
168
|
-
it("strips v prefix", () => {
|
|
169
|
-
writeVersionCache({
|
|
170
|
-
lastChecked: new Date().toISOString(),
|
|
171
|
-
latestVersion: "2.0.0",
|
|
172
|
-
installedVersion: "0.0.1",
|
|
173
|
-
dismissed: [],
|
|
174
|
-
checkInterval: 86_400,
|
|
175
|
-
});
|
|
176
|
-
dismissVersion("v2.0.0");
|
|
177
|
-
const cache = readVersionCache();
|
|
178
|
-
expect(cache.dismissed).toContain("2.0.0");
|
|
179
|
-
});
|
|
180
|
-
it("is idempotent", () => {
|
|
181
|
-
writeVersionCache({
|
|
182
|
-
lastChecked: new Date().toISOString(),
|
|
183
|
-
latestVersion: "3.0.0",
|
|
184
|
-
installedVersion: "0.0.1",
|
|
185
|
-
dismissed: ["3.0.0"],
|
|
186
|
-
checkInterval: 86_400,
|
|
187
|
-
});
|
|
188
|
-
dismissVersion("3.0.0");
|
|
189
|
-
const cache = readVersionCache();
|
|
190
|
-
expect(cache.dismissed.filter((v) => v === "3.0.0").length).toBe(1);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
describe("throttle — max 1 check per interval", () => {
|
|
194
|
-
it("does not re-fetch when last check is recent", async () => {
|
|
195
|
-
writeVersionCache({
|
|
196
|
-
lastChecked: new Date().toISOString(),
|
|
197
|
-
latestVersion: "0.5.0",
|
|
198
|
-
installedVersion: "0.0.1",
|
|
199
|
-
dismissed: [],
|
|
200
|
-
checkInterval: 86_400,
|
|
201
|
-
});
|
|
202
|
-
// checkForUpdate should NOT hit the network because lastChecked is fresh
|
|
203
|
-
const { checkForUpdate } = await import("../daemon/version-checker.js");
|
|
204
|
-
const info = await checkForUpdate();
|
|
205
|
-
// Should use cached value
|
|
206
|
-
expect(info.latest).toBe("0.5.0");
|
|
207
|
-
expect(info.available).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
describe("notification gating", () => {
|
|
211
|
-
it("shouldNotify returns true when available and not dismissed", () => {
|
|
212
|
-
writeVersionCache({
|
|
213
|
-
lastChecked: new Date().toISOString(),
|
|
214
|
-
latestVersion: "10.0.0",
|
|
215
|
-
installedVersion: "0.0.1",
|
|
216
|
-
dismissed: [],
|
|
217
|
-
checkInterval: 86_400,
|
|
218
|
-
});
|
|
219
|
-
expect(shouldNotify()).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
it("shouldNotify returns false when dismissed", () => {
|
|
222
|
-
writeVersionCache({
|
|
223
|
-
lastChecked: new Date().toISOString(),
|
|
224
|
-
latestVersion: "10.0.0",
|
|
225
|
-
installedVersion: "0.0.1",
|
|
226
|
-
dismissed: ["10.0.0"],
|
|
227
|
-
checkInterval: 86_400,
|
|
228
|
-
});
|
|
229
|
-
expect(shouldNotify()).toBe(false);
|
|
230
|
-
});
|
|
231
|
-
it("_meta.update_available only fires when >2 minors behind", () => {
|
|
232
|
-
// This tests the policy: behindMinor > 2 threshold
|
|
233
|
-
const info1 = {
|
|
234
|
-
available: true,
|
|
235
|
-
current: "0.2.0",
|
|
236
|
-
latest: "0.4.0",
|
|
237
|
-
behindMinor: 2,
|
|
238
|
-
dismissed: false,
|
|
239
|
-
};
|
|
240
|
-
// 2 minors behind: should NOT inject (threshold is >2, not >=2)
|
|
241
|
-
expect(info1.behindMinor > 2).toBe(false);
|
|
242
|
-
const info2 = {
|
|
243
|
-
available: true,
|
|
244
|
-
current: "0.2.0",
|
|
245
|
-
latest: "0.5.0",
|
|
246
|
-
behindMinor: 3,
|
|
247
|
-
dismissed: false,
|
|
248
|
-
};
|
|
249
|
-
// 3 minors behind: SHOULD inject
|
|
250
|
-
expect(info2.behindMinor > 2).toBe(true);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
describe("no auto-apply", () => {
|
|
254
|
-
it("checkForUpdate never modifies installed version", async () => {
|
|
255
|
-
const before = getInstalledVersion();
|
|
256
|
-
const { checkForUpdate } = await import("../daemon/version-checker.js");
|
|
257
|
-
await checkForUpdate();
|
|
258
|
-
const after = getInstalledVersion();
|
|
259
|
-
expect(after).toBe(before);
|
|
260
|
-
});
|
|
261
|
-
it("version cache only stores metadata, not binaries", () => {
|
|
262
|
-
const cache = readVersionCache();
|
|
263
|
-
const keys = Object.keys(cache);
|
|
264
|
-
// No "binary", "download", "archive", "update" keys
|
|
265
|
-
expect(keys).not.toContain("binary");
|
|
266
|
-
expect(keys).not.toContain("download");
|
|
267
|
-
expect(keys).not.toContain("archive");
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
describe("getInstalledVersion", () => {
|
|
271
|
-
it("returns a semver-like string", () => {
|
|
272
|
-
const v = getInstalledVersion();
|
|
273
|
-
expect(v).toMatch(/^\d+\.\d+\.\d+/);
|
|
274
|
-
});
|
|
275
|
-
});
|