repository.test.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import { describe, expect, test } from "bun:test"
  2. import path from "path"
  3. import { pathToFileURL } from "url"
  4. import { Repository } from "@opencode-ai/core/repository"
  5. describe("Repository", () => {
  6. test("parses github shorthand and builds an explicit-root cache path", () => {
  7. const reference = Repository.parseRemote("owner/repo")
  8. expect(reference).toMatchObject({
  9. host: "github.com",
  10. path: "owner/repo",
  11. segments: ["owner", "repo"],
  12. owner: "owner",
  13. repo: "repo",
  14. remote: "https://github.com/owner/repo.git",
  15. label: "owner/repo",
  16. })
  17. expect(Repository.cachePath("/cache", reference)).toBe(path.join("/cache", "github.com", "owner", "repo"))
  18. expect(Repository.cachePath("/cache", reference, "main")).toBe(
  19. path.join("/cache", "github.com", "owner", "repo@main"),
  20. )
  21. expect(Repository.cachePath("/cache", reference, "feature/x")).toBe(
  22. path.join("/cache", "github.com", "owner", "repo@feature%2Fx"),
  23. )
  24. expect(Repository.cacheIdentity(reference)).toBe("github.com/owner/repo")
  25. })
  26. test("parses host path and scp remote references", () => {
  27. expect(Repository.parseRemote("gitlab.com/group/repo")).toMatchObject({
  28. host: "gitlab.com",
  29. path: "group/repo",
  30. remote: "https://gitlab.com/group/repo.git",
  31. label: "gitlab.com/group/repo",
  32. })
  33. expect(Repository.parseRemote("git@github.com:owner/repo.git")).toMatchObject({
  34. host: "github.com",
  35. path: "owner/repo",
  36. remote: "git@github.com:owner/repo.git",
  37. label: "owner/repo",
  38. })
  39. })
  40. test("keeps local file repositories distinct from remote repositories", () => {
  41. const localPath = path.resolve("repo.git")
  42. const reference = Repository.parse(pathToFileURL(localPath).href)
  43. expect(reference).toMatchObject({ host: "file", protocol: "file:", label: localPath })
  44. expect(reference && Repository.isFile(reference)).toBe(true)
  45. expect(reference && Repository.isRemote(reference)).toBe(false)
  46. expect(() => Repository.parseRemote(pathToFileURL(localPath).href)).toThrow(
  47. Repository.UnsupportedLocalRepositoryError,
  48. )
  49. })
  50. test("rejects unsafe remote references and branches with typed errors", () => {
  51. expect(() => Repository.parseRemote("not-a-repo")).toThrow(Repository.InvalidReferenceError)
  52. expect(() => Repository.parseRemote("git@github.com:../../../etc/passwd")).toThrow(Repository.InvalidReferenceError)
  53. expect(() => Repository.validateBranch("feature/docs.v1")).not.toThrow()
  54. expect(() => Repository.validateBranch("-bad")).toThrow(Repository.InvalidBranchError)
  55. expect(() => Repository.validateBranch("bad..branch")).toThrow(Repository.InvalidBranchError)
  56. expect(() => Repository.validateBranch("bad branch")).toThrow(Repository.InvalidBranchError)
  57. })
  58. test("compares cache identity independent of input spelling", () => {
  59. const shorthand = Repository.parseRemote("owner/repo")
  60. expect(Repository.same(shorthand, Repository.parseRemote("https://github.com/owner/repo.git"))).toBe(true)
  61. expect(Repository.same(shorthand, Repository.parseRemote("github.com/owner/repo"))).toBe(true)
  62. })
  63. })