chore: add tests

This commit is contained in:
Julien Calixte
2026-06-06 22:13:14 +02:00
parent 8a8509a0f3
commit 1a2d8f4ebf
23 changed files with 1943 additions and 3 deletions

View File

@@ -0,0 +1,123 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
vi.mock("./octo", () => ({
getOctokit: vi.fn(),
runWithAuthRetry: vi.fn()
}))
// Stub heavyweight transitive imports so loading repo.ts doesn't spin up the
// data Web Worker or the full markdown rendering stack.
vi.mock("@/hooks/useMarkdown.hook", () => ({
markdownBuilder: () => ({ render: (s: string) => s })
}))
vi.mock("@/modules/note/cache/prepareNoteCache", () => ({
prepareNoteCache: () => ({
getCachedNote: async () => ({ note: null }),
saveCacheNote: vi.fn()
})
}))
import { getOctokit, runWithAuthRetry } from "./octo"
import { getFiles, queryFileContent } from "./repo"
const makeOctokitWithRequest = (impl: (route: string, params: unknown) => unknown) => ({
request: vi.fn(impl)
})
describe("getFiles", () => {
beforeEach(() => {
vi.mocked(getOctokit).mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
})
it("returns empty array when owner is missing", async () => {
expect(await getFiles("", "repo")).toEqual([])
expect(getOctokit).not.toHaveBeenCalled()
})
it("returns empty array when repo is missing", async () => {
expect(await getFiles("owner", "")).toEqual([])
expect(getOctokit).not.toHaveBeenCalled()
})
it("returns empty array when there are no commits", async () => {
const octokit = makeOctokitWithRequest((route) => {
if (route === "GET /repos/{owner}/{repo}/commits") {
return { data: [] }
}
throw new Error("unexpected route " + route)
})
vi.mocked(getOctokit).mockResolvedValue(octokit as never)
expect(await getFiles("owner", "repo")).toEqual([])
})
it("fetches the tree from the latest commit and filters out non-blob entries", async () => {
const octokit = makeOctokitWithRequest((route, params) => {
if (route === "GET /repos/{owner}/{repo}/commits") {
return {
data: [{ commit: { tree: { sha: "TREE_SHA" } } }]
}
}
if (route === "GET /repos/{owner}/{repo}/git/trees/{tree_sha}") {
expect((params as { tree_sha: string }).tree_sha).toBe("TREE_SHA")
expect((params as { recursive: string }).recursive).toBe("true")
return {
data: {
tree: [
{ path: "README.md", type: "blob", sha: "a" },
{ path: "src", type: "tree", sha: "b" },
{ path: "src/note.md", type: "blob", sha: "c" }
]
}
}
}
throw new Error("unexpected route " + route)
})
vi.mocked(getOctokit).mockResolvedValue(octokit as never)
const files = await getFiles("owner", "repo")
expect(files.map((f) => f.path)).toEqual(["README.md", "src/note.md"])
})
})
describe("queryFileContent", () => {
beforeEach(() => {
vi.mocked(runWithAuthRetry).mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
})
it("returns null when owner or repo is missing", async () => {
expect(await queryFileContent("", "repo", "sha")).toBeNull()
expect(await queryFileContent("owner", "", "sha")).toBeNull()
expect(runWithAuthRetry).not.toHaveBeenCalled()
})
it("returns the blob content via runWithAuthRetry", async () => {
vi.mocked(runWithAuthRetry).mockImplementation(async (call) => {
const octokit = {
request: vi.fn().mockResolvedValue({
data: { content: "BASE64" }
})
}
return call(octokit as never)
})
expect(await queryFileContent("owner", "repo", "SHA")).toBe("BASE64")
})
it("returns null and swallows errors when the call fails", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
vi.mocked(runWithAuthRetry).mockRejectedValue(new Error("boom"))
expect(await queryFileContent("owner", "repo", "SHA")).toBeNull()
expect(warn).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,97 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
vi.mock("@/modules/user/service/signIn", () => ({
getAccessToken: vi.fn(),
refreshToken: vi.fn()
}))
import {
getAccessToken,
refreshToken
} from "@/modules/user/service/signIn"
import { runWithAuthRetry } from "./octo"
const unauthorized = () => Object.assign(new Error("Bad credentials"), { status: 401 })
const notFound = () => Object.assign(new Error("Not found"), { status: 404 })
describe("runWithAuthRetry", () => {
beforeEach(() => {
vi.mocked(getAccessToken).mockResolvedValue({
token: "t1"
} as Awaited<ReturnType<typeof getAccessToken>>)
vi.mocked(refreshToken).mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
})
it("returns the call result on success without refreshing", async () => {
const call = vi.fn().mockResolvedValue("ok")
const result = await runWithAuthRetry(call)
expect(result).toBe("ok")
expect(call).toHaveBeenCalledTimes(1)
expect(refreshToken).not.toHaveBeenCalled()
})
it("rethrows non-401 errors immediately without refreshing", async () => {
const err = notFound()
const call = vi.fn().mockRejectedValue(err)
await expect(runWithAuthRetry(call)).rejects.toBe(err)
expect(refreshToken).not.toHaveBeenCalled()
expect(call).toHaveBeenCalledTimes(1)
})
it("refreshes and retries once on 401, returning the retry result", async () => {
vi.mocked(refreshToken).mockResolvedValue({
token: "t2"
} as Awaited<ReturnType<typeof refreshToken>>)
const call = vi
.fn()
.mockRejectedValueOnce(unauthorized())
.mockResolvedValueOnce("after-refresh")
const result = await runWithAuthRetry(call)
expect(result).toBe("after-refresh")
expect(refreshToken).toHaveBeenCalledWith({ force: true })
expect(call).toHaveBeenCalledTimes(2)
})
it("rethrows the original 401 when refresh returns null", async () => {
vi.mocked(refreshToken).mockResolvedValue(null)
const err = unauthorized()
const call = vi.fn().mockRejectedValue(err)
await expect(runWithAuthRetry(call)).rejects.toBe(err)
expect(call).toHaveBeenCalledTimes(1)
})
it("rethrows the original 401 when refresh itself throws", async () => {
vi.mocked(refreshToken).mockRejectedValue(new Error("network down"))
const err = unauthorized()
const call = vi.fn().mockRejectedValue(err)
await expect(runWithAuthRetry(call)).rejects.toBe(err)
})
it("propagates a retry-time error after a successful refresh", async () => {
vi.mocked(refreshToken).mockResolvedValue({
token: "t2"
} as Awaited<ReturnType<typeof refreshToken>>)
const retryErr = unauthorized()
const call = vi
.fn()
.mockRejectedValueOnce(unauthorized())
.mockRejectedValueOnce(retryErr)
await expect(runWithAuthRetry(call)).rejects.toBe(retryErr)
expect(call).toHaveBeenCalledTimes(2)
})
})