Files
oh-my-pi/packages/coding-agent/test/profile-alias.test.ts
T
Ogrodev 2e4f258050 fix(profiles): harden alias upsert, env precedence, and bootstrap flag handling
- profile-alias: refuse to rewrite a managed block whose start marker lacks a
  matching end marker instead of appending, which on the next install would
  splice from the stale start through the new end and delete intervening user
  shell config (data loss in dotfiles).
- dirs/cli: add resolveProfileEnv so OMP_PROFILE takes precedence and an
  explicitly-empty OMP_PROFILE selects the default profile instead of falling
  through to PI_PROFILE; share the rule across both env-read sites.
- dirs: reject uppercase profile names so profile identity/isolation is stable
  across case-sensitive and case-insensitive filesystems.
- profile-bootstrap: treat an unclassified bare long option as a possible
  extension string flag and forward its successor untouched (never as a global
  --profile/--alias), while exempting known value-less launch flags via
  VALUELESS_FLAGS so 'omp --print --profile work' still selects a profile.
- tests: cover all four contracts.
2026-05-30 11:05:30 -03:00

187 lines
5.6 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { installProfileAlias, readProfileAliasConfigFile } from "../src/cli/profile-alias";
describe("profile alias installer", () => {
it("writes a bash-compatible alias that forwards subcommands through omp", async () => {
const files = new Map<string, string>();
const result = await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/bin/bash",
platform: "linux",
homeDir: "/home/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
expect(result.configPath).toBe("/home/me/.bashrc");
expect(files.get("/home/me/.bashrc")).toContain("alias omp-work='command omp --profile work'");
});
it("writes a fish function that forwards argv", async () => {
const files = new Map<string, string>();
await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/opt/homebrew/bin/fish",
platform: "darwin",
homeDir: "/Users/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
const content = files.get("/Users/me/.config/fish/conf.d/omp-profiles.fish") ?? "";
expect(content).toContain("function omp-work --wraps omp");
expect(content).toContain("command omp --profile work $argv");
});
it("writes a PowerShell function because aliases cannot carry arguments", async () => {
const files = new Map<string, string>();
await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "pwsh.exe",
platform: "win32",
homeDir: "C:\\Users\\me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
const content = files.get("C:\\Users\\me/Documents/PowerShell/Microsoft.PowerShell_profile.ps1") ?? "";
expect(content).toContain("function omp-work");
expect(content).toContain("& omp --profile work @args");
});
it("replaces a previous block for the same alias", async () => {
const files = new Map<string, string>([
[
"/home/me/.zshrc",
[
"before",
"# >>> omp profile alias: omp-work >>>",
"alias omp-work='command omp --profile old'",
"# <<< omp profile alias: omp-work <<<",
"after",
].join("\n"),
],
]);
await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/bin/zsh",
platform: "darwin",
homeDir: "/home/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
const content = files.get("/home/me/.zshrc") ?? "";
expect(content).toContain("before");
expect(content).toContain("after");
expect(content).toContain("alias omp-work='command omp --profile work'");
expect(content).not.toContain("--profile old");
});
it("refuses to rewrite a malformed managed block missing its end marker", async () => {
// A start marker without its matching end marker means a previous install
// was interrupted or hand-edited. Appending a fresh block would let the
// *next* install splice from the stale start through the new end, deleting
// the user config in between. Refuse and preserve the file untouched.
const original = [
"# >>> omp profile alias: omp-work >>>",
"alias omp-work='command omp --profile old'",
"export SECRET=keepme",
].join("\n");
const files = new Map<string, string>([["/home/me/.zshrc", original]]);
let wrote = false;
await expect(
installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/bin/zsh",
platform: "darwin",
homeDir: "/home/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
wrote = true;
files.set(filePath, content);
},
}),
).rejects.toThrow(/without a matching/);
expect(wrote).toBe(false);
expect(files.get("/home/me/.zshrc")).toBe(original);
});
it("refuses to shadow the base omp command case-insensitively", async () => {
for (const aliasName of ["omp", "OMP"]) {
await expect(
installProfileAlias({
profile: "work",
aliasName,
shellPath: "/bin/bash",
homeDir: "/home/me",
}),
).rejects.toThrow("Refusing to shadow");
}
});
it("rejects POSIX sh because it does not read bash config files", async () => {
await expect(
installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/bin/sh",
platform: "linux",
homeDir: "/home/me",
}),
).rejects.toThrow('Unsupported shell "sh"');
});
it("treats missing shell config as empty but preserves other read failures", async () => {
await expect(
readProfileAliasConfigFile("/home/me/.bashrc", async () => {
throw Object.assign(new Error("missing"), { code: "ENOENT" });
}),
).resolves.toBe("");
await expect(
readProfileAliasConfigFile("/home/me/.bashrc", async () => {
throw Object.assign(new Error("denied"), { code: "EACCES" });
}),
).rejects.toThrow("denied");
});
it("validates profile names before rendering shell code", async () => {
const files = new Map<string, string>();
await expect(
installProfileAlias({
profile: "work'; touch /tmp/pwn; #",
aliasName: "omp-work",
shellPath: "/bin/bash",
platform: "linux",
homeDir: "/home/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
}),
).rejects.toThrow("Invalid OMP profile");
expect(files.size).toBe(0);
});
});