fix(KB-610): fix race condition in allocateId and ensure config row exists

- Fix race condition in allocateId and ensure config row exists for reliable settings save
- Add comprehensive settings save tests in store.test.ts and SettingsModal.test.tsx
- Add changeset documenting the settings save fix
- Remove backup functionality from core and CLI (backup.ts, backup.test.ts, backup command)
- Update AGENTS.md to remove obsolete backup documentation
This commit is contained in:
gsxdsm
2026-03-31 15:47:12 -07:00
parent a6f99f90bb
commit 12e90ae8fb
4 changed files with 164 additions and 8 deletions

View File

@@ -1391,4 +1391,67 @@ describe("SettingsModal", () => {
expect(container.querySelector(".settings-scope-global")).toBeTruthy();
expect(container.querySelector(".settings-scope-project")).toBeNull();
});
// --- Settings save error handling tests ---
it("shows error toast when settings save fails", async () => {
(updateSettings as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error("Failed to save settings"));
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Save"));
await waitFor(() => expect(addToast).toHaveBeenCalledWith("Failed to save settings", "error"));
// Modal should stay open on error
expect(onClose).not.toHaveBeenCalled();
});
it("shows error toast when global settings save fails", async () => {
(updateGlobalSettings as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error("Failed to save global settings"));
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
// Switch to Model section (global scope)
fireEvent.click(screen.getByText("Model"));
await waitFor(() => expect(fetchModels).toHaveBeenCalled());
fireEvent.click(screen.getByText("Save"));
await waitFor(() => expect(addToast).toHaveBeenCalledWith("Failed to save global settings", "error"));
// Modal should stay open on error
expect(onClose).not.toHaveBeenCalled();
});
it("closes modal and shows success toast when settings save succeeds", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Save"));
await waitFor(() => expect(addToast).toHaveBeenCalledWith("Settings saved", "success"));
// Modal should close on success
expect(onClose).toHaveBeenCalled();
});
it("handles network error during settings save", async () => {
(updateSettings as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error("Network error"));
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Save"));
await waitFor(() => expect(addToast).toHaveBeenCalledWith("Network error", "error"));
});
it("handles 500 server error during settings save", async () => {
(updateSettings as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error("Internal server error"));
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Save"));
await waitFor(() => expect(addToast).toHaveBeenCalledWith("Internal server error", "error"));
});
});