/** * @file Tests for CloneSourcesForm: every URL in the box is cloned in turn as * an extra folder, a host that wants a login opens the login row, and Retry * re-runs only the failed URLs with that login. An SSH link while the * account has no key is never sent — the user is pointed at Settings → Git — * and an HTTPS host with no saved login is flagged before Clone. The * partial-clone box adds `partial: true` to every clone. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import "../../../i18n"; vi.mock("../../../lib/api", () => ({ api: { lanes: { cloneFolder: vi.fn(), sources: vi.fn(), cloneJobs: vi.fn() }, gitIdentity: { get: vi.fn() } }, })); import { api, type GitIdentity, type LaneSourcesView } from "../../../lib/api"; import { CloneSourcesForm, cloneTarget } from "../CloneSourcesForm"; import { CLONE_JOB_EVENT, resetCloneJobsForTests } from "../../../lib/cloneJobs"; const view = { sources: [] } as unknown as LaneSourcesView & { path: string }; describe("CloneSourcesForm", () => { beforeEach(() => { vi.mocked(api.lanes.cloneFolder).mockReset(); // A finished clone (an older server's answer, or a job the store saw end) // makes the form read the folder list again. vi.mocked(api.lanes.sources).mockReset().mockResolvedValue(view); vi.mocked(api.lanes.cloneJobs).mockReset().mockResolvedValue({ jobs: [] }); resetCloneJobsForTests(); // Unknown identity: nothing is checked up front. vi.mocked(api.gitIdentity.get).mockReset().mockRejectedValue(new Error("old server")); }); it("clones each URL, asks for a login where needed, and retries only that one", async () => { const user = userEvent.setup(); const onAdded = vi.fn(); vi.mocked(api.lanes.cloneFolder).mockImplementation(async (_id, body) => { if (body?.url.includes("private") && !body.credentials) throw Object.assign(new Error("login"), { code: "ECREDENTIALS", auth: { protocol: "https", host: "private.test" }, }); return view; }); render(); await user.type( screen.getByTestId("workspace-clone-input"), "https://h/a.git\nhttps://private.test/b.git\nhttps://h/a.git" ); await user.click(screen.getByTestId("workspace-clone-run")); await waitFor(() => expect(api.lanes.cloneFolder).toHaveBeenCalledTimes(2)); await waitFor(() => expect(onAdded).toHaveBeenCalledTimes(1)); expect(await screen.findByText(/private\.test needs a login/)).toBeInTheDocument(); // Only what is left stays in the box. expect(screen.getByTestId("workspace-clone-input")).toHaveValue("https://private.test/b.git"); await user.type(screen.getByLabelText("Username"), "dev"); await user.type(screen.getByLabelText("Password or access token"), "tok"); await user.click(screen.getByRole("button", { name: "Retry the failed ones" })); await waitFor(() => expect(onAdded).toHaveBeenCalledTimes(2)); expect(api.lanes.cloneFolder).toHaveBeenLastCalledWith(3, { background: true, url: "https://private.test/b.git", credentials: { username: "dev", password: "tok", remember: true }, }); expect(screen.getByTestId("workspace-clone-input")).toHaveValue(""); }); it("hands a background clone to the dock and re-reads the folders when it ends", async () => { const user = userEvent.setup(); const onAdded = vi.fn(); const job = { id: "s1", kind: "source", state: "queued", url: "https://h/a.git", name: "a", parent: "/", target: "/a", laneId: 3, position: 1, phase: null, label: null, percent: null, detail: null, error: null, result: null, createdAt: "", finishedAt: null, request: { url: "https://h/a.git", parent: "/", name: "a" }, } as const; vi.mocked(api.lanes.cloneFolder).mockResolvedValue({ job: { ...job } }); render(); await user.type(screen.getByTestId("workspace-clone-input"), "https://h/a.git"); await user.click(screen.getByTestId("workspace-clone-run")); expect(await screen.findByText(/progress is in the corner/)).toBeInTheDocument(); expect(screen.getByTestId("workspace-clone-input")).toHaveValue(""); expect(onAdded).not.toHaveBeenCalled(); // Another workspace's clone is not ours. window.dispatchEvent( new CustomEvent(CLONE_JOB_EVENT, { detail: { ...job, laneId: 4, state: "done" } }) ); window.dispatchEvent(new CustomEvent(CLONE_JOB_EVENT, { detail: { ...job, state: "done" } })); await waitFor(() => expect(onAdded).toHaveBeenCalledTimes(1)); expect(api.lanes.sources).toHaveBeenCalledWith(3); }); it("asks for partial clones when the box is ticked", async () => { const user = userEvent.setup(); vi.mocked(api.lanes.cloneFolder).mockResolvedValue(view); render(); await user.type(screen.getByTestId("workspace-clone-input"), "https://h/big.git"); await user.click(screen.getByTestId("workspace-clone-partial")); await user.click(screen.getByTestId("workspace-clone-run")); await waitFor(() => expect(api.lanes.cloneFolder).toHaveBeenCalledWith(3, { background: true, url: "https://h/big.git", partial: true, }) ); }); it("tells an SSH link from an HTTPS one", () => { expect(cloneTarget("git@github.com:o/r.git")).toEqual({ kind: "ssh", host: "github.com" }); expect(cloneTarget("ssh://git@h.test:2222/o/r")).toEqual({ kind: "ssh", host: "h.test" }); expect(cloneTarget("https://h.test/o/r.git")).toEqual({ kind: "https", host: "h.test" }); expect(cloneTarget("not a url")).toBeNull(); }); it("does not clone an SSH link before the account has a key, and flags an HTTPS host with no login", async () => { const user = userEvent.setup(); vi.mocked(api.gitIdentity.get).mockResolvedValue({ ssh: null, sshKeys: [], https: [], } as unknown as GitIdentity); vi.mocked(api.lanes.cloneFolder).mockResolvedValue(view); render(); await waitFor(() => expect(api.gitIdentity.get).toHaveBeenCalled()); await user.type( screen.getByTestId("workspace-clone-input"), "git@github.com:o/r.git\nhttps://h.test/a.git" ); expect(screen.getByTestId("workspace-clone-need-ssh")).toHaveTextContent("github.com"); expect(screen.getByTestId("workspace-clone-need-login")).toHaveTextContent("h.test"); await user.click(screen.getByTestId("workspace-clone-run")); await waitFor(() => expect(api.lanes.cloneFolder).toHaveBeenCalledTimes(1)); expect(api.lanes.cloneFolder).toHaveBeenCalledWith(3, { url: "https://h.test/a.git", background: true, }); expect(await screen.findByText(/has no SSH key yet/)).toBeInTheDocument(); await waitFor(() => expect(screen.getByTestId("workspace-clone-input")).toHaveValue("git@github.com:o/r.git") ); }); });