fix(FN-1028): add crypto.randomUUID fallback for schedule step ID generation

- Add fallback ID generation using Math.random when crypto.randomUUID is unavailable
- Update ScheduleStepsEditor component with robust step creation
- Add unit tests for fallback ID generation behavior
- Document fallback approach in Scheduled Tasks section of README
This commit is contained in:
gsxdsm
2026-04-06 00:09:21 -07:00
parent 9de3bcd618
commit ba40c042d7
3 changed files with 56 additions and 2 deletions

View File

@@ -11,7 +11,14 @@ interface ScheduleStepsEditorProps {
}
function generateStepId(): string {
return crypto.randomUUID();
// crypto.randomUUID() may be unavailable in non-secure contexts (HTTP),
// older browsers, or some test environments. Fall back to a
// cryptographically-acceptable alternative when needed.
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
// Deterministic fallback: timestamp + random hex
return `step-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
}
function createEmptyStep(type: AutomationStepType): AutomationStep {

View File

@@ -256,4 +256,51 @@ describe("ScheduleStepsEditor", () => {
expect(screen.getByText("Steps (2)")).toBeDefined();
});
});
describe("ID generation fallback", () => {
it("adds steps when crypto.randomUUID is unavailable", () => {
// Remove crypto.randomUUID to simulate non-secure context
vi.stubGlobal("crypto", {});
render(<ScheduleStepsEditor steps={[]} onChange={onChange} />);
fireEvent.click(screen.getByText("Add Command Step"));
expect(onChange).toHaveBeenCalledTimes(1);
const newSteps = onChange.mock.calls[0][0] as AutomationStep[];
expect(newSteps).toHaveLength(1);
expect(newSteps[0].type).toBe("command");
expect(newSteps[0].id).toBeTruthy();
});
it("adds AI prompt steps when crypto.randomUUID is unavailable", () => {
vi.stubGlobal("crypto", {});
render(<ScheduleStepsEditor steps={[]} onChange={onChange} />);
fireEvent.click(screen.getByText("Add AI Prompt Step"));
expect(onChange).toHaveBeenCalledTimes(1);
const newSteps = onChange.mock.calls[0][0] as AutomationStep[];
expect(newSteps).toHaveLength(1);
expect(newSteps[0].type).toBe("ai-prompt");
expect(newSteps[0].id).toBeTruthy();
});
it("adds steps when crypto is entirely undefined", () => {
vi.stubGlobal("crypto", undefined);
render(<ScheduleStepsEditor steps={[]} onChange={onChange} />);
fireEvent.click(screen.getByText("Add Command Step"));
expect(onChange).toHaveBeenCalledTimes(1);
const newSteps = onChange.mock.calls[0][0] as AutomationStep[];
expect(newSteps).toHaveLength(1);
expect(newSteps[0].id).toMatch(/^step-/);
});
it("enters edit mode for new step using fallback IDs", () => {
vi.stubGlobal("crypto", {});
function StatefulEditor() {
const [steps, setSteps] = useState<AutomationStep[]>([]);
return <ScheduleStepsEditor steps={steps} onChange={setSteps} />;
}
render(<StatefulEditor />);
fireEvent.click(screen.getByText("Add Command Step"));
// Editor should open immediately for the new step
expect(screen.getByText("Save Step")).toBeDefined();
});
});
});