mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
613 lines
21 KiB
TypeScript
613 lines
21 KiB
TypeScript
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { FilePolicyLoader } from "#src/policy-loader";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function makeTempDir(): string {
|
|
return mkdtempSync(join(tmpdir(), "policy-loader-test-"));
|
|
}
|
|
|
|
function makeLoader(
|
|
baseDir: string,
|
|
options: {
|
|
globalConfig?: Record<string, unknown>;
|
|
mcpServerNames?: readonly string[];
|
|
} = {},
|
|
) {
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
|
|
const globalConfigPath = join(baseDir, "config.json");
|
|
writeFileSync(
|
|
globalConfigPath,
|
|
JSON.stringify(options.globalConfig ?? {}, null, 2),
|
|
);
|
|
|
|
return new FilePolicyLoader({
|
|
globalConfigPath,
|
|
agentsDir,
|
|
mcpServerNames: options.mcpServerNames
|
|
? [...options.mcpServerNames]
|
|
: undefined,
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// loadGlobalConfig
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.loadGlobalConfig", () => {
|
|
it("returns ScopeConfig with permission from a valid config file", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir, {
|
|
globalConfig: { permission: { "*": "allow", read: "ask" } },
|
|
});
|
|
const config = loader.loadGlobalConfig();
|
|
expect(config.permission).toEqual({ "*": "allow", read: "ask" });
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("returns empty ScopeConfig when config file is missing", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
});
|
|
const config = loader.loadGlobalConfig();
|
|
expect(config.permission).toBeUndefined();
|
|
});
|
|
|
|
it("returns empty ScopeConfig when config file has no permission key", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir, {
|
|
globalConfig: { debugLog: true },
|
|
});
|
|
const config = loader.loadGlobalConfig();
|
|
expect(config.permission).toBeUndefined();
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// loadProjectConfig
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.loadProjectConfig", () => {
|
|
it("returns empty ScopeConfig when no project path is configured", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
});
|
|
const config = loader.loadProjectConfig();
|
|
expect(config).toEqual({});
|
|
});
|
|
|
|
it("returns ScopeConfig from a project config file", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const projectConfigPath = join(baseDir, "project-config.json");
|
|
writeFileSync(
|
|
projectConfigPath,
|
|
JSON.stringify({ permission: { bash: "allow" } }),
|
|
);
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
projectGlobalConfigPath: projectConfigPath,
|
|
});
|
|
const config = loader.loadProjectConfig();
|
|
expect(config.permission).toEqual({ bash: "allow" });
|
|
expect(config.invalid).toBeUndefined();
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("marks the scope invalid when the project config file is rejected", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const projectConfigPath = join(baseDir, "project-config.json");
|
|
// Unrecognized key — schema rejection, the issue's "typo" scenario.
|
|
writeFileSync(projectConfigPath, JSON.stringify({ debugLo: true }));
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
projectGlobalConfigPath: projectConfigPath,
|
|
});
|
|
const config = loader.loadProjectConfig();
|
|
expect(config.invalid).toBe(true);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("leaves invalid unset when no project file is present", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
projectGlobalConfigPath: "/nonexistent/project-config.json",
|
|
});
|
|
const config = loader.loadProjectConfig();
|
|
expect(config.invalid).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// loadAgentConfig / loadProjectAgentConfig
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.loadAgentConfig", () => {
|
|
it("returns empty ScopeConfig when agentName is undefined", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir);
|
|
expect(loader.loadAgentConfig()).toEqual({});
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("returns ScopeConfig from agent frontmatter", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(
|
|
join(agentsDir, "coder.md"),
|
|
`---\npermission:\n bash: allow\n---\n# Coder agent\n`,
|
|
);
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: join(baseDir, "config.json"),
|
|
agentsDir,
|
|
});
|
|
writeFileSync(join(baseDir, "config.json"), "{}");
|
|
const config = loader.loadAgentConfig("coder");
|
|
expect(config.permission).toEqual({ bash: "allow" });
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("returns empty ScopeConfig when agent file does not exist", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir);
|
|
const config = loader.loadAgentConfig("nonexistent");
|
|
expect(config).toEqual({});
|
|
expect(config.invalid).toBeUndefined();
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("marks the scope invalid when an existing agent file cannot be read", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
// A directory at the agent's file path exists (stat succeeds) but
|
|
// readFileSync throws EISDIR — a present-but-unreadable file.
|
|
mkdirSync(join(agentsDir, "coder.md"));
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: join(baseDir, "config.json"),
|
|
agentsDir,
|
|
});
|
|
writeFileSync(join(baseDir, "config.json"), "{}");
|
|
const config = loader.loadAgentConfig("coder");
|
|
expect(config.invalid).toBe(true);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("FilePolicyLoader.loadProjectAgentConfig", () => {
|
|
it("returns empty ScopeConfig when no projectAgentsDir is configured", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir);
|
|
expect(loader.loadProjectAgentConfig("coder")).toEqual({});
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// getConfigIssues
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.getConfigIssues", () => {
|
|
it("returns empty array before any loads", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
});
|
|
expect(loader.getConfigIssues()).toEqual([]);
|
|
});
|
|
|
|
it("returns empty array for valid config", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir, {
|
|
globalConfig: { permission: { "*": "ask" } },
|
|
});
|
|
loader.loadGlobalConfig();
|
|
expect(loader.getConfigIssues()).toEqual([]);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// getResolvedPolicyPaths
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.getResolvedPolicyPaths", () => {
|
|
it("returns correct paths and existence when files exist", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const globalConfigPath = join(baseDir, "config.json");
|
|
const agentsDir = join(baseDir, "agents");
|
|
writeFileSync(globalConfigPath, "{}");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
|
|
const loader = new FilePolicyLoader({ globalConfigPath, agentsDir });
|
|
const paths = loader.getResolvedPolicyPaths();
|
|
|
|
expect(paths.globalConfigPath).toBe(globalConfigPath);
|
|
expect(paths.globalConfigExists).toBe(true);
|
|
expect(paths.agentsDir).toBe(agentsDir);
|
|
expect(paths.agentsDirExists).toBe(true);
|
|
expect(paths.projectConfigPath).toBeNull();
|
|
expect(paths.projectConfigExists).toBe(false);
|
|
expect(paths.projectAgentsDir).toBeNull();
|
|
expect(paths.projectAgentsDirExists).toBe(false);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// getCacheStamp
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.getCacheStamp", () => {
|
|
it("returns a string stamp", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
});
|
|
const stamp = loader.getCacheStamp();
|
|
expect(typeof stamp).toBe("string");
|
|
expect(stamp.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("changes when the global config file changes", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const globalConfigPath = join(baseDir, "config.json");
|
|
writeFileSync(globalConfigPath, "{}");
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
|
|
const loader = new FilePolicyLoader({ globalConfigPath, agentsDir });
|
|
const stamp1 = loader.getCacheStamp();
|
|
|
|
// Wait a tick so mtime changes
|
|
const now = Date.now();
|
|
while (Date.now() - now < 50) {
|
|
// busy-wait for mtime resolution
|
|
}
|
|
writeFileSync(globalConfigPath, '{"permission": {}}');
|
|
const stamp2 = loader.getCacheStamp();
|
|
|
|
expect(stamp1).not.toBe(stamp2);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("includes agent stamp when agentName is provided and agent file exists", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(join(baseDir, "config.json"), "{}");
|
|
writeFileSync(
|
|
join(agentsDir, "coder.md"),
|
|
"---\npermission:\n read: allow\n---\n",
|
|
);
|
|
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: join(baseDir, "config.json"),
|
|
agentsDir,
|
|
});
|
|
|
|
const stampWithout = loader.getCacheStamp();
|
|
const stampWith = loader.getCacheStamp("coder");
|
|
// Agent file exists, so the stamp differs from the no-agent case.
|
|
expect(stampWithout).not.toBe(stampWith);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mtime cache invalidation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader mtime caching", () => {
|
|
it("returns cached value on second call with unchanged file", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const loader = makeLoader(baseDir, {
|
|
globalConfig: { permission: { "*": "allow" } },
|
|
});
|
|
const first = loader.loadGlobalConfig();
|
|
const second = loader.loadGlobalConfig();
|
|
// Same reference — cache hit
|
|
expect(second).toBe(first);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("reloads when file mtime changes", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const globalConfigPath = join(baseDir, "config.json");
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(
|
|
globalConfigPath,
|
|
JSON.stringify({ permission: { "*": "allow" } }),
|
|
);
|
|
|
|
const loader = new FilePolicyLoader({ globalConfigPath, agentsDir });
|
|
const first = loader.loadGlobalConfig();
|
|
expect(first.permission?.["*"]).toBe("allow");
|
|
|
|
// busy-wait for mtime resolution
|
|
const now = Date.now();
|
|
while (Date.now() - now < 50) {
|
|
/* spin */
|
|
}
|
|
|
|
writeFileSync(
|
|
globalConfigPath,
|
|
JSON.stringify({ permission: { "*": "deny" } }),
|
|
);
|
|
const second = loader.loadGlobalConfig();
|
|
expect(second.permission?.["*"]).toBe("deny");
|
|
expect(second).not.toBe(first);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Agent frontmatter loading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader agent frontmatter", () => {
|
|
it("loads permission from agent frontmatter with pattern map", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(join(baseDir, "config.json"), "{}");
|
|
writeFileSync(
|
|
join(agentsDir, "coder.md"),
|
|
[
|
|
"---",
|
|
"permission:",
|
|
" bash:",
|
|
' "git *": allow',
|
|
' "rm *": deny',
|
|
"---",
|
|
"# Coder",
|
|
].join("\n"),
|
|
);
|
|
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: join(baseDir, "config.json"),
|
|
agentsDir,
|
|
});
|
|
const config = loader.loadAgentConfig("coder");
|
|
expect(config.permission).toBeDefined();
|
|
expect(config.permission?.bash).toEqual({
|
|
"git *": "allow",
|
|
"rm *": "deny",
|
|
});
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("returns empty config for agent file without frontmatter", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(join(baseDir, "config.json"), "{}");
|
|
writeFileSync(join(agentsDir, "plain.md"), "# No frontmatter\n");
|
|
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: join(baseDir, "config.json"),
|
|
agentsDir,
|
|
});
|
|
expect(loader.loadAgentConfig("plain")).toEqual({});
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("project agent config loads from projectAgentsDir", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const agentsDir = join(baseDir, "agents");
|
|
const projectAgentsDir = join(baseDir, "project-agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
mkdirSync(projectAgentsDir, { recursive: true });
|
|
writeFileSync(join(baseDir, "config.json"), "{}");
|
|
writeFileSync(
|
|
join(projectAgentsDir, "coder.md"),
|
|
"---\npermission:\n write: allow\n---\n# Coder\n",
|
|
);
|
|
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: join(baseDir, "config.json"),
|
|
agentsDir,
|
|
projectAgentsDir,
|
|
});
|
|
const config = loader.loadProjectAgentConfig("coder");
|
|
expect(config.permission).toEqual({ write: "allow" });
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MCP server name reading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader.getConfiguredMcpServerNames", () => {
|
|
it("returns override names when provided", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
mcpServerNames: ["exa", "research"],
|
|
});
|
|
expect(loader.getConfiguredMcpServerNames()).toEqual(
|
|
expect.arrayContaining(["exa", "research"]),
|
|
);
|
|
});
|
|
|
|
it("deduplicates and trims override names", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
mcpServerNames: [" exa ", "exa", "research"],
|
|
});
|
|
const names = loader.getConfiguredMcpServerNames();
|
|
expect(names.filter((n) => n === "exa")).toHaveLength(1);
|
|
});
|
|
|
|
it("reads server names from mcp.json on disk", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const mcpConfigPath = join(baseDir, "mcp.json");
|
|
writeFileSync(
|
|
mcpConfigPath,
|
|
JSON.stringify({ mcpServers: { exa: {}, research: {} } }),
|
|
);
|
|
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
globalMcpConfigPath: mcpConfigPath,
|
|
});
|
|
const names = loader.getConfiguredMcpServerNames();
|
|
expect(names).toEqual(expect.arrayContaining(["exa", "research"]));
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("returns empty array when mcp.json is missing", () => {
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
globalMcpConfigPath: "/nonexistent/mcp.json",
|
|
});
|
|
expect(loader.getConfiguredMcpServerNames()).toEqual([]);
|
|
});
|
|
|
|
it("caches MCP server names across calls", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const mcpConfigPath = join(baseDir, "mcp.json");
|
|
writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers: { exa: {} } }));
|
|
|
|
const loader = new FilePolicyLoader({
|
|
globalConfigPath: "/nonexistent/config.json",
|
|
agentsDir: "/nonexistent/agents",
|
|
globalMcpConfigPath: mcpConfigPath,
|
|
});
|
|
const first = loader.getConfiguredMcpServerNames();
|
|
const second = loader.getConfiguredMcpServerNames();
|
|
// Same reference — cache hit
|
|
expect(second).toBe(first);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config issue accumulation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("FilePolicyLoader config issue accumulation", () => {
|
|
it("accumulates issues from malformed config files", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
// Write invalid JSON to trigger a parse error issue
|
|
const globalConfigPath = join(baseDir, "config.json");
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(globalConfigPath, "{ INVALID JSON");
|
|
|
|
const loader = new FilePolicyLoader({ globalConfigPath, agentsDir });
|
|
loader.loadGlobalConfig();
|
|
const issues = loader.getConfigIssues();
|
|
expect(issues.length).toBeGreaterThanOrEqual(1);
|
|
expect(issues[0]).toContain("Failed to read config");
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not duplicate issues on repeated loads", () => {
|
|
const baseDir = makeTempDir();
|
|
try {
|
|
const globalConfigPath = join(baseDir, "config.json");
|
|
const agentsDir = join(baseDir, "agents");
|
|
mkdirSync(agentsDir, { recursive: true });
|
|
writeFileSync(globalConfigPath, "{ INVALID JSON");
|
|
|
|
const loader = new FilePolicyLoader({ globalConfigPath, agentsDir });
|
|
loader.loadGlobalConfig();
|
|
const issuesBefore = loader.getConfigIssues();
|
|
|
|
// Bust cache by waiting for mtime change
|
|
const now = Date.now();
|
|
while (Date.now() - now < 50) {
|
|
/* spin */
|
|
}
|
|
writeFileSync(globalConfigPath, "{ INVALID JSON");
|
|
loader.loadGlobalConfig();
|
|
const issuesAfter = loader.getConfigIssues();
|
|
expect(issuesAfter.length).toBe(issuesBefore.length);
|
|
} finally {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|