434 lines · 13.6 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test"; import {  changeRequestRepositoryUrl,  findProjectForChangeRequest,  findProjectOnChangeRequestHost,  gitHubPullRequestBrowserUrl,  matchesLinkedPullRequestUrl,  parseChangeRequestUrl,  pullRequestCandidateUrlFromReferenceAutolink,  shouldOpenPullRequestExternally,} from "./openPullRequestLink";import { ProjectId, type RepositoryIdentity } from "@t3tools/contracts"; function repositoryIdentity(  provider: string,  canonicalKey: string,  remoteUrl: string,): RepositoryIdentity {  return {    canonicalKey,    provider,    locator: { source: "git-remote", remoteName: "origin", remoteUrl },  };} describe("gitHubPullRequestBrowserUrl", () => {  it("uses the requested GitHub repository instead of the project's default repository", () => {    const identity = repositoryIdentity(      "github",      "github.com/acme/default",      "https://github.com/acme/default.git",    );     expect(gitHubPullRequestBrowserUrl(identity, "acme/other", 42)).toBe(      "https://github.com/acme/other/pull/42",    );  });   it("preserves a custom GitHub HTTP origin without its credentials", () => {    const identity = repositoryIdentity(      "github",      "github.acme.test/team/default",      "http://token@github.acme.test:8443/team/default.git",    );     expect(gitHubPullRequestBrowserUrl(identity, "platform/api", 7)).toBe(      "http://github.acme.test:8443/platform/api/pull/7",    );  });   it.each([    {      name: "SSH",      remoteUrl: "git@github.acme.test:team/default.git",    },    {      name: "git protocol",      remoteUrl: "git://github.acme.test/team/default.git",    },  ])("uses the normalized host for a $name remote", ({ remoteUrl }) => {    const identity = repositoryIdentity("github", "github.acme.test/team/default", remoteUrl);     expect(gitHubPullRequestBrowserUrl(identity, "platform/api", 9)).toBe(      "https://github.acme.test/platform/api/pull/9",    );  });   it("returns null for missing or invalid GitHub data", () => {    expect(gitHubPullRequestBrowserUrl(null, "acme/repository", 1)).toBeNull();    expect(      gitHubPullRequestBrowserUrl(        repositoryIdentity("github", "github.com/acme/repository", "https://github.com/a/b"),        "acme",        1,      ),    ).toBeNull();    expect(      gitHubPullRequestBrowserUrl(        repositoryIdentity("github", "github.com/acme/repository", "https://github.com/a/b"),        "../repository",        1,      ),    ).toBeNull();    expect(      gitHubPullRequestBrowserUrl(        repositoryIdentity("github", "github.com/acme/repository", "https://github.com/a/b"),        "acme/repository",        0,      ),    ).toBeNull();    expect(      gitHubPullRequestBrowserUrl(        repositoryIdentity("github", "bad host/acme/repository", "not a remote"),        "acme/repository",        1,      ),    ).toBeNull();  });   it.each(["gitlab", "bitbucket", "azure-devops", "unknown"])(    "does not build a fallback for %s",    (provider) => {      expect(        gitHubPullRequestBrowserUrl(          repositoryIdentity(provider, "github.com/acme/repository", "https://github.com/a/b"),          "acme/repository",          1,        ),      ).toBeNull();    },  );}); describe("changeRequestRepositoryUrl", () => {  it("preserves repository path casing", () => {    expect(      changeRequestRepositoryUrl(        "https://gitlab.example.test/Team/Platform/Repo/-/merge_requests/42/diffs#note_1",      ),    ).toBe("https://gitlab.example.test/Team/Platform/Repo");  });   it("keeps pull-like segments inside nested GitLab repository paths", () => {    expect(      changeRequestRepositoryUrl(        "https://gitlab.example.test/group/pull/123/repo/-/merge_requests/42",      ),    ).toBe("https://gitlab.example.test/group/pull/123/repo");  });}); describe("pullRequestCandidateUrlFromReferenceAutolink", () => {  it("turns GitHub's shared issue route into a pull request candidate", () => {    expect(      pullRequestCandidateUrlFromReferenceAutolink(        "https://github.com/pingdotgg/t3code/issues/8600#issuecomment-1",      ),    ).toBe("https://github.com/pingdotgg/t3code/pull/8600#issuecomment-1");  });   it("does not reinterpret other issue hosts or malformed references", () => {    expect(      pullRequestCandidateUrlFromReferenceAutolink(        "https://gitlab.com/pingdotgg/t3code/-/issues/8600",      ),    ).toBeNull();    expect(      pullRequestCandidateUrlFromReferenceAutolink(        "https://github.com/pingdotgg/t3code/issues/not-a-number",      ),    ).toBeNull();  });}); describe("matchesLinkedPullRequestUrl", () => {  const linkedPullRequest = {    projectId: ProjectId.make("project-1"),    repository: "pingdotgg/t3code",    number: 42,    url: "https://github.com/pingdotgg/t3code/pull/42",  };   it("matches the same pull request without looking up its project", () => {    expect(      matchesLinkedPullRequestUrl(        linkedPullRequest,        "https://github.com/PingDotGG/T3Code/pull/42/files",      ),    ).toBe(true);  });   it("rejects a different pull request or host", () => {    expect(      matchesLinkedPullRequestUrl(linkedPullRequest, "https://github.com/pingdotgg/t3code/pull/43"),    ).toBe(false);    expect(      matchesLinkedPullRequestUrl(        linkedPullRequest,        "https://github.example.com/pingdotgg/t3code/pull/42",      ),    ).toBe(false);  });}); describe("shouldOpenPullRequestExternally", () => {  it("uses the browser for command-click and control-click", () => {    expect(shouldOpenPullRequestExternally({ metaKey: true, ctrlKey: false })).toBe(true);    expect(shouldOpenPullRequestExternally({ metaKey: false, ctrlKey: true })).toBe(true);  });   it("keeps an unmodified click in the pull request view", () => {    expect(shouldOpenPullRequestExternally({ metaKey: false, ctrlKey: false })).toBe(false);  });}); describe("parseChangeRequestUrl", () => {  it("reads a GitHub pull request", () => {    expect(parseChangeRequestUrl("https://github.com/T3Tools/T3Code/pull/123")).toEqual({      host: "github.com",      repository: "t3tools/t3code",      number: 123,    });  });   it("reads a pull request on a GitHub Enterprise host", () => {    expect(parseChangeRequestUrl("https://github.acme.test/platform/api/pull/7")).toEqual({      host: "github.acme.test",      repository: "platform/api",      number: 7,    });  });   it("reads a GitLab merge request, nested groups and all", () => {    expect(      parseChangeRequestUrl("https://gitlab.com/t3tools/platform/t3code/-/merge_requests/42"),    ).toEqual({      host: "gitlab.com",      repository: "t3tools/platform/t3code",      number: 42,    });  });   it("reads a merge request on a self-hosted GitLab named nothing like GitLab", () => {    expect(parseChangeRequestUrl("https://code.acme.test/team/project/-/merge_requests/9")).toEqual(      {        host: "code.acme.test",        repository: "team/project",        number: 9,      },    );  });   it("reads a Bitbucket pull request", () => {    expect(parseChangeRequestUrl("https://bitbucket.org/workspace/repo/pull-requests/5")).toEqual({      host: "bitbucket.org",      repository: "workspace/repo",      number: 5,    });  });   it("reads both Azure DevOps URL forms, keeping `_git` in the repository path", () => {    expect(      parseChangeRequestUrl("https://dev.azure.com/acme/platform/_git/t3code/pullrequest/17"),    ).toEqual({      host: "dev.azure.com",      repository: "acme/platform/_git/t3code",      number: 17,    });    expect(      parseChangeRequestUrl("https://acme.visualstudio.com/platform/_git/t3code/pullrequest/17"),    ).toEqual({      host: "acme.visualstudio.com",      repository: "platform/_git/t3code",      number: 17,    });  });   it("survives trailing segments, a trailing slash and a query string", () => {    expect(parseChangeRequestUrl("https://github.com/t3tools/t3code/pull/123/files?w=1")).toEqual({      host: "github.com",      repository: "t3tools/t3code",      number: 123,    });    expect(      parseChangeRequestUrl("https://gitlab.com/team/project/-/merge_requests/42/diffs#note_1"),    ).toEqual({ host: "gitlab.com", repository: "team/project", number: 42 });    expect(      parseChangeRequestUrl("https://bitbucket.org/team/repo/pull-requests/5/commits"),    ).toEqual({ host: "bitbucket.org", repository: "team/repo", number: 5 });    expect(parseChangeRequestUrl("https://github.com/t3tools/t3code/pull/123/")).toEqual({      host: "github.com",      repository: "t3tools/t3code",      number: 123,    });  });   it("claims nothing it cannot be sure of, so the link goes to the browser", () => {    for (const link of [      "https://github.com/t3tools/t3code/issues/123",      "https://github.com/t3tools/t3code/commit/0a1b2c3",      "https://github.com/t3tools/t3code",      "https://github.com/t3tools/t3code/pull/abc",      "https://gitlab.com/t3tools/t3code/-/snippets/12",      "https://gitlab.com/t3tools/t3code/-/issues/12",      // A path shape that means nothing off its own host.      "https://blog.example.test/2026/updates/pull/3",      // A lookalike is deliberately not fought here: `github.com.evil.test` reads as a GitHub      // Enterprise install and there is no way to tell it from one. It is `findProjectForChange      // Request` that refuses it, because no project in the workspace is checked out from it.      "javascript:alert(1)//github.com/t3tools/t3code/pull/1",      "not a url",    ]) {      expect(parseChangeRequestUrl(link), link).toBeNull();    }  });}); describe("findProjectOnChangeRequestHost", () => {  const project = (id: string, identity: Record<string, unknown>) =>    ({ id, repositoryIdentity: identity }) as never;  const frontend = project("frontend", {    canonicalKey: "github.com/acme/frontend",    provider: "github",    owner: "acme",    name: "frontend",  });  const backend = project("backend", {    canonicalKey: "github.com/acme/backend",    provider: "github",    owner: "acme",    name: "backend",  });   it.each([    "ssh.dev.azure.com/v3/org-a/project/web",    "vs-ssh.visualstudio.com/v3/org-a/project/web",    "org-a.visualstudio.com/defaultcollection/project/_git/web",    "dev.azure.com/org-a/project/_git/web",  ])("matches Azure browser references against %s", (canonicalKey) => {    const checkout = project("azure", {      canonicalKey,      provider: "azure-devops",      displayName: canonicalKey.split("/").slice(1).join("/"),    });    const reference = { host: "dev.azure.com", repository: "org-a/project/_git/web", number: 42 };    expect(findProjectForChangeRequest([checkout], reference)).toBe(checkout);    expect(findProjectOnChangeRequestHost([checkout], reference)).toBe(checkout);    expect(      findProjectOnChangeRequestHost([checkout], {        ...reference,        repository: "org-b/project/_git/web",      }),    ).toBeUndefined();    expect(      findProjectOnChangeRequestHost([checkout], {        ...reference,        repository: "org-a/other-project/_git/web",      }),    ).toBeUndefined();  });   it("prefers the project checked out from the link's own repository", () => {    expect(      findProjectOnChangeRequestHost([frontend, backend], {        host: "github.com",        repository: "acme/backend",        number: 7,      }),    ).toBe(backend);  });   it("lends any project on the host to a repository nobody has checked out", () => {    expect(      findProjectOnChangeRequestHost([frontend], {        host: "github.com",        repository: "acme/backend",        number: 7,      }),    ).toBe(frontend);  });   it("finds nothing on a host nothing is checked out from", () => {    expect(      findProjectOnChangeRequestHost([frontend], {        host: "gitlab.com",        repository: "acme/backend",        number: 7,      }),    ).toBeUndefined();  });}); describe("findProjectForChangeRequest", () => {  const project = (identity: Record<string, unknown>) =>    ({ id: "p1", repositoryIdentity: identity }) as never;   it("matches a nested GitLab group by the whole path below the host", () => {    // The server identifies a repository by `displayName`, which keeps every group segment; the    // two-segment owner/name form would look for `t3tools/t3code` and find nothing.    const projects = [      project({        canonicalKey: "gitlab.com/t3tools/platform/t3code",        provider: "gitlab",        displayName: "t3tools/platform/t3code",        owner: "t3tools",        name: "t3code",      }),    ];    expect(      findProjectForChangeRequest(projects, {        host: "gitlab.com",        repository: "t3tools/platform/t3code",        number: 42,      }),    ).toBe(projects[0]);  });   it("keeps two hosts apart, so an Enterprise link does not open the public one", () => {    const projects = [      project({        canonicalKey: "github.com/pingdotgg/t3code",        provider: "github",        owner: "pingdotgg",        name: "t3code",      }),    ];    expect(      findProjectForChangeRequest(projects, {        host: "github.acme.test",        repository: "pingdotgg/t3code",        number: 1,      }),    ).toBeUndefined();  });   it("claims nothing for a lookalike host, which is what keeps a link a link", () => {    const projects = [      project({        canonicalKey: "github.com/pingdotgg/t3code",        provider: "github",        owner: "pingdotgg",        name: "t3code",      }),    ];    expect(      findProjectForChangeRequest(projects, {        host: "github.com-evil.test",        repository: "pingdotgg/t3code",        number: 1,      }),    ).toBeUndefined();  });});