feat(repo): hide write UI for users without push access

Fetch the viewer's push permission via GET /repos/{owner}/{repo} when
entering a repo and gate every write affordance behind it: edit and
image-upload buttons in StackedNote, new-fleeting-note and YouTube
buttons in FleetingNotes, and interactive checkboxes in TodoNotes
(rendered disabled when read-only). Anonymous viewers get the same
treatment because the permissions field is absent without auth, which
collapses to canPush = false.

Previously every write button was visible regardless of role, so
read-role collaborators and anonymous viewers could enter edit mode and
type before the save failed with 401/403.
This commit is contained in:
Julien Calixte
2026-05-29 15:24:12 +02:00
parent 455addf760
commit a09e541fa8
7 changed files with 115 additions and 10 deletions

View File

@@ -0,0 +1,56 @@
import { beforeEach, describe, expect, it, vi } from "vitest"
const { reposGet } = vi.hoisted(() => ({ reposGet: vi.fn() }))
vi.mock("@/modules/repo/services/octo", () => ({
getOctokit: vi.fn().mockResolvedValue({
repos: { get: reposGet }
}),
runWithAuthRetry: vi.fn()
}))
vi.mock("@/hooks/useMarkdown.hook", () => ({
markdownBuilder: () => ({ render: (s: string) => s })
}))
vi.mock("@/modules/note/cache/prepareNoteCache", () => ({
prepareNoteCache: () => ({
getCachedNote: vi.fn().mockResolvedValue({ note: null }),
saveCacheNote: vi.fn()
})
}))
import { getRepoPermission } from "./repo"
describe("getRepoPermission", () => {
beforeEach(() => {
reposGet.mockReset()
})
it("returns true when permissions.push is true", async () => {
reposGet.mockResolvedValue({
data: { permissions: { push: true } }
})
await expect(getRepoPermission("owner", "repo")).resolves.toBe(true)
})
it("returns false when permissions.push is false", async () => {
reposGet.mockResolvedValue({
data: { permissions: { push: false } }
})
await expect(getRepoPermission("owner", "repo")).resolves.toBe(false)
})
it("returns false when permissions is missing (anonymous request)", async () => {
reposGet.mockResolvedValue({
data: {}
})
await expect(getRepoPermission("owner", "repo")).resolves.toBe(false)
})
it("returns false when owner or repo is empty", async () => {
await expect(getRepoPermission("", "repo")).resolves.toBe(false)
await expect(getRepoPermission("owner", "")).resolves.toBe(false)
expect(reposGet).not.toHaveBeenCalled()
})
})