Files
NianAIGC/tests/organization-client.test.ts
2026-07-03 11:25:25 +08:00

259 lines
9.3 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
addOrganizationMemberAndCreatePlatformUser,
createOrganizationGroup,
getOrganizationApiConfig,
isOrganizationPermissionDenied,
isOrganizationRouteNotFound,
listOrganizationMembers,
resetPlatformUserPassword
} from "@/lib/server/organization-client";
describe("organization account client", () => {
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
});
it("tracks required organization and staff API configuration separately", () => {
vi.stubEnv("ZHINIAN_ORG_API_BASE_URL", "https://gateway.example.com/basic");
vi.stubEnv("ZHINIAN_ORG_API_TOKEN", "org-token");
expect(getOrganizationApiConfig()).toMatchObject({
configured: true,
staffConfigured: false,
staffMissing: ["ZHINIAN_STAFF_API_BASE_URL", "ZHINIAN_ORG_TENANT_ID"]
});
});
it("accepts the current login token instead of a static organization token", () => {
vi.stubEnv("ZHINIAN_ORG_API_BASE_URL", "https://gateway.example.com/basic");
vi.stubEnv("ZHINIAN_STAFF_API_BASE_URL", "https://gateway.example.com/staff");
vi.stubEnv("ZHINIAN_ORG_TENANT_ID", "1");
expect(getOrganizationApiConfig("login-token")).toMatchObject({
organizationToken: "login-token",
staffToken: "login-token",
configured: true,
staffConfigured: true,
missing: [],
staffMissing: []
});
});
it("calls member list through the hotelStaff admin organization proxy by default", async () => {
const seen: Array<{ input: string; init?: RequestInit }> = [];
stubOrganizationEnv();
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
seen.push({ input: String(input), init });
return jsonResponse({
code: 0,
data: { records: [], total: 0, size: 10, current: 1, pages: 0 }
});
});
await listOrganizationMembers({ organizationId: "org_1", pageNum: 1, pageSize: 10 });
expect(seen[0].input).toBe("https://gateway.example.com/basic/adminOrganization/organizationMember/organizationMemberList");
expect(seen[0].init?.method).toBe("POST");
expect(seen[0].init?.headers).toMatchObject({
Authorization: "Bearer org-token",
tenantId: "1"
});
expect(seen[0].init?.headers).not.toMatchObject({ from: "Y" });
});
it("prefers the current login token when forwarding organization requests", async () => {
const seen: Array<{ input: string; init?: RequestInit }> = [];
stubOrganizationEnv();
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
seen.push({ input: String(input), init });
return jsonResponse({
code: 0,
data: { records: [], total: 0, size: 10, current: 1, pages: 0 }
});
});
await listOrganizationMembers(
{ organizationId: "org_1", pageNum: 1, pageSize: 10 },
{ accessToken: "login-token" }
);
expect(seen[0].init?.headers).toMatchObject({
Authorization: "Bearer login-token",
tenantId: "1"
});
});
it("creates organization groups through the documented group endpoint", async () => {
const seen: Array<{ input: string; init?: RequestInit }> = [];
stubOrganizationEnv();
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
seen.push({ input: String(input), init });
return jsonResponse({ code: 0, data: true });
});
await createOrganizationGroup({
organizationId: "org_1",
groupName: "客房部",
groupDesc: "处理客房相关工单"
}, { accessToken: "login-token" });
expect(seen[0].input).toBe("https://gateway.example.com/basic/organizationGroup/createOrganizationGroup");
expect(seen[0].init?.headers).toMatchObject({
Authorization: "Bearer login-token",
tenantId: "1"
});
expect(JSON.parse(String(seen[0].init?.body))).toMatchObject({
organizationId: "org_1",
groupName: "客房部",
groupDesc: "处理客房相关工单"
});
});
it("allows overriding the member list path exposed by the gateway", async () => {
const seen: Array<{ input: string; init?: RequestInit }> = [];
stubOrganizationEnv();
vi.stubEnv("ZHINIAN_ORG_MEMBER_LIST_PATH", "/adminOrganization/organizationMember/list");
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
seen.push({ input: String(input), init });
return jsonResponse({
code: 0,
data: { records: [], total: 0, size: 10, current: 1, pages: 0 }
});
});
await listOrganizationMembers({ organizationId: "org_1", pageNum: 1, pageSize: 10 });
expect(seen[0].input).toBe("https://gateway.example.com/basic/adminOrganization/organizationMember/list");
expect(seen[0].init?.headers).not.toMatchObject({ from: "Y" });
});
it("keeps the inner marker when the member list path points directly at the basic service", async () => {
const seen: Array<{ input: string; init?: RequestInit }> = [];
stubOrganizationEnv();
vi.stubEnv("ZHINIAN_ORG_MEMBER_LIST_PATH", "/organizationMember/organizationMemberList");
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
seen.push({ input: String(input), init });
return jsonResponse({
code: 0,
data: { records: [], total: 0, size: 10, current: 1, pages: 0 }
});
});
await listOrganizationMembers({ organizationId: "org_1", pageNum: 1, pageSize: 10 });
expect(seen[0].input).toBe("https://gateway.example.com/basic/organizationMember/organizationMemberList");
expect(seen[0].init?.headers).toMatchObject({ from: "Y" });
});
it("marks static resource errors as missing organization routes", async () => {
stubOrganizationEnv();
vi.stubGlobal("fetch", async () => new Response(JSON.stringify({
code: 1,
msg: "No static resource organizationMember/organizationMemberList."
}), {
status: 404,
headers: { "Content-Type": "application/json" }
}));
try {
await listOrganizationMembers({ organizationId: "org_1", pageNum: 1, pageSize: 10 });
throw new Error("expected organization request to fail");
} catch (error) {
expect(isOrganizationRouteNotFound(error)).toBe(true);
expect(error).toMatchObject({
status: 404,
message: expect.stringContaining("ZHINIAN_ORG_MEMBER_LIST_PATH")
});
}
});
it("marks upstream administrator-role errors as organization permission denials", async () => {
stubOrganizationEnv();
vi.stubGlobal("fetch", async () => jsonResponse({
code: 1,
msg: "仅管理员角色允许调用"
}));
try {
await listOrganizationMembers({ organizationId: "org_1", pageNum: 1, pageSize: 10 });
throw new Error("expected organization request to fail");
} catch (error) {
expect(isOrganizationPermissionDenied(error)).toBe(true);
expect(isOrganizationRouteNotFound(error)).toBe(false);
expect(error).toMatchObject({
status: 502,
message: "仅管理员角色允许调用"
});
}
});
it("uses staff service endpoints for user creation and password reset", async () => {
const seen: Array<{ input: string; init?: RequestInit }> = [];
stubOrganizationEnv();
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
seen.push({ input: String(input), init });
if (String(input).endsWith("/resetPlatformUserPassword")) return jsonResponse({ code: 0, data: true });
return jsonResponse({
code: 0,
data: {
userId: "10001",
username: "zhangsan",
phone: "13800000000",
tenantId: 1,
initialPassword: "Nianxx@123456"
}
});
});
const user = await addOrganizationMemberAndCreatePlatformUser({
tenantId: 1,
username: "zhangsan",
phone: "13800000000",
name: "张三",
memberName: "张三",
memberPhone: "13800000000",
roleId: "role_1",
organizationId: "org_1",
groupId: "group_1",
mustChangePassword: true
});
await resetPlatformUserPassword({
tenantId: 1,
userId: 10001,
newPassword: "Nianxx@654321",
mustChangePassword: true
});
expect(user.initialPassword).toBe("Nianxx@123456");
expect(seen[0].input).toBe("https://gateway.example.com/staff/adminOrganization/organizationMember/addOrganizationMemberAndCreatePlatformUser");
expect(seen[1].input).toBe("https://gateway.example.com/staff/adminPcUser/resetPlatformUserPassword");
expect(seen[0].init?.headers).toMatchObject({
Authorization: "Bearer staff-token",
tenantId: "1"
});
expect(JSON.parse(String(seen[1].init?.body))).toMatchObject({
tenantId: 1,
userId: 10001,
newPassword: "Nianxx@654321",
mustChangePassword: true
});
});
});
function stubOrganizationEnv() {
vi.stubEnv("ZHINIAN_ORG_API_BASE_URL", "https://gateway.example.com/basic");
vi.stubEnv("ZHINIAN_ORG_API_TOKEN", "org-token");
vi.stubEnv("ZHINIAN_STAFF_API_BASE_URL", "https://gateway.example.com/staff");
vi.stubEnv("ZHINIAN_STAFF_API_TOKEN", "staff-token");
vi.stubEnv("ZHINIAN_ORG_TENANT_ID", "1");
}
function jsonResponse(value: unknown) {
return new Response(JSON.stringify(value), {
status: 200,
headers: { "Content-Type": "application/json" }
});
}