Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/rosettify/src/commands/plan/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ export function depsSatisfied(
// Validation Functions
// ---------------------------------------------------------------------------

export function validatePlanName(name: string): string | null {
if (!name || !name.trim()) return "size_limit_exceeded";
export function validatePlanName(name: unknown): string | null {
if (typeof name !== "string" || !name.trim()) return "size_limit_exceeded";
if (name.length > PLAN_MAX_NAME_LENGTH) return "size_limit_exceeded";
return null;
}
Expand Down Expand Up @@ -383,8 +383,8 @@ export function validateDependencies(plan: Plan): string | null {
export function validateSizeLimits(plan: Plan): string | null {
if ((plan.phases ?? []).length > PLAN_MAX_PHASES) return "size_limit_exceeded";

if (plan.name && plan.name.length > PLAN_MAX_NAME_LENGTH)
return "size_limit_exceeded";
const nameErr = validatePlanName(plan.name);
if (nameErr) return nameErr;

function checkStringLength(value: unknown): boolean {
if (typeof value === "string" && value.length > PLAN_MAX_STRING_LENGTH)
Expand Down
8 changes: 8 additions & 0 deletions src/rosettify/tests/unit/plan/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ describe("cmdCreate — FR-PLAN-0010 / FR-PLAN-0040", () => {
expect(tree.plan.name).toBe("Unnamed Plan");
});

it.each(["", " ", 123, {}])("rejects an invalid plan name %#", async (name) => {
const file = planFile();
const result = await cmdCreate(file, { name });
expect(result.ok).toBe(false);
expect(result.error).toBe("size_limit_exceeded");
expect(fs.existsSync(file)).toBe(false);
});

it("rejects plan with duplicate ids", async () => {
const file = planFile();
const data = {
Expand Down
8 changes: 8 additions & 0 deletions src/rosettify/tests/unit/plan/upsert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ describe("cmdUpsert — entire_plan on existing file (merge)", () => {
expect(plan.name).toBe("Test Plan"); // unchanged
});

it.each(["", " ", 123, {}])("rejects an invalid plan name %#", async (name) => {
const file = writePlan();
const result = await cmdUpsert(file, "entire_plan", { name });
expect(result.ok).toBe(false);
expect(result.error).toBe("size_limit_exceeded");
expect(loadPlan(file)!.name).toBe("Test Plan");
});

it("merges phases by id", async () => {
const file = writePlan();
const result = await cmdUpsert(file, "entire_plan", {
Expand Down
10 changes: 10 additions & 0 deletions src/rosettify/tests/unit/shared/core-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ describe("validatePlanName", () => {
expect(validatePlanName(" ")).toBe("size_limit_exceeded");
});

it.each([123, {}])("returns error for non-string name %#", (name) => {
expect(validatePlanName(name)).toBe("size_limit_exceeded");
});

it("returns error for name exceeding 256 chars", () => {
expect(validatePlanName("x".repeat(257))).toBe("size_limit_exceeded");
});
Expand Down Expand Up @@ -502,6 +506,12 @@ describe("validateSizeLimits", () => {
expect(validateSizeLimits(fullPlan())).toBeNull();
});

it.each(["", " "])("rejects an empty plan name %#", (name) => {
const plan = fullPlan();
plan.name = name;
expect(validateSizeLimits(plan)).toBe("size_limit_exceeded");
});

it("returns size_limit_exceeded for plan name > 256", () => {
const plan = fullPlan();
plan.name = "x".repeat(257);
Expand Down