feat: add main features
Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
131
frontend/src/features/first-run/profile.test.ts
Normal file
131
frontend/src/features/first-run/profile.test.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* L5 — pure first-run profile logic: validation mirrors of the backend invariants
|
||||
* (`validateProfile`/`isProfileValid`), `isRelativeSafe`, `isValidEnvVar`,
|
||||
* `defaultInjection`, `emptyCustomProfile`, and `parseArgs`.
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import type { AgentProfile } from "@/domain";
|
||||
import {
|
||||
defaultInjection,
|
||||
emptyCustomProfile,
|
||||
isProfileValid,
|
||||
isRelativeSafe,
|
||||
isValidEnvVar,
|
||||
parseArgs,
|
||||
validateProfile,
|
||||
} from "./profile";
|
||||
|
||||
function base(overrides: Partial<AgentProfile> = {}): AgentProfile {
|
||||
return {
|
||||
id: "p1",
|
||||
name: "Claude",
|
||||
command: "claude",
|
||||
args: [],
|
||||
contextInjection: { strategy: "conventionFile", target: "CLAUDE.md" },
|
||||
detect: null,
|
||||
cwdTemplate: "{projectRoot}",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("isRelativeSafe", () => {
|
||||
it("accepts a plain relative file name", () => {
|
||||
expect(isRelativeSafe("CLAUDE.md")).toBe(true);
|
||||
expect(isRelativeSafe("docs/AGENTS.md")).toBe(true);
|
||||
});
|
||||
it("rejects empty, absolute, traversal and drive paths", () => {
|
||||
expect(isRelativeSafe("")).toBe(false);
|
||||
expect(isRelativeSafe("/etc/passwd")).toBe(false);
|
||||
expect(isRelativeSafe("\\windows")).toBe(false);
|
||||
expect(isRelativeSafe("../secret")).toBe(false);
|
||||
expect(isRelativeSafe("a/../b")).toBe(false);
|
||||
expect(isRelativeSafe("C:/tmp")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isValidEnvVar", () => {
|
||||
it("accepts valid identifiers", () => {
|
||||
expect(isValidEnvVar("AGENT_CONTEXT")).toBe(true);
|
||||
expect(isValidEnvVar("_x1")).toBe(true);
|
||||
});
|
||||
it("rejects invalid identifiers", () => {
|
||||
expect(isValidEnvVar("")).toBe(false);
|
||||
expect(isValidEnvVar("1ABC")).toBe(false);
|
||||
expect(isValidEnvVar("HAS-DASH")).toBe(false);
|
||||
expect(isValidEnvVar("has space")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateProfile / isProfileValid", () => {
|
||||
it("a well-formed profile is valid", () => {
|
||||
expect(validateProfile(base())).toEqual({});
|
||||
expect(isProfileValid(base())).toBe(true);
|
||||
});
|
||||
it("blank name and command are reported", () => {
|
||||
const errors = validateProfile(base({ name: " ", command: "" }));
|
||||
expect(errors.name).toBeDefined();
|
||||
expect(errors.command).toBeDefined();
|
||||
expect(isProfileValid(base({ name: "" }))).toBe(false);
|
||||
});
|
||||
it("convention file target must be relative-safe", () => {
|
||||
const errors = validateProfile(
|
||||
base({ contextInjection: { strategy: "conventionFile", target: "../x" } }),
|
||||
);
|
||||
expect(errors.target).toBeDefined();
|
||||
});
|
||||
it("flag must be non-empty", () => {
|
||||
const errors = validateProfile(
|
||||
base({ contextInjection: { strategy: "flag", flag: " " } }),
|
||||
);
|
||||
expect(errors.flag).toBeDefined();
|
||||
});
|
||||
it("env var must be a valid identifier", () => {
|
||||
const errors = validateProfile(
|
||||
base({ contextInjection: { strategy: "env", var: "1bad" } }),
|
||||
);
|
||||
expect(errors.var).toBeDefined();
|
||||
});
|
||||
it("stdin needs no extra field", () => {
|
||||
expect(
|
||||
isProfileValid(base({ contextInjection: { strategy: "stdin" } })),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultInjection", () => {
|
||||
it("produces a sensible default per strategy", () => {
|
||||
expect(defaultInjection("conventionFile")).toEqual({
|
||||
strategy: "conventionFile",
|
||||
target: "CONTEXT.md",
|
||||
});
|
||||
expect(defaultInjection("flag")).toEqual({
|
||||
strategy: "flag",
|
||||
flag: "--context-file {path}",
|
||||
});
|
||||
expect(defaultInjection("env")).toEqual({
|
||||
strategy: "env",
|
||||
var: "AGENT_CONTEXT_FILE",
|
||||
});
|
||||
expect(defaultInjection("stdin")).toEqual({ strategy: "stdin" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("emptyCustomProfile", () => {
|
||||
it("is a blank, invalid draft with a fresh id", () => {
|
||||
const a = emptyCustomProfile();
|
||||
const b = emptyCustomProfile();
|
||||
expect(a.name).toBe("");
|
||||
expect(a.command).toBe("");
|
||||
expect(isProfileValid(a)).toBe(false);
|
||||
expect(a.id).not.toEqual(b.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseArgs", () => {
|
||||
it("splits on whitespace, trims and drops empties", () => {
|
||||
expect(parseArgs(" --foo bar\t baz ")).toEqual(["--foo", "bar", "baz"]);
|
||||
expect(parseArgs("")).toEqual([]);
|
||||
expect(parseArgs(" ")).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user