chore: merge origin/main with local test additions
All checks were successful
CI / verify (push) Successful in 10m1s

Resolves conflict in repo.spec.ts by combining getRepoPermission
tests from main with the getFiles and queryFileContent tests from
the local test pass. Adds getRepoPermission to the userRepo.store
spec mocks so the new permission probe added upstream doesn't break
the store tests.
This commit is contained in:
Julien Calixte
2026-06-06 22:17:10 +02:00
17 changed files with 522 additions and 50 deletions

View File

@@ -1,7 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
const { reposGet } = vi.hoisted(() => ({ reposGet: vi.fn() }))
vi.mock("./octo", () => ({
getOctokit: vi.fn(),
getOctokit: vi.fn().mockResolvedValue({
repos: { get: reposGet }
}),
runWithAuthRetry: vi.fn()
}))
@@ -18,12 +22,52 @@ vi.mock("@/modules/note/cache/prepareNoteCache", () => ({
}))
import { getOctokit, runWithAuthRetry } from "./octo"
import { getFiles, queryFileContent } from "./repo"
import {
getFiles,
getRepoPermission,
queryFileContent
} from "./repo"
const makeOctokitWithRequest = (impl: (route: string, params: unknown) => unknown) => ({
request: vi.fn(impl)
})
describe("getRepoPermission", () => {
beforeEach(() => {
reposGet.mockReset()
vi.mocked(getOctokit).mockResolvedValue({
repos: { get: reposGet }
} as never)
})
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()
})
})
describe("getFiles", () => {
beforeEach(() => {
vi.mocked(getOctokit).mockReset()

View File

@@ -4,6 +4,18 @@ import { RepoFile } from "@/modules/repo/interfaces/RepoFile"
import { UserSettings } from "@/modules/repo/interfaces/UserSettings"
import { getOctokit, runWithAuthRetry } from "@/modules/repo/services/octo"
export const getRepoPermission = async (
owner: string,
repo: string
): Promise<boolean> => {
if (!owner || !repo) {
return false
}
const octokit = await getOctokit()
const { data } = await octokit.repos.get({ owner, repo })
return data.permissions?.push ?? false
}
export const getFiles = async (
owner: string,
repo: string

View File

@@ -14,7 +14,8 @@ vi.mock("@/modules/repo/services/repo", () => ({
getFiles: vi.fn().mockResolvedValue([]),
getMainReadme: vi.fn().mockResolvedValue(null),
getCachedMainReadme: vi.fn().mockResolvedValue(null),
getUserSettingsContent: vi.fn().mockResolvedValue(null)
getUserSettingsContent: vi.fn().mockResolvedValue(null),
getRepoPermission: vi.fn().mockResolvedValue(false)
}))
vi.mock("@/modules/user/service/signIn", () => ({
@@ -26,6 +27,7 @@ import {
getCachedMainReadme,
getFiles,
getMainReadme,
getRepoPermission,
getUserSettingsContent
} from "@/modules/repo/services/repo"
@@ -145,6 +147,7 @@ describe("userRepo store — setUserRepo", () => {
vi.mocked(getMainReadme).mockResolvedValue(null)
vi.mocked(getCachedMainReadme).mockResolvedValue(null)
vi.mocked(getUserSettingsContent).mockResolvedValue(null)
vi.mocked(getRepoPermission).mockResolvedValue(false)
})
it("sets user and repo immediately and clears loadError", async () => {

View File

@@ -10,6 +10,7 @@ import {
getCachedMainReadme,
getFiles,
getMainReadme,
getRepoPermission,
getUserSettingsContent
} from "@/modules/repo/services/repo"
import { refreshToken } from "@/modules/user/service/signIn"
@@ -24,6 +25,7 @@ interface State {
userSettings?: UserSettings | null
needToLogin: boolean
loadError: RepoLoadError
canPush: boolean
_requestId: number
}
@@ -46,6 +48,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
userSettings: undefined,
needToLogin: false,
loadError: null,
canPush: false,
_requestId: 0
}),
actions: {
@@ -78,6 +81,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
this.user = user
this.repo = repo
this.loadError = null
this.canPush = false
let lsLayout: Partial<UserSettings> = {}
try {
@@ -120,6 +124,17 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
if (requestId !== this._requestId) return
getRepoPermission(user, repo)
.then((canPush) => {
if (requestId !== this._requestId) return
this.canPush = canPush
})
.catch((error) => {
if (requestId !== this._requestId) return
console.warn("getRepoPermission failed", error)
this.canPush = false
})
getFiles(user, repo)
.then(async (files) => {
if (requestId !== this._requestId) return
@@ -230,6 +245,28 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
})
this.files = newFiles
},
registerUploadedFile(file: RepoFile) {
if (!file.path) {
return
}
const savedRepoId = generateId(
DataType.SavedRepo,
`${this.user}-${this.repo}`
)
const newFiles = [
...toRaw(this.files).filter((f) => f.path !== file.path),
toRaw(file)
]
data.update<DataType.SavedRepo, SavedRepo>({
_id: savedRepoId,
$type: DataType.SavedRepo,
repo: this.repo,
user: this.user,
files: newFiles
})
this.files = newFiles
},
resetUserRepo() {
this.user = ""
this.repo = ""
@@ -239,6 +276,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
resetFiles() {
this.files = []
this.readme = null
this.canPush = false
},
setFontFamily(fontFamily: string) {
if (!this.userSettings) {

View File

@@ -0,0 +1,7 @@
export const GITHUB_OAUTH_RETURN_PATH_KEY = "github-oauth-return-path"
export const consumeGithubOAuthReturnPath = (): string | null => {
const path = sessionStorage.getItem(GITHUB_OAUTH_RETURN_PATH_KEY)
sessionStorage.removeItem(GITHUB_OAUTH_RETURN_PATH_KEY)
return path
}