From a581cf2ac92557dc410c10c3ec21237ffb7973e8 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 8 Feb 2026 21:42:23 +0530 Subject: [PATCH 1/6] Add tests for useGetChatHistory hook Adds a new test suite for the useGetChatHistory React Query hook. Tests cover initial loading state, behavior when mocks are enabled, query options (staleTime), real API fetching with auth token, API error handling, and behavior when projectId is missing. Mocks are provided for logger, Asgardeo auth, mock config, API delay constant, and global fetch; tests run within a QueryClientProvider. --- .../api/__tests__/useGetChatHistory.test.tsx | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 apps/customer-portal/webapp/src/api/__tests__/useGetChatHistory.test.tsx diff --git a/apps/customer-portal/webapp/src/api/__tests__/useGetChatHistory.test.tsx b/apps/customer-portal/webapp/src/api/__tests__/useGetChatHistory.test.tsx new file mode 100644 index 0000000000..4f32aeb60d --- /dev/null +++ b/apps/customer-portal/webapp/src/api/__tests__/useGetChatHistory.test.tsx @@ -0,0 +1,211 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { renderHook, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { useGetChatHistory } from "@api/useGetChatHistory"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { ReactNode } from "react"; + +const mockLogger = { + debug: vi.fn(), + error: vi.fn(), +}; +vi.mock("@/hooks/useLogger", () => ({ + useLogger: () => mockLogger, +})); + +vi.mock("@/constants/apiConstants", async (importOriginal) => { + const actual = (await importOriginal()) as Record; + return { + ...actual, + API_MOCK_DELAY: 0, + }; +}); + +const mockGetIdToken = vi.fn().mockResolvedValue("mock-token"); +vi.mock("@asgardeo/react", () => ({ + useAsgardeo: () => ({ + getIdToken: mockGetIdToken, + isSignedIn: true, + isLoading: false, + }), +})); + +let mockIsMockEnabled = true; +vi.mock("@/providers/MockConfigProvider", () => ({ + useMockConfig: () => ({ + isMockEnabled: mockIsMockEnabled, + }), +})); + +describe("useGetChatHistory", () => { + let queryClient: QueryClient; + + beforeEach(() => { + queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + mockLogger.debug.mockClear(); + mockLogger.error.mockClear(); + mockIsMockEnabled = true; + vi.clearAllMocks(); + }); + + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ); + + it("should return loading state initially", async () => { + const { result } = renderHook( + () => useGetChatHistory("project-1"), + { wrapper }, + ); + + expect(result.current.isLoading).toBe(true); + }); + + it("should return mock data when mock is enabled", async () => { + mockIsMockEnabled = true; + const { result } = renderHook( + () => useGetChatHistory("project-1"), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toBeDefined(); + expect(result.current.data?.chatHistory).toBeDefined(); + expect(Array.isArray(result.current.data?.chatHistory)).toBe(true); + expect(result.current.data?.chatHistory.length).toBeGreaterThan(0); + expect(result.current.data?.chatHistory[0]).toMatchObject({ + chatId: expect.any(String), + title: expect.any(String), + startedTime: expect.any(String), + messages: expect.any(Number), + kbArticles: expect.any(Number), + status: expect.any(String), + }); + expect(mockLogger.debug).toHaveBeenCalledWith( + expect.stringContaining( + "Fetching chat history for project ID: project-1, mock: true", + ), + ); + }); + + it("should have correct query options", () => { + renderHook(() => useGetChatHistory("project-1"), { wrapper }); + + const query = queryClient.getQueryCache().findAll({ + queryKey: ["chat-history", "project-1", true], + })[0]; + + expect((query?.options as { staleTime?: number }).staleTime).toBe( + 5 * 60 * 1000, + ); + }); + + it("should fetch real data when mock is disabled", async () => { + mockIsMockEnabled = false; + const mockResponse = { + chatHistory: [ + { + chatId: "1", + title: "Test chat", + startedTime: "1 hour ago", + messages: 3, + kbArticles: 1, + status: "Resolved", + }, + ], + }; + + const originalConfig = window.config; + window.config = { + CUSTOMER_PORTAL_BACKEND_BASE_URL: "https://api.test", + } as typeof window.config; + + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockResponse), + status: 200, + } as Response), + ); + + const { result } = renderHook( + () => useGetChatHistory("project-1"), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockResponse); + expect(mockGetIdToken).toHaveBeenCalled(); + expect(mockLogger.debug).toHaveBeenCalledWith( + expect.stringContaining( + "Fetching chat history for project ID: project-1, mock: false", + ), + ); + + window.config = originalConfig; + }); + + it("should handle API error when mock is disabled", async () => { + mockIsMockEnabled = false; + + const originalConfig = window.config; + window.config = { + CUSTOMER_PORTAL_BACKEND_BASE_URL: "https://api.test", + } as typeof window.config; + + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: false, + statusText: "Internal Server Error", + status: 500, + } as Response), + ); + + const { result } = renderHook( + () => useGetChatHistory("project-1"), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isError).toBe(true)); + expect(result.current.error?.message).toContain( + "Error fetching chat history: Internal Server Error", + ); + expect(mockLogger.error).toHaveBeenCalled(); + + window.config = originalConfig; + }); + + it("should not fetch if projectId is missing", () => { + const { result } = renderHook(() => useGetChatHistory(""), { + wrapper, + }); + + expect(result.current.isLoading).toBe(false); + expect(result.current.fetchStatus).toBe("idle"); + }); +}); From 7f6c9643b328cd9c8d3425a2af90f53fd00eeffd Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 8 Feb 2026 21:42:34 +0530 Subject: [PATCH 2/6] Add useGetChatHistory React Query hook Introduce useGetChatHistory hook to fetch project chat history via react-query. Supports mock mode (uses getMockChatHistory with API_MOCK_DELAY) and real API calls (gets idToken from Asgardeo, builds request to CUSTOMER_PORTAL_BACKEND_BASE_URL, and adds headers via addApiHeaders). Includes logging, error handling, query key scoping, enabled gating based on auth/mock state, and 5 minute staleTime. --- .../webapp/src/api/useGetChatHistory.ts | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 apps/customer-portal/webapp/src/api/useGetChatHistory.ts diff --git a/apps/customer-portal/webapp/src/api/useGetChatHistory.ts b/apps/customer-portal/webapp/src/api/useGetChatHistory.ts new file mode 100644 index 0000000000..9b4eba9024 --- /dev/null +++ b/apps/customer-portal/webapp/src/api/useGetChatHistory.ts @@ -0,0 +1,92 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { useAsgardeo } from "@asgardeo/react"; +import { getMockChatHistory } from "@models/mockFunctions"; +import { useMockConfig } from "@providers/MockConfigProvider"; +import { useLogger } from "@hooks/useLogger"; +import { ApiQueryKeys, API_MOCK_DELAY } from "@constants/apiConstants"; +import { addApiHeaders } from "@utils/apiUtils"; +import type { ChatHistoryResponse } from "@models/responses"; + +/** + * Fetches chat history for a project. + * + * @param {string} projectId - The project ID. + * @returns {UseQueryResult} The query result. + */ +export function useGetChatHistory( + projectId: string, +): UseQueryResult { + const logger = useLogger(); + const { getIdToken, isSignedIn, isLoading: isAuthLoading } = useAsgardeo(); + const { isMockEnabled } = useMockConfig(); + + return useQuery({ + queryKey: [ApiQueryKeys.CHAT_HISTORY, projectId, isMockEnabled], + queryFn: async (): Promise => { + logger.debug( + `Fetching chat history for project ID: ${projectId}, mock: ${isMockEnabled}`, + ); + + if (isMockEnabled) { + await new Promise((resolve) => setTimeout(resolve, API_MOCK_DELAY)); + const data = getMockChatHistory(); + logger.debug( + `Chat history fetched successfully for project ID: ${projectId} (mock)`, + data, + ); + return data; + } + + try { + const idToken = await getIdToken(); + const baseUrl = window.config?.CUSTOMER_PORTAL_BACKEND_BASE_URL; + + if (!baseUrl) { + throw new Error("CUSTOMER_PORTAL_BACKEND_BASE_URL is not configured"); + } + + const requestUrl = `${baseUrl}/projects/${projectId}/chat-history`; + + const response = await fetch(requestUrl, { + method: "GET", + headers: addApiHeaders(idToken), + }); + + logger.debug( + `[useGetChatHistory] Response status: ${response.status}`, + ); + + if (!response.ok) { + throw new Error( + `Error fetching chat history: ${response.statusText}`, + ); + } + + const data: ChatHistoryResponse = await response.json(); + logger.debug("[useGetChatHistory] Data received:", data); + return data; + } catch (error) { + logger.error("[useGetChatHistory] Error:", error); + throw error; + } + }, + enabled: !!projectId && (isMockEnabled || (isSignedIn && !isAuthLoading)), + staleTime: 5 * 60 * 1000, + }); +} From 7139b2546ad7b6159c93217505e8fe3e197b87c3 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 8 Feb 2026 21:42:44 +0530 Subject: [PATCH 3/6] Add CHAT_HISTORY to ApiQueryKeys Introduce a new CHAT_HISTORY entry in ApiQueryKeys (apps/customer-portal/webapp/src/constants/apiConstants.ts) with value 'chat-history' to support caching/identification of chat history API/query operations. --- apps/customer-portal/webapp/src/constants/apiConstants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/customer-portal/webapp/src/constants/apiConstants.ts b/apps/customer-portal/webapp/src/constants/apiConstants.ts index 519a1a51f6..7c9a608884 100644 --- a/apps/customer-portal/webapp/src/constants/apiConstants.ts +++ b/apps/customer-portal/webapp/src/constants/apiConstants.ts @@ -27,4 +27,5 @@ export const ApiQueryKeys = { DASHBOARD_STATS: "dashboard-stats", PROJECT_STATS: "project-stats", PROJECT_CASES: "project-cases", + CHAT_HISTORY: "chat-history", } as const; From 9378301899611dd45210815e751a9ea73f8248c5 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 8 Feb 2026 21:43:07 +0530 Subject: [PATCH 4/6] Add mock chat history generator Introduce getMockChatHistory() returning a ChatHistoryResponse with sample chat entries (chatId, title, startedTime, messages, kbArticles, status) for the customer portal webapp. Also update imports to include ChatHistoryResponse. This provides mock data for UI development and testing of chat/history components. --- .../webapp/src/models/mockFunctions.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/apps/customer-portal/webapp/src/models/mockFunctions.ts b/apps/customer-portal/webapp/src/models/mockFunctions.ts index 7c493a2c36..31b1921043 100644 --- a/apps/customer-portal/webapp/src/models/mockFunctions.ts +++ b/apps/customer-portal/webapp/src/models/mockFunctions.ts @@ -27,6 +27,7 @@ import type { ProjectSupportStats, DashboardMockStats, ProjectStatsResponse, + ChatHistoryResponse, } from "@models/responses"; /** @@ -233,6 +234,58 @@ export const getMockDashboardStats = (): DashboardMockStats => { }; }; +/** + * Returns mock chat history for a project. + * + * @returns {ChatHistoryResponse} Mock chat history list. + */ +export const getMockChatHistory = (): ChatHistoryResponse => { + return { + chatHistory: [ + { + chatId: "1628192673", + title: "How do I configure custom claims in JWT tokens?", + startedTime: "2 hours ago", + messages: 8, + kbArticles: 3, + status: "Resolved", + }, + { + chatId: "1628192674", + title: "Getting error 401 when calling the API endpoint...", + startedTime: "1 day ago", + messages: 5, + kbArticles: 2, + status: "Still Open", + }, + { + chatId: "1628192675", + title: "Need help understanding rate limiting configuration", + startedTime: "2 days ago", + messages: 12, + kbArticles: 5, + status: "Resolved", + }, + { + chatId: "1628192676", + title: "Can I integrate with Azure AD for authentication?", + startedTime: "3 days ago", + messages: 6, + kbArticles: 4, + status: "Resolved", + }, + { + chatId: "1628192677", + title: "Deployment best practices for high availability setup", + startedTime: "5 days ago", + messages: 15, + kbArticles: 7, + status: "Abandoned", + }, + ], + }; +}; + /** * Returns mock project statistics. * From 78612dd4655500baf8cca963fb6092c2de474996 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 8 Feb 2026 21:43:49 +0530 Subject: [PATCH 5/6] Add chat history response models Introduce ChatHistoryItem and ChatHistoryResponse interfaces to represent support chat session summaries and project chat history lists. ChatHistoryItem includes chatId, title, startedTime, messages, kbArticles, and status to support chat listing APIs. --- .../webapp/src/models/responses.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/customer-portal/webapp/src/models/responses.ts b/apps/customer-portal/webapp/src/models/responses.ts index c14d737c18..c6fb84651b 100644 --- a/apps/customer-portal/webapp/src/models/responses.ts +++ b/apps/customer-portal/webapp/src/models/responses.ts @@ -196,3 +196,18 @@ export interface CaseMetadataResponse { severities: MetadataItem[]; caseTypes: MetadataItem[]; } + +// Chat history list item (support chat session summary). +export interface ChatHistoryItem { + chatId: string; + title: string; + startedTime: string; + messages: number; + kbArticles: number; + status: string; +} + +// Response for project chat history list. +export interface ChatHistoryResponse { + chatHistory: ChatHistoryItem[]; +} From fe75bdea4c61492eae8a44304e4387eab9db3d45 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 8 Feb 2026 21:48:58 +0530 Subject: [PATCH 6/6] Extract mock chat history into mockData Move hardcoded chat history out of mockFunctions and into a reusable mockChatHistory export in mockData.ts. Update imports and have getMockChatHistory return the shared mockChatHistory to centralize mock data and avoid duplication. --- .../webapp/src/models/mockData.ts | 47 +++++++++++++++++++ .../webapp/src/models/mockFunctions.ts | 46 +----------------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/apps/customer-portal/webapp/src/models/mockData.ts b/apps/customer-portal/webapp/src/models/mockData.ts index b9f804925c..1e8cb4c424 100644 --- a/apps/customer-portal/webapp/src/models/mockData.ts +++ b/apps/customer-portal/webapp/src/models/mockData.ts @@ -19,6 +19,7 @@ import type { ProjectListItem, ProjectDetails, UserDetails, + ChatHistoryResponse, } from "@models/responses"; import { PROJECT_TYPE, @@ -153,6 +154,52 @@ export const mockOpenCasesOptions: number[] = [0, 5, 12, 24, 48, 72]; // Mock active chats counts for projects. export const mockActiveChatsOptions: number[] = [0, 2, 5, 10, 15, 20]; +// Mock chat history returned when isMockEnabled (useGetChatHistory). +export const mockChatHistory: ChatHistoryResponse = { + chatHistory: [ + { + chatId: "1628192673", + title: "How do I configure custom claims in JWT tokens?", + startedTime: "2 hours ago", + messages: 8, + kbArticles: 3, + status: "Resolved", + }, + { + chatId: "1628192674", + title: "Getting error 401 when calling the API endpoint...", + startedTime: "1 day ago", + messages: 5, + kbArticles: 2, + status: "Still Open", + }, + { + chatId: "1628192675", + title: "Need help understanding rate limiting configuration", + startedTime: "2 days ago", + messages: 12, + kbArticles: 5, + status: "Resolved", + }, + { + chatId: "1628192676", + title: "Can I integrate with Azure AD for authentication?", + startedTime: "3 days ago", + messages: 6, + kbArticles: 4, + status: "Resolved", + }, + { + chatId: "1628192677", + title: "Deployment best practices for high availability setup", + startedTime: "5 days ago", + messages: 15, + kbArticles: 7, + status: "Abandoned", + }, + ], +}; + export const mockUserDetails: UserDetails = { id: "c2d1c8961b2dfe50a002c9d3604bcba0", email: "John@example.com", diff --git a/apps/customer-portal/webapp/src/models/mockFunctions.ts b/apps/customer-portal/webapp/src/models/mockFunctions.ts index 31b1921043..21852a69ea 100644 --- a/apps/customer-portal/webapp/src/models/mockFunctions.ts +++ b/apps/customer-portal/webapp/src/models/mockFunctions.ts @@ -17,6 +17,7 @@ import { mockActiveChatsOptions, mockCaseCreationMetadata, + mockChatHistory, mockOpenCasesOptions, mockStatusOptions, } from "@models/mockData"; @@ -240,50 +241,7 @@ export const getMockDashboardStats = (): DashboardMockStats => { * @returns {ChatHistoryResponse} Mock chat history list. */ export const getMockChatHistory = (): ChatHistoryResponse => { - return { - chatHistory: [ - { - chatId: "1628192673", - title: "How do I configure custom claims in JWT tokens?", - startedTime: "2 hours ago", - messages: 8, - kbArticles: 3, - status: "Resolved", - }, - { - chatId: "1628192674", - title: "Getting error 401 when calling the API endpoint...", - startedTime: "1 day ago", - messages: 5, - kbArticles: 2, - status: "Still Open", - }, - { - chatId: "1628192675", - title: "Need help understanding rate limiting configuration", - startedTime: "2 days ago", - messages: 12, - kbArticles: 5, - status: "Resolved", - }, - { - chatId: "1628192676", - title: "Can I integrate with Azure AD for authentication?", - startedTime: "3 days ago", - messages: 6, - kbArticles: 4, - status: "Resolved", - }, - { - chatId: "1628192677", - title: "Deployment best practices for high availability setup", - startedTime: "5 days ago", - messages: 15, - kbArticles: 7, - status: "Abandoned", - }, - ], - }; + return mockChatHistory; }; /**