import { afterEach, describe, expect, it, vi } from "vitest"; import { fetchOrganizationBalance } from "@/lib/client/organization-balance"; const payload = (balanceFen = 12345) => ({ organization: { id: "org-current", name: "当前组织" }, wallet: { organizationId: "org-current", balanceFen, updatedAt: "2026-10-01T00:00:00Z" } }); afterEach(() => vi.unstubAllGlobals()); describe("creation organization balance", () => { it.each([12345, 0, -100])("preserves the available balance %i without substituting a fallback", async (amount) => { const fetchMock = vi.fn().mockResolvedValue(Response.json(payload(amount))); vi.stubGlobal("fetch", fetchMock); const controller = new AbortController(); expect((await fetchOrganizationBalance("org-current", controller.signal)).wallet.balanceFen).toBe(amount); expect(fetchMock).toHaveBeenCalledWith("/api/billing/balance", { cache: "no-store", signal: controller.signal }); }); it.each([ { ...payload(), wallet: { ...payload().wallet, organizationId: "org-other" } }, { ...payload(), wallet: { ...payload().wallet, balanceFen: "12345" } }, { ...payload(), wallet: { ...payload().wallet, balanceFen: 1.5 } }, { organization: payload().organization }, null ])("rejects malformed or other-organization responses instead of showing a balance", async (value) => { vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json(value))); await expect(fetchOrganizationBalance("org-current")).rejects.toThrow("余额信息不完整"); }); it("explains when the server has refreshed the user into a different organization", async () => { vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({ ...payload(), organization: { id: "org-other", name: "新组织" } }))); await expect(fetchOrganizationBalance("org-current")).rejects.toThrow("账号所属组织已变更,请刷新页面"); }); it.each([401, 403, 422])("explains expired or changed access for HTTP %i", async (status) => { vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({}, { status }))); await expect(fetchOrganizationBalance("org-current")).rejects.toThrow("登录状态或组织信息已变更,请刷新页面"); }); it("does not expose internal errors or turn failures into zero balance", async () => { vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({ error: "internal database details" }, { status: 500 }))); await expect(fetchOrganizationBalance("org-current")).rejects.toThrow("余额暂时无法读取,请重试。"); }); it("handles a non-JSON proxy response as an error", async () => { vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("gateway unavailable", { status: 502 }))); await expect(fetchOrganizationBalance("org-current")).rejects.toThrow("余额暂时无法读取,请重试。"); }); });