import { describe, it, expect, beforeEach, vi } from "vitest";
// Mock the dependencies before importing the module
vi.mock("../../src/config/environments.js", () => ({
getCurrentEnvironment: () => "TST",
loadEnvironmentConfig: () => ({
ti: "TEST_TENANT",
cn: "TestConnection",
dt: "Browser",
ci: "test-client-id",
cs: "test-client-secret",
iu: "https://api.example.com",
pu: "https://auth.example.com",
oa: "/oauth/authorize",
ot: "/oauth/token",
or: "/oauth/revoke",
ev: "1.0",
v: "1.0",
saak: "test-access-key",
sask: "test-secret-key",
}),
}));
vi.mock("../../src/auth/ionAuth.js", () => ({
getAccessToken: vi.fn().mockResolvedValue("mock-access-token"),
}));
vi.mock("../../src/config/ionapi.js", () => ({
getRestApiBaseUrl: (config: { ti: string }) =>
`https://api.example.com/${config.ti}/BIRST/InforBirstRestAPI`,
getGenAiApiBaseUrl: (config: { ti: string }) =>
`https://api.example.com/${config.ti}/BIRST/genai/tool`,
getIcwApiBaseUrl: (config: { ti: string }) =>
`https://api.example.com/${config.ti}/BIRST/insights`,
}));
// Mock fetch globally
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
describe("birstClient", () => {
beforeEach(async () => {
vi.clearAllMocks();
// Reset the module to get a fresh client instance
vi.resetModules();
});
describe("rest", () => {
it("should make authenticated GET request to REST API", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: true,
headers: new Map([["content-type", "application/json"]]),
json: async () => ({ data: "test" }),
});
const client = getBirstClient();
const result = await client.rest("/spaces");
expect(mockFetch).toHaveBeenCalledWith(
"https://api.example.com/TEST_TENANT/BIRST/InforBirstRestAPI/spaces",
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({
Authorization: "Bearer mock-access-token",
Accept: "application/json",
}),
})
);
expect(result).toEqual({ data: "test" });
});
it("should make POST request with body", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: true,
headers: new Map([["content-type", "application/json"]]),
json: async () => ({ success: true }),
});
const client = getBirstClient();
await client.rest("/spaces", {
method: "POST",
body: { name: "test" },
});
expect(mockFetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({
"Content-Type": "application/json",
}),
body: JSON.stringify({ name: "test" }),
})
);
});
it("should include query parameters in URL", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: true,
headers: new Map([["content-type", "application/json"]]),
json: async () => ({}),
});
const client = getBirstClient();
await client.rest("/spaces", {
queryParams: { limit: 10, offset: 0 },
});
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining("limit=10"),
expect.any(Object)
);
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining("offset=0"),
expect.any(Object)
);
});
});
describe("genai", () => {
it("should make request to GenAI API", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: true,
headers: new Map([["content-type", "application/json"]]),
json: async () => ({ bql: "SELECT * FROM test" }),
});
const client = getBirstClient();
await client.genai("/generate-bql", {
method: "POST",
body: { query: "show sales" },
});
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining("/BIRST/genai/tool/generate-bql"),
expect.any(Object)
);
});
});
describe("icw", () => {
it("should make request to ICW API", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: true,
headers: new Map([["content-type", "application/json"]]),
json: async () => ({ data: [] }),
});
const client = getBirstClient();
await client.icw("/query/execute", {
method: "POST",
body: { query: "SELECT 1" },
});
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining("/BIRST/insights/query/execute"),
expect.any(Object)
);
});
});
describe("error handling", () => {
it("should throw MCP error on API failure", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
json: async () => ({ message: "Space not found" }),
});
const client = getBirstClient();
await expect(client.rest("/spaces/invalid")).rejects.toThrow();
});
it("should handle non-JSON error responses", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
json: async () => {
throw new Error("Not JSON");
},
text: async () => "Internal Server Error",
});
const client = getBirstClient();
await expect(client.rest("/spaces")).rejects.toThrow();
});
});
describe("singleton behavior", () => {
it("should return the same instance", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
resetBirstClient();
const client1 = getBirstClient();
const client2 = getBirstClient();
expect(client1).toBe(client2);
});
it("should create new instance after reset", async () => {
const { getBirstClient, resetBirstClient } = await import(
"../../src/client/birstClient.js"
);
const client1 = getBirstClient();
resetBirstClient();
const client2 = getBirstClient();
expect(client1).not.toBe(client2);
});
});
});