Типы ответов хаба, клиент с разбором ошибок (AuthError, HubDownError, ApiError), хранилище токена с подпиской, экран входа с проверкой токена до сохранения, QueryClient с выходом на экран входа при 401, хуки запросов. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
41 lines
1.7 KiB
TypeScript
41 lines
1.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
describe("token", () => {
|
|
beforeEach(() => { vi.resetModules(); localStorage.clear(); });
|
|
afterEach(() => { vi.restoreAllMocks(); });
|
|
|
|
it("читает токен, сохранённый прежней доской", async () => {
|
|
localStorage.setItem("jira-hub-token", "abc");
|
|
const t = await import("./token.ts");
|
|
expect(t.getToken()).toBe("abc");
|
|
});
|
|
|
|
it("setToken и clearToken пишут в localStorage и оповещают подписчиков", async () => {
|
|
const t = await import("./token.ts");
|
|
const fn = vi.fn();
|
|
t.subscribe(fn);
|
|
t.setToken("xyz");
|
|
expect(localStorage.getItem("jira-hub-token")).toBe("xyz");
|
|
t.clearToken("bad");
|
|
expect(t.getAuth()).toEqual({ token: "", reason: "bad" });
|
|
expect(localStorage.getItem("jira-hub-token")).toBeNull();
|
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("закрытое хранилище: токен живёт в памяти, исключений нет", async () => {
|
|
vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => { throw new DOMException("closed", "SecurityError"); });
|
|
vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => { throw new DOMException("closed", "SecurityError"); });
|
|
const t = await import("./token.ts");
|
|
expect(t.getToken()).toBe("");
|
|
t.setToken("mem");
|
|
expect(t.getToken()).toBe("mem");
|
|
});
|
|
|
|
it("TOKEN_RE пропускает только печатный ASCII без пробелов", async () => {
|
|
const { TOKEN_RE } = await import("./token.ts");
|
|
expect(TOKEN_RE.test("Ab-_9~")).toBe(true);
|
|
expect(TOKEN_RE.test("ab c")).toBe(false);
|
|
expect(TOKEN_RE.test("токен")).toBe(false);
|
|
});
|
|
});
|