Secret Scan / scan (push) Successful in 14s
Build App (Preview) / compute-version (pull_request) Successful in 7s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 2m43s
Build App (Preview) / build-windows (pull_request) Successful in 4m59s
Build App (Preview) / build-linux (pull_request) Successful in 7m29s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
A rejected import (bad env var name, disallowed host path) used to leave keychain secrets already overwritten while the settings themselves stayed unchanged. apply_settings_import now runs update_settings's validation (extracted into validate_settings_update) before any secret write. Also from this review round: sharpened two format-version tests that previously passed against the pre-fix code too, added a direct test for split_settings_and_secrets, warned on a dormant web terminal token even when the terminal import leaves it off, matched the password-length check to the frontend's unit of measure, zeroized the export plaintext buffer, and surfaced non-blank Ollama/llama.cpp/OpenAI-compatible/gateway base URLs in the import preview so a traffic redirect isn't silent.
108 lines
4.8 KiB
TypeScript
108 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import ImportSettingsModal from "./ImportSettingsModal";
|
|
import type { AppSettings, SettingsImportPreview } from "../../lib/types";
|
|
|
|
const previewSettingsImport = vi.fn();
|
|
const applySettingsImport = vi.fn();
|
|
|
|
vi.mock("../../lib/tauri-commands", () => ({
|
|
previewSettingsImport: (password: string) => previewSettingsImport(password),
|
|
applySettingsImport: (password: string) => applySettingsImport(password),
|
|
}));
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const samplePreview: SettingsImportPreview = {
|
|
exported_at: "2026-08-27T00:00:00Z",
|
|
app_version: "0.4.14",
|
|
custom_env_var_count: 2,
|
|
gateway_model_count: 0,
|
|
has_claude_code_settings: false,
|
|
has_claude_oauth_token: true,
|
|
has_gateway_api_key: false,
|
|
has_gateway_master_key: false,
|
|
has_web_terminal_access_token: false,
|
|
enables_web_terminal: false,
|
|
ollama_base_url: null,
|
|
llamacpp_base_url: null,
|
|
openai_compatible_base_url: null,
|
|
gateway_api_base: null,
|
|
};
|
|
|
|
describe("ImportSettingsModal", () => {
|
|
it("keeps 'Choose file' disabled until a password is entered", () => {
|
|
render(<ImportSettingsModal onClose={vi.fn()} onImported={vi.fn()} />);
|
|
expect(screen.getByRole("button", { name: /choose file/i })).toBeDisabled();
|
|
|
|
fireEvent.change(screen.getByLabelText("Password"), { target: { value: "hunter2" } });
|
|
expect(screen.getByRole("button", { name: /choose file/i })).not.toBeDisabled();
|
|
});
|
|
|
|
it("shows the preview and confirms with the same password used to open it", async () => {
|
|
previewSettingsImport.mockResolvedValue(samplePreview);
|
|
applySettingsImport.mockResolvedValue({} as AppSettings);
|
|
const onImported = vi.fn();
|
|
render(<ImportSettingsModal onClose={vi.fn()} onImported={onImported} />);
|
|
|
|
fireEvent.change(screen.getByLabelText("Password"), { target: { value: "hunter2" } });
|
|
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
|
|
|
|
await waitFor(() => expect(previewSettingsImport).toHaveBeenCalledWith("hunter2"));
|
|
expect(await screen.findByText(/2 global custom env vars/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/your shared claude login/i)).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /^import$/i }));
|
|
await waitFor(() => expect(applySettingsImport).toHaveBeenCalledWith("hunter2"));
|
|
await waitFor(() => expect(onImported).toHaveBeenCalledWith({}));
|
|
expect(await screen.findByText(/settings imported/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows a distinct warning when the import would enable the web terminal", async () => {
|
|
previewSettingsImport.mockResolvedValue({ ...samplePreview, enables_web_terminal: true });
|
|
render(<ImportSettingsModal onClose={vi.fn()} onImported={vi.fn()} />);
|
|
|
|
fireEvent.change(screen.getByLabelText("Password"), { target: { value: "hunter2" } });
|
|
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
|
|
|
|
expect(await screen.findByText(/enables the remote web terminal/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("closes quietly when the file picker is dismissed", async () => {
|
|
previewSettingsImport.mockResolvedValue(null);
|
|
const onClose = vi.fn();
|
|
render(<ImportSettingsModal onClose={onClose} onImported={vi.fn()} />);
|
|
|
|
fireEvent.change(screen.getByLabelText("Password"), { target: { value: "hunter2" } });
|
|
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
|
|
|
|
await waitFor(() => expect(onClose).toHaveBeenCalled());
|
|
});
|
|
|
|
it("shows an error when the password is wrong rather than a blank preview", async () => {
|
|
previewSettingsImport.mockRejectedValue("Wrong password, or the file is corrupted.");
|
|
render(<ImportSettingsModal onClose={vi.fn()} onImported={vi.fn()} />);
|
|
|
|
fireEvent.change(screen.getByLabelText("Password"), { target: { value: "wrong" } });
|
|
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
|
|
|
|
expect(await screen.findByText(/wrong password, or the file is corrupted/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows an error if applying the import fails, without claiming success", async () => {
|
|
previewSettingsImport.mockResolvedValue(samplePreview);
|
|
applySettingsImport.mockRejectedValue("Keychain write failed");
|
|
render(<ImportSettingsModal onClose={vi.fn()} onImported={vi.fn()} />);
|
|
|
|
fireEvent.change(screen.getByLabelText("Password"), { target: { value: "hunter2" } });
|
|
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
|
|
await screen.findByText(/2 global custom env vars/i);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /^import$/i }));
|
|
expect(await screen.findByText("Keychain write failed")).toBeInTheDocument();
|
|
expect(screen.queryByText(/settings imported/i)).not.toBeInTheDocument();
|
|
});
|
|
});
|