Files
oh-my-pi/packages/coding-agent/test/profile-bootstrap.test.ts
T
Ogrodev 3a50761153 fix(coding-agent): handle -- boundary and subcommands in CLI parsing
- Add POSIX `--` end-of-options handling in argument parser
- Stop consuming flags after `--` and pass remaining tokens as messages
- Stop global `--profile`/alias extraction at first registered subcommand
- Add focused tests for parseArgs and profile bootstrap boundary behavior
2026-05-30 09:08:15 -03:00

118 lines
5.6 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { extractProfileFlags } from "../src/cli/profile-bootstrap";
describe("extractProfileFlags", () => {
it("extracts --profile without disturbing other tokens", () => {
expect(extractProfileFlags(["--profile", "work"])).toEqual({
argv: [],
profile: "work",
aliasName: undefined,
});
expect(extractProfileFlags(["foo", "--profile=work", "bar"])).toEqual({
argv: ["foo", "bar"],
profile: "work",
aliasName: undefined,
});
});
it("does not eat the value of known string-valued flags", () => {
// `omp --system-prompt --profile foo` must pass the literal `--profile`
// through to the launch parser (it's the system prompt) and `foo` is the
// positional message. The previous implementation would silently activate
// profile `foo` here, dropping the user's prompt.
const result = extractProfileFlags(["--system-prompt", "--profile", "foo", "bar"]);
expect(result.profile).toBeUndefined();
expect(result.argv).toEqual(["--system-prompt", "--profile", "foo", "bar"]);
});
it("does not eat the value of --approval-mode (regression: PR #1435 review)", () => {
// `--approval-mode` is a string-valued flag in args.ts (`args[++i]` with
// no `-` check). The pre-parser must mirror that contract or
// `omp --approval-mode --profile foo` silently activates profile `foo`
// instead of letting the launch parser surface the invalid mode value.
const result = extractProfileFlags(["--approval-mode", "--profile", "foo", "bar"]);
expect(result.profile).toBeUndefined();
expect(result.argv).toEqual(["--approval-mode", "--profile", "foo", "bar"]);
});
it("still extracts --profile after an unrelated string-valued flag", () => {
// Mirror image: when the user does mean to activate a profile *after*
// a string-valued flag, we must skip past the flag's value but still
// pick up the trailing `--profile`.
const result = extractProfileFlags(["--system-prompt", "hello", "--profile", "work"]);
expect(result.profile).toBe("work");
expect(result.argv).toEqual(["--system-prompt", "hello"]);
});
it("treats optional-value flags as consuming the next token only when it doesn't look like a flag", () => {
// `--resume <id>` consumes the id, `--resume` alone is a picker.
const consumed = extractProfileFlags(["--resume", "abc123", "--profile", "work"]);
expect(consumed.argv).toEqual(["--resume", "abc123"]);
expect(consumed.profile).toBe("work");
const picker = extractProfileFlags(["--resume", "--profile", "work"]);
expect(picker.argv).toEqual(["--resume"]);
expect(picker.profile).toBe("work");
// `--list-models` mirrors args.ts and does not consume `@`-prefixed
// tokens (they're file args); the pre-pass releases them and the
// trailing `--profile work` still activates.
const filePrefixed = extractProfileFlags(["--list-models", "@models.txt", "--profile", "work"]);
expect(filePrefixed.argv).toEqual(["--list-models", "@models.txt"]);
expect(filePrefixed.profile).toBe("work");
});
it("does not consume empty-string resume values before a trailing profile", () => {
// Shared OPTIONAL_FLAGS metadata drives the bootstrap too. Empty string is
// "no value" for resume/session aliases, so the bootstrap must release it
// and still activate the trailing --profile.
const result = extractProfileFlags(["--resume", "", "--profile", "work"]);
expect(result.argv).toEqual(["--resume", ""]);
expect(result.profile).toBe("work");
});
it("honors `--` and stops scanning for flags", () => {
const result = extractProfileFlags(["--", "--profile", "foo", "--alias", "bar"]);
expect(result.profile).toBeUndefined();
expect(result.aliasName).toBeUndefined();
expect(result.argv).toEqual(["--", "--profile", "foo", "--alias", "bar"]);
});
it("rejects --profile without a value", () => {
expect(() => extractProfileFlags(["--profile"])).toThrow("--profile requires a profile name");
expect(() => extractProfileFlags(["--profile", "--version"])).toThrow("--profile requires a profile name");
expect(() => extractProfileFlags(["--profile="])).toThrow("--profile requires a profile name");
});
it("rejects --alias without a value", () => {
expect(() => extractProfileFlags(["--alias"])).toThrow("--alias requires a command name");
expect(() => extractProfileFlags(["--alias", "--profile"])).toThrow("--alias requires a command name");
expect(() => extractProfileFlags(["--alias="])).toThrow("--alias requires a command name");
});
it("stops extracting global flags at a subcommand boundary", () => {
// `omp grep --profile <path>` must reach the grep subcommand intact; the
// bootstrap must not treat `--profile <path>` as a profile selection.
const result = extractProfileFlags(["grep", "--profile", "packages/coding-agent/src/cli.ts"]);
expect(result.profile).toBeUndefined();
expect(result.argv).toEqual(["grep", "--profile", "packages/coding-agent/src/cli.ts"]);
});
it("extracts a global --profile that precedes a subcommand", () => {
const result = extractProfileFlags(["--profile", "work", "grep", "foo"]);
expect(result.profile).toBe("work");
expect(result.argv).toEqual(["grep", "foo"]);
});
it("still extracts --profile after a non-subcommand positional (launch message)", () => {
const result = extractProfileFlags(["hello", "--profile", "work"]);
expect(result.profile).toBe("work");
expect(result.argv).toEqual(["hello"]);
});
it("does not treat a --profile value that names a subcommand as a boundary", () => {
const result = extractProfileFlags(["--profile", "config", "later"]);
expect(result.profile).toBe("config");
expect(result.argv).toEqual(["later"]);
});
});