test: cover post-merge gaps in utils, services, and hooks
All checks were successful
CI / verify (push) Successful in 59s

Adds specs for the files added or modified in the recent merge that
weren't yet covered: oauthReturnPath, withATProtoImages, link,
displayLanguage, and the useGitHubContent / useImageUpload /
useNoteFreshness hooks. Locks in the new pullLatest contract
({raw, failureStatus}) and the fetchLatestSha branch semantics
(ok/unauthorized/offline).
This commit is contained in:
Julien Calixte
2026-06-06 23:36:17 +02:00
parent af2ffc3949
commit bb3a4a4dad
7 changed files with 751 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
vi.mock("@/modules/repo/services/octo", () => ({
getOctokit: vi.fn(),
runWithAuthRetry: vi.fn()
}))
vi.mock("@/utils/notif", () => ({
confirmMessage: vi.fn(),
errorMessage: vi.fn()
}))
import { getOctokit, runWithAuthRetry } from "@/modules/repo/services/octo"
import { confirmMessage, errorMessage } from "@/utils/notif"
import { useGitHubContent } from "./useGitHubContent.hook"
const make = () => useGitHubContent({ user: "alice", repo: "notes" })
describe("useGitHubContent.fetchLatestSha", () => {
beforeEach(() => {
vi.mocked(runWithAuthRetry).mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
})
it("returns the sha from a file response", async () => {
vi.mocked(runWithAuthRetry).mockResolvedValue({
data: { sha: "abc123" }
} as never)
expect(await make().fetchLatestSha("a.md")).toEqual({
kind: "ok",
sha: "abc123"
})
})
it("returns sha=null when the path resolves to a directory (array response)", async () => {
vi.mocked(runWithAuthRetry).mockResolvedValue({ data: [] } as never)
expect(await make().fetchLatestSha("dir/")).toEqual({
kind: "ok",
sha: null
})
})
it("returns sha=null when the data object has no sha field", async () => {
vi.mocked(runWithAuthRetry).mockResolvedValue({
data: { type: "submodule" }
} as never)
expect(await make().fetchLatestSha("path")).toEqual({
kind: "ok",
sha: null
})
})
it("returns kind=unauthorized on 401", async () => {
vi.mocked(runWithAuthRetry).mockRejectedValue(
Object.assign(new Error("Unauthorized"), { status: 401 })
)
expect(await make().fetchLatestSha("path")).toEqual({
kind: "unauthorized"
})
})
it("returns kind=offline on non-401 errors (404, network, etc.)", async () => {
vi.mocked(runWithAuthRetry).mockRejectedValue(
Object.assign(new Error("Not found"), { status: 404 })
)
expect(await make().fetchLatestSha("path")).toEqual({ kind: "offline" })
vi.mocked(runWithAuthRetry).mockRejectedValue(new Error("network"))
expect(await make().fetchLatestSha("path")).toEqual({ kind: "offline" })
})
})
describe("useGitHubContent.updateFile / createFile (putFile)", () => {
const mockOctokitRequest = vi.fn()
beforeEach(() => {
mockOctokitRequest.mockReset()
vi.mocked(getOctokit).mockResolvedValue({
request: mockOctokitRequest
} as never)
vi.mocked(confirmMessage).mockReset()
vi.mocked(errorMessage).mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
})
it("returns the new sha and calls confirmMessage on success", async () => {
mockOctokitRequest.mockResolvedValue({
data: { content: { sha: "new-sha" } }
})
const result = await make().updateFile({
content: "# hello",
path: "a.md",
sha: "old-sha"
})
expect(result).toEqual({ sha: "new-sha", conflict: false })
expect(confirmMessage).toHaveBeenCalledWith("✅ Note saved")
})
it("flags conflict=true on 409 and 422 responses", async () => {
vi.spyOn(console, "warn").mockImplementation(() => {})
for (const status of [409, 422]) {
mockOctokitRequest.mockRejectedValueOnce(
Object.assign(new Error("Conflict"), { status })
)
const result = await make().createFile({ content: "x", path: "a.md" })
expect(result).toEqual({ sha: null, conflict: true })
}
expect(errorMessage).toHaveBeenCalledWith(
"⚠ Conflict: this note changed on GitHub"
)
})
it("returns conflict=false and calls errorMessage on non-conflict errors", async () => {
vi.spyOn(console, "warn").mockImplementation(() => {})
mockOctokitRequest.mockRejectedValue(
Object.assign(new Error("Server error"), { status: 500 })
)
const result = await make().updateFile({
content: "x",
path: "a.md",
sha: "old"
})
expect(result).toEqual({ sha: null, conflict: false })
expect(errorMessage).toHaveBeenCalledWith("❌ Note could not be saved")
})
})
describe("useGitHubContent.uploadBinaryFile", () => {
const mockOctokitRequest = vi.fn()
beforeEach(() => {
mockOctokitRequest.mockReset()
vi.mocked(getOctokit).mockResolvedValue({
request: mockOctokitRequest
} as never)
vi.mocked(errorMessage).mockReset()
vi.mocked(confirmMessage).mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
})
it("uploads pre-base64-encoded content without re-encoding", async () => {
mockOctokitRequest.mockResolvedValue({
data: { content: { sha: "img-sha" } }
})
const result = await make().uploadBinaryFile({
base64: "ZGF0YQ==",
path: "img/cover.png"
})
expect(result).toEqual({ sha: "img-sha", conflict: false })
expect(mockOctokitRequest).toHaveBeenCalledWith(
"PUT /repos/{owner}/{repo}/contents/{+path}",
expect.objectContaining({
path: "img/cover.png",
content: "ZGF0YQ=="
})
)
expect(confirmMessage).toHaveBeenCalledWith("✅ Image uploaded")
})
it("surfaces a conflict on existing file (422)", async () => {
vi.spyOn(console, "warn").mockImplementation(() => {})
mockOctokitRequest.mockRejectedValue(
Object.assign(new Error("Exists"), { status: 422 })
)
const result = await make().uploadBinaryFile({
base64: "Zm9v",
path: "img.png"
})
expect(result).toEqual({ sha: null, conflict: true })
expect(errorMessage).toHaveBeenCalledWith(
"⚠ A file already exists at this path on GitHub"
)
})
})