feat(web): AuthContext + клиент под cookie-сессии, projectId из контекста
This commit is contained in:
+95
-10
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import { api } from "./client"
|
||||
import { DEFAULT_PROJECT_ID } from "@/lib/config"
|
||||
import { api, UnauthorizedError } from "./client"
|
||||
|
||||
const PROJECT_ID = "11111111-1111-1111-1111-111111111111"
|
||||
|
||||
beforeEach(() => { vi.restoreAllMocks() })
|
||||
|
||||
@@ -13,19 +14,72 @@ function mockFetch(body: unknown, ok = true, status = 200) {
|
||||
}
|
||||
|
||||
describe("api client", () => {
|
||||
it("lists accounts at project-scoped path", async () => {
|
||||
it("sends credentials:include on every request", async () => {
|
||||
const spy = mockFetch([])
|
||||
await api.listAccounts(PROJECT_ID)
|
||||
const [, opts] = spy.mock.calls[0]
|
||||
expect((opts as RequestInit).credentials).toBe("include")
|
||||
})
|
||||
|
||||
describe("api.auth", () => {
|
||||
it("login POSTs to /api/v1/auth/login with credentials:include", async () => {
|
||||
const spy = mockFetch({ user: { id: "u1", email: "a@b.com" }, project: { id: "p1", name: "Default" } })
|
||||
await api.auth.login("a@b.com", "secret")
|
||||
const [url, opts] = spy.mock.calls[0]
|
||||
expect(url).toBe("/api/v1/auth/login")
|
||||
expect((opts as RequestInit).method).toBe("POST")
|
||||
expect((opts as RequestInit).credentials).toBe("include")
|
||||
expect(String((opts as RequestInit).body)).toContain("secret")
|
||||
})
|
||||
|
||||
it("register POSTs to /api/v1/auth/register", async () => {
|
||||
const spy = mockFetch({ user: { id: "u1", email: "a@b.com" }, project: { id: "p1", name: "Default" } })
|
||||
await api.auth.register("a@b.com", "secret")
|
||||
const [url, opts] = spy.mock.calls[0]
|
||||
expect(url).toBe("/api/v1/auth/register")
|
||||
expect((opts as RequestInit).method).toBe("POST")
|
||||
})
|
||||
|
||||
it("logout POSTs to /api/v1/auth/logout", async () => {
|
||||
const spy = mockFetch(undefined, true, 204)
|
||||
await api.auth.logout()
|
||||
const [url, opts] = spy.mock.calls[0]
|
||||
expect(url).toBe("/api/v1/auth/logout")
|
||||
expect((opts as RequestInit).method).toBe("POST")
|
||||
})
|
||||
|
||||
it("me GETs /api/v1/auth/me and returns AuthState", async () => {
|
||||
const state = { user: { id: "u1", email: "a@b.com" }, project: { id: "p1", name: "Default" } }
|
||||
const spy = mockFetch(state)
|
||||
const result = await api.auth.me()
|
||||
const [url] = spy.mock.calls[0]
|
||||
expect(url).toBe("/api/v1/auth/me")
|
||||
expect(result).toEqual(state)
|
||||
})
|
||||
})
|
||||
|
||||
it("resource methods hit project-scoped path with projectId first", async () => {
|
||||
const spy = mockFetch([{ id: "a1", provider: "selectel", comment: "" }])
|
||||
const accounts = await api.listAccounts()
|
||||
const accounts = await api.listAccounts(PROJECT_ID)
|
||||
expect(accounts).toHaveLength(1)
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
`/api/v1/projects/${DEFAULT_PROJECT_ID}/accounts`,
|
||||
`/api/v1/projects/${PROJECT_ID}/accounts`,
|
||||
expect.objectContaining({ method: "GET" }),
|
||||
)
|
||||
})
|
||||
|
||||
it("listDomains(projectId) hits /api/v1/projects/{projectId}/domains", async () => {
|
||||
const spy = mockFetch([])
|
||||
await api.listDomains(PROJECT_ID)
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
`/api/v1/projects/${PROJECT_ID}/domains`,
|
||||
expect.objectContaining({ method: "GET" }),
|
||||
)
|
||||
})
|
||||
|
||||
it("sends secret on account creation but path has no secret leakage in response typing", async () => {
|
||||
const spy = mockFetch({ id: "a2", provider: "selectel", comment: "prod" })
|
||||
await api.createAccount({ provider: "selectel", secret: "TOKEN", comment: "prod" })
|
||||
await api.createAccount(PROJECT_ID, { provider: "selectel", secret: "TOKEN", comment: "prod" })
|
||||
const [, opts] = spy.mock.calls[0]
|
||||
expect((opts as RequestInit).method).toBe("POST")
|
||||
expect(String((opts as RequestInit).body)).toContain("TOKEN")
|
||||
@@ -33,14 +87,45 @@ describe("api client", () => {
|
||||
|
||||
it("throws on non-ok response", async () => {
|
||||
mockFetch({ error: "boom" }, false, 500)
|
||||
await expect(api.listDomains()).rejects.toThrow()
|
||||
await expect(api.listDomains(PROJECT_ID)).rejects.toThrow()
|
||||
})
|
||||
|
||||
it("applies with prune flag", async () => {
|
||||
it("throws UnauthorizedError on 401", async () => {
|
||||
mockFetch({ error: "unauthorized" }, false, 401)
|
||||
await expect(api.listDomains(PROJECT_ID)).rejects.toThrow(UnauthorizedError)
|
||||
})
|
||||
|
||||
it("applies with prune flag using projectId, id, body order", async () => {
|
||||
const spy = mockFetch({ updates: [], prunes: [], readOnly: [], inSyncCount: 0 })
|
||||
await api.applyDomain("d1", { applyUpdates: true, applyPrunes: true })
|
||||
await api.applyDomain(PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: true })
|
||||
const [url, opts] = spy.mock.calls[0]
|
||||
expect(url).toContain("/domains/d1/apply")
|
||||
expect(url).toBe(`/api/v1/projects/${PROJECT_ID}/domains/d1/apply`)
|
||||
expect(String((opts as RequestInit).body)).toContain("applyPrunes")
|
||||
})
|
||||
|
||||
it("checkDomain(projectId, id) hits project-scoped check path", async () => {
|
||||
const spy = mockFetch({ updates: [], prunes: [], readOnly: [], inSyncCount: 0 })
|
||||
await api.checkDomain(PROJECT_ID, "d1")
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
`/api/v1/projects/${PROJECT_ID}/domains/d1/check`,
|
||||
expect.objectContaining({ method: "GET" }),
|
||||
)
|
||||
})
|
||||
|
||||
it("importZones(projectId, accountId) hits project-scoped import path", async () => {
|
||||
const spy = mockFetch([])
|
||||
await api.importZones(PROJECT_ID, "acc1")
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
`/api/v1/projects/${PROJECT_ID}/accounts/acc1/import`,
|
||||
expect.objectContaining({ method: "POST" }),
|
||||
)
|
||||
})
|
||||
|
||||
it("setDomainTemplate(projectId, id, templateId) hits project-scoped domain path", async () => {
|
||||
const spy = mockFetch({ id: "d1", providerAccountId: "acc1", zoneName: "x.", zoneId: "z1" })
|
||||
await api.setDomainTemplate(PROJECT_ID, "d1", "t1")
|
||||
const [url, opts] = spy.mock.calls[0]
|
||||
expect(url).toBe(`/api/v1/projects/${PROJECT_ID}/domains/d1`)
|
||||
expect((opts as RequestInit).method).toBe("PATCH")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user