mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
test: remove duplicate and theatrical tests (#2830)
- delete manifest-cov.test.ts: it duplicated stripDangerousKeys, agentKeys/cloudKeys/matrixStatus/countImplemented from manifest.test.ts; unique tests (isStaleCache, getCacheAge, richer loadManifest edge cases) consolidated into manifest.test.ts - remove sprite/interactiveSession from sprite-cov.test.ts: superseded by sprite-keep-alive.test.ts which tests actual script content - remove sprite/installSpriteKeepAlive from sprite-cov.test.ts: superseded by sprite-keep-alive.test.ts - remove startGateway from agent-setup-cov.test.ts: superseded by gateway-resilience.test.ts which checks systemd config, cron, and port-wait all 2050 tests pass Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
This commit is contained in:
parent
8be8b650b0
commit
32525f5dd7
4 changed files with 118 additions and 403 deletions
|
|
@ -230,32 +230,6 @@ describe("offerGithubAuth with token", () => {
|
|||
|
||||
// ── startGateway ──────────────────────────────────────────────────────
|
||||
|
||||
describe("startGateway", () => {
|
||||
it("runs gateway setup commands on the remote", async () => {
|
||||
const { startGateway } = await import("../shared/agent-setup.js");
|
||||
const runner = {
|
||||
runServer: mock(() => Promise.resolve()),
|
||||
uploadFile: mock(() => Promise.resolve()),
|
||||
downloadFile: mock(() => Promise.resolve()),
|
||||
};
|
||||
await startGateway(runner);
|
||||
expect(runner.runServer).toHaveBeenCalled();
|
||||
const calls = runner.runServer.mock.calls;
|
||||
const allCmds = calls.map((c: unknown[]) => String(c[0])).join(" ");
|
||||
expect(allCmds).toContain("openclaw");
|
||||
});
|
||||
|
||||
it("throws when runServer fails", async () => {
|
||||
const { startGateway } = await import("../shared/agent-setup.js");
|
||||
const runner = {
|
||||
runServer: mock(() => Promise.reject(new Error("gateway failed"))),
|
||||
uploadFile: mock(() => Promise.resolve()),
|
||||
downloadFile: mock(() => Promise.resolve()),
|
||||
};
|
||||
await expect(startGateway(runner)).rejects.toThrow("gateway failed");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Agent install, configure, and envVars coverage ────────────────────
|
||||
|
||||
describe("createCloudAgents detailed", () => {
|
||||
|
|
|
|||
|
|
@ -1,304 +0,0 @@
|
|||
/**
|
||||
* manifest-cov.test.ts — Coverage tests for manifest.ts
|
||||
*
|
||||
* Focuses on uncovered paths: stripDangerousKeys, isValidManifest edge cases,
|
||||
* stale cache fallback, local manifest loading, forceRefresh, utility functions
|
||||
* (agentKeys, cloudKeys, matrixStatus, countImplemented, isStaleCache, getCacheAge).
|
||||
*/
|
||||
|
||||
import type { TestEnvironment } from "./test-helpers";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
_resetCacheForTesting,
|
||||
agentKeys,
|
||||
cloudKeys,
|
||||
countImplemented,
|
||||
getCacheAge,
|
||||
isStaleCache,
|
||||
loadManifest,
|
||||
matrixStatus,
|
||||
stripDangerousKeys,
|
||||
} from "../manifest";
|
||||
import { createMockManifest, setupTestEnvironment, teardownTestEnvironment } from "./test-helpers";
|
||||
|
||||
const mockManifest = createMockManifest();
|
||||
|
||||
describe("manifest.ts coverage", () => {
|
||||
let env: TestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = setupTestEnvironment();
|
||||
_resetCacheForTesting();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
teardownTestEnvironment(env);
|
||||
});
|
||||
|
||||
// ── stripDangerousKeys ────────────────────────────────────────────────
|
||||
|
||||
describe("stripDangerousKeys", () => {
|
||||
it("removes __proto__ keys", () => {
|
||||
const input = {
|
||||
normal: "ok",
|
||||
__proto__: {
|
||||
malicious: true,
|
||||
},
|
||||
};
|
||||
const cleaned = stripDangerousKeys(input);
|
||||
expect(cleaned).toEqual({
|
||||
normal: "ok",
|
||||
});
|
||||
});
|
||||
|
||||
it("removes constructor keys", () => {
|
||||
const input = {
|
||||
a: 1,
|
||||
constructor: "bad",
|
||||
};
|
||||
const cleaned = stripDangerousKeys(input);
|
||||
expect(cleaned).toEqual({
|
||||
a: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("removes prototype keys", () => {
|
||||
const input = {
|
||||
a: 1,
|
||||
prototype: {
|
||||
x: 1,
|
||||
},
|
||||
};
|
||||
const cleaned = stripDangerousKeys(input);
|
||||
expect(cleaned).toEqual({
|
||||
a: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("recursively strips from nested objects", () => {
|
||||
const input = {
|
||||
nested: {
|
||||
__proto__: "bad",
|
||||
ok: "fine",
|
||||
},
|
||||
};
|
||||
const cleaned = stripDangerousKeys(input);
|
||||
expect(cleaned).toEqual({
|
||||
nested: {
|
||||
ok: "fine",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("handles arrays", () => {
|
||||
const input = [
|
||||
{
|
||||
__proto__: "bad",
|
||||
ok: "fine",
|
||||
},
|
||||
];
|
||||
const cleaned = stripDangerousKeys(input);
|
||||
expect(cleaned).toEqual([
|
||||
{
|
||||
ok: "fine",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns primitives unchanged", () => {
|
||||
expect(stripDangerousKeys("hello")).toBe("hello");
|
||||
expect(stripDangerousKeys(42)).toBe(42);
|
||||
expect(stripDangerousKeys(null)).toBeNull();
|
||||
expect(stripDangerousKeys(true)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── agentKeys / cloudKeys / matrixStatus / countImplemented ────────────
|
||||
|
||||
describe("utility functions", () => {
|
||||
it("agentKeys returns sorted by stars descending", () => {
|
||||
const keys = agentKeys(mockManifest);
|
||||
expect(keys).toContain("claude");
|
||||
expect(keys).toContain("codex");
|
||||
});
|
||||
|
||||
it("cloudKeys returns cloud keys", () => {
|
||||
const keys = cloudKeys(mockManifest);
|
||||
expect(keys).toContain("sprite");
|
||||
expect(keys).toContain("hetzner");
|
||||
});
|
||||
|
||||
it("matrixStatus returns implemented for known combo", () => {
|
||||
expect(matrixStatus(mockManifest, "sprite", "claude")).toBe("implemented");
|
||||
});
|
||||
|
||||
it("matrixStatus returns missing for known missing combo", () => {
|
||||
expect(matrixStatus(mockManifest, "hetzner", "codex")).toBe("missing");
|
||||
});
|
||||
|
||||
it("matrixStatus returns missing for unknown combo", () => {
|
||||
expect(matrixStatus(mockManifest, "unknown", "unknown")).toBe("missing");
|
||||
});
|
||||
|
||||
it("countImplemented counts implemented entries", () => {
|
||||
const count = countImplemented(mockManifest);
|
||||
expect(count).toBe(3); // sprite/claude, sprite/codex, hetzner/claude
|
||||
});
|
||||
});
|
||||
|
||||
// ── isStaleCache / getCacheAge ────────────────────────────────────────
|
||||
|
||||
describe("cache state helpers", () => {
|
||||
it("isStaleCache returns false initially", () => {
|
||||
expect(isStaleCache()).toBe(false);
|
||||
});
|
||||
|
||||
it("getCacheAge returns Infinity when no cache", () => {
|
||||
expect(getCacheAge()).toBe(Number.POSITIVE_INFINITY);
|
||||
});
|
||||
});
|
||||
|
||||
// ── loadManifest ──────────────────────────────────────────────────────
|
||||
|
||||
describe("loadManifest", () => {
|
||||
it("fetches from GitHub and caches", async () => {
|
||||
global.fetch = mock(async () => new Response(JSON.stringify(mockManifest)));
|
||||
const m = await loadManifest();
|
||||
expect(m.agents).toBeDefined();
|
||||
expect(m.clouds).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns in-memory cache on second call", async () => {
|
||||
const fetchMock = mock(async () => new Response(JSON.stringify(mockManifest)));
|
||||
global.fetch = fetchMock;
|
||||
await loadManifest();
|
||||
const fetchCount = fetchMock.mock.calls.length;
|
||||
await loadManifest();
|
||||
// Should not have fetched again
|
||||
expect(fetchMock.mock.calls.length).toBe(fetchCount);
|
||||
});
|
||||
|
||||
it("reads from disk cache when fresh", async () => {
|
||||
// Write a fresh cache file
|
||||
const cacheDir = join(env.testDir, "spawn");
|
||||
mkdirSync(cacheDir, {
|
||||
recursive: true,
|
||||
});
|
||||
writeFileSync(join(cacheDir, "manifest.json"), JSON.stringify(mockManifest));
|
||||
|
||||
_resetCacheForTesting();
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response("should not be called", {
|
||||
status: 500,
|
||||
}),
|
||||
);
|
||||
|
||||
const m = await loadManifest();
|
||||
expect(m.agents.claude).toBeDefined();
|
||||
});
|
||||
|
||||
it("forceRefresh bypasses in-memory and disk cache", async () => {
|
||||
const updatedManifest = {
|
||||
...mockManifest,
|
||||
agents: {
|
||||
...mockManifest.agents,
|
||||
newagent: {
|
||||
name: "New Agent",
|
||||
description: "test",
|
||||
url: "test",
|
||||
install: "test",
|
||||
launch: "test",
|
||||
env: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
global.fetch = mock(async () => new Response(JSON.stringify(mockManifest)));
|
||||
await loadManifest();
|
||||
|
||||
global.fetch = mock(async () => new Response(JSON.stringify(updatedManifest)));
|
||||
const m = await loadManifest(true);
|
||||
expect(m.agents.newagent).toBeDefined();
|
||||
});
|
||||
|
||||
it("falls back to stale cache when fetch fails", async () => {
|
||||
// Write a cache file (will be stale because cache TTL check)
|
||||
const cacheDir = join(env.testDir, "spawn");
|
||||
mkdirSync(cacheDir, {
|
||||
recursive: true,
|
||||
});
|
||||
writeFileSync(join(cacheDir, "manifest.json"), JSON.stringify(mockManifest));
|
||||
|
||||
_resetCacheForTesting();
|
||||
// All fetches fail
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response("error", {
|
||||
status: 500,
|
||||
}),
|
||||
);
|
||||
|
||||
const m = await loadManifest(true);
|
||||
expect(m.agents.claude).toBeDefined();
|
||||
expect(isStaleCache()).toBe(true);
|
||||
});
|
||||
|
||||
it("throws when no cache and fetch fails", async () => {
|
||||
_resetCacheForTesting();
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response("error", {
|
||||
status: 500,
|
||||
}),
|
||||
);
|
||||
|
||||
// Make sure no cache file exists
|
||||
const cacheFile = join(env.testDir, "spawn", "manifest.json");
|
||||
if (existsSync(cacheFile)) {
|
||||
rmSync(cacheFile);
|
||||
}
|
||||
|
||||
await expect(loadManifest(true)).rejects.toThrow("Cannot load manifest");
|
||||
});
|
||||
|
||||
it("handles invalid manifest from GitHub", async () => {
|
||||
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
not: "a manifest",
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Ensure no cache
|
||||
const cacheFile = join(env.testDir, "spawn", "manifest.json");
|
||||
if (existsSync(cacheFile)) {
|
||||
rmSync(cacheFile);
|
||||
}
|
||||
|
||||
await expect(loadManifest(true)).rejects.toThrow("Cannot load manifest");
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("handles network error during fetch", async () => {
|
||||
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
||||
global.fetch = mock(async () => {
|
||||
throw new Error("Network timeout");
|
||||
});
|
||||
|
||||
const cacheFile = join(env.testDir, "spawn", "manifest.json");
|
||||
if (existsSync(cacheFile)) {
|
||||
rmSync(cacheFile);
|
||||
}
|
||||
|
||||
await expect(loadManifest(true)).rejects.toThrow("Cannot load manifest");
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,10 +1,20 @@
|
|||
import type { Manifest } from "../manifest";
|
||||
import type { TestEnvironment } from "./test-helpers";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { agentKeys, cloudKeys, countImplemented, loadManifest, matrixStatus, stripDangerousKeys } from "../manifest";
|
||||
import {
|
||||
_resetCacheForTesting,
|
||||
agentKeys,
|
||||
cloudKeys,
|
||||
countImplemented,
|
||||
getCacheAge,
|
||||
isStaleCache,
|
||||
loadManifest,
|
||||
matrixStatus,
|
||||
stripDangerousKeys,
|
||||
} from "../manifest";
|
||||
import {
|
||||
createEmptyManifest,
|
||||
createMockManifest,
|
||||
|
|
@ -103,6 +113,7 @@ describe("manifest", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
env = setupTestEnvironment();
|
||||
_resetCacheForTesting();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -110,7 +121,6 @@ describe("manifest", () => {
|
|||
});
|
||||
|
||||
it("should fetch from network when cache is missing", async () => {
|
||||
// Mock successful fetch
|
||||
global.fetch = mockSuccessfulFetch(mockManifest);
|
||||
|
||||
const manifest = await loadManifest(true); // Force refresh
|
||||
|
|
@ -127,13 +137,11 @@ describe("manifest", () => {
|
|||
});
|
||||
|
||||
it("should use disk cache when fresh", async () => {
|
||||
// Write fresh cache
|
||||
mkdirSync(join(env.testDir, "spawn"), {
|
||||
recursive: true,
|
||||
});
|
||||
writeFileSync(env.cacheFile, JSON.stringify(mockManifest));
|
||||
|
||||
// Mock fetch — must NOT be called when cache is fresh
|
||||
global.fetch = mock(() => Promise.resolve(new Response(JSON.stringify(mockManifest))));
|
||||
|
||||
const manifest = await loadManifest();
|
||||
|
|
@ -145,13 +153,11 @@ describe("manifest", () => {
|
|||
});
|
||||
|
||||
it("should refresh cache when forceRefresh is true", async () => {
|
||||
// Write stale cache
|
||||
mkdirSync(join(env.testDir, "spawn"), {
|
||||
recursive: true,
|
||||
});
|
||||
writeFileSync(env.cacheFile, JSON.stringify(mockManifest));
|
||||
|
||||
// Mock successful fetch with different data
|
||||
const updatedManifest = {
|
||||
...mockManifest,
|
||||
agents: {},
|
||||
|
|
@ -164,6 +170,110 @@ describe("manifest", () => {
|
|||
expect(manifest).toHaveProperty("matrix");
|
||||
expect(global.fetch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns in-memory cache on second call without fetching", async () => {
|
||||
const fetchMock = mock(async () => new Response(JSON.stringify(mockManifest)));
|
||||
global.fetch = fetchMock;
|
||||
await loadManifest();
|
||||
const fetchCount = fetchMock.mock.calls.length;
|
||||
await loadManifest();
|
||||
expect(fetchMock.mock.calls.length).toBe(fetchCount);
|
||||
});
|
||||
|
||||
it("falls back to stale cache when fetch fails", async () => {
|
||||
const cacheDir = join(env.testDir, "spawn");
|
||||
mkdirSync(cacheDir, {
|
||||
recursive: true,
|
||||
});
|
||||
writeFileSync(join(cacheDir, "manifest.json"), JSON.stringify(mockManifest));
|
||||
|
||||
_resetCacheForTesting();
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response("error", {
|
||||
status: 500,
|
||||
}),
|
||||
);
|
||||
|
||||
const m = await loadManifest(true);
|
||||
expect(m.agents.claude).toBeDefined();
|
||||
expect(isStaleCache()).toBe(true);
|
||||
});
|
||||
|
||||
it("throws when no cache and fetch fails", async () => {
|
||||
_resetCacheForTesting();
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response("error", {
|
||||
status: 500,
|
||||
}),
|
||||
);
|
||||
|
||||
const cacheFile = join(env.testDir, "spawn", "manifest.json");
|
||||
if (existsSync(cacheFile)) {
|
||||
rmSync(cacheFile);
|
||||
}
|
||||
|
||||
await expect(loadManifest(true)).rejects.toThrow("Cannot load manifest");
|
||||
});
|
||||
|
||||
it("throws when manifest from GitHub is invalid", async () => {
|
||||
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
not: "a manifest",
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const cacheFile = join(env.testDir, "spawn", "manifest.json");
|
||||
if (existsSync(cacheFile)) {
|
||||
rmSync(cacheFile);
|
||||
}
|
||||
|
||||
await expect(loadManifest(true)).rejects.toThrow("Cannot load manifest");
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("throws when network errors occur and no cache exists", async () => {
|
||||
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
||||
global.fetch = mock(async () => {
|
||||
throw new Error("Network timeout");
|
||||
});
|
||||
|
||||
const cacheFile = join(env.testDir, "spawn", "manifest.json");
|
||||
if (existsSync(cacheFile)) {
|
||||
rmSync(cacheFile);
|
||||
}
|
||||
|
||||
await expect(loadManifest(true)).rejects.toThrow("Cannot load manifest");
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── cache state helpers ───────────────────────────────────────────────────────
|
||||
|
||||
describe("manifest cache state", () => {
|
||||
let env: TestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = setupTestEnvironment();
|
||||
_resetCacheForTesting();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
teardownTestEnvironment(env);
|
||||
});
|
||||
|
||||
it("isStaleCache returns false initially", () => {
|
||||
expect(isStaleCache()).toBe(false);
|
||||
});
|
||||
|
||||
it("getCacheAge returns Infinity when no cache file exists", () => {
|
||||
expect(getCacheAge()).toBe(Number.POSITIVE_INFINITY);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -487,48 +487,6 @@ describe("sprite/downloadFileSprite", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ─── interactiveSession ──────────────────────────────────────────────────────
|
||||
|
||||
describe("sprite/interactiveSession", () => {
|
||||
it("uses spawnInteractive by default", async () => {
|
||||
const spawnSyncSpy = mockSpawnSync(0, "/usr/bin/sprite");
|
||||
const { interactiveSession } = await import("../sprite/sprite");
|
||||
const spawnFn = mock(() => 0);
|
||||
const code = await interactiveSession("echo hello", spawnFn);
|
||||
expect(code).toBe(0);
|
||||
expect(spawnFn).toHaveBeenCalled();
|
||||
spawnSyncSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("uses -tty when SPAWN_PROMPT is not set", async () => {
|
||||
delete process.env.SPAWN_PROMPT;
|
||||
const spawnSyncSpy = mockSpawnSync(0, "/usr/bin/sprite");
|
||||
const { interactiveSession } = await import("../sprite/sprite");
|
||||
let capturedArgs: string[] = [];
|
||||
const spawnFn = mock((args: string[]) => {
|
||||
capturedArgs = args;
|
||||
return 0;
|
||||
});
|
||||
await interactiveSession("echo hello", spawnFn);
|
||||
expect(capturedArgs).toContain("-tty");
|
||||
spawnSyncSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("omits -tty when SPAWN_PROMPT is set", async () => {
|
||||
process.env.SPAWN_PROMPT = "some prompt";
|
||||
const spawnSyncSpy = mockSpawnSync(0, "/usr/bin/sprite");
|
||||
const { interactiveSession } = await import("../sprite/sprite");
|
||||
let capturedArgs: string[] = [];
|
||||
const spawnFn = mock((args: string[]) => {
|
||||
capturedArgs = args;
|
||||
return 0;
|
||||
});
|
||||
await interactiveSession("echo hello", spawnFn);
|
||||
expect(capturedArgs).not.toContain("-tty");
|
||||
spawnSyncSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── destroyServer ───────────────────────────────────────────────────────────
|
||||
|
||||
describe("sprite/destroyServer", () => {
|
||||
|
|
@ -574,29 +532,6 @@ describe("sprite/runSprite", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ─── installSpriteKeepAlive ──────────────────────────────────────────────────
|
||||
|
||||
describe("sprite/installSpriteKeepAlive", () => {
|
||||
it("succeeds when curl works", async () => {
|
||||
const spawnSyncSpy = mockSpawnSync(0, "/usr/bin/sprite");
|
||||
const spawnSpy = mockBunSpawn(0);
|
||||
const { installSpriteKeepAlive } = await import("../sprite/sprite");
|
||||
await installSpriteKeepAlive();
|
||||
spawnSyncSpy.mockRestore();
|
||||
spawnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("warns but does not throw when install fails", async () => {
|
||||
const spawnSyncSpy = mockSpawnSync(0, "/usr/bin/sprite");
|
||||
const spawnSpy = mockBunSpawn(1, "", "curl failed");
|
||||
const { installSpriteKeepAlive } = await import("../sprite/sprite");
|
||||
// Should not throw
|
||||
await installSpriteKeepAlive();
|
||||
spawnSyncSpy.mockRestore();
|
||||
spawnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── setupShellEnvironment ───────────────────────────────────────────────────
|
||||
|
||||
describe("sprite/setupShellEnvironment", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue