@@ -5,7 +5,11 @@ import type { ChatMessage } from '../llmClient';
55const fetchMock = vi . fn ( ) ;
66vi . stubGlobal ( 'fetch' , fetchMock ) ;
77
8- const STORAGE_KEY = 'webuiapps-chat-history' ;
8+ const SESSION_PATH = 'char-1/mod-1' ;
9+
10+ function expectedUrl ( file : string ) : string {
11+ return `/api/session-data?path=${ encodeURIComponent ( `${ SESSION_PATH } /chat/${ file } ` ) } ` ;
12+ }
913
1014const sampleMessages : DisplayMessage [ ] = [
1115 { id : '1' , role : 'user' , content : 'Hello' } ,
@@ -24,157 +28,101 @@ function makeSavedData(msgs = sampleMessages, history = sampleChatHistory): Chat
2428describe ( 'chatHistoryStorage' , ( ) => {
2529 beforeEach ( ( ) => {
2630 fetchMock . mockReset ( ) ;
27- localStorage . clear ( ) ;
2831 vi . resetModules ( ) ;
2932 } ) ;
3033
31- // ============ loadChatHistorySync ============
32-
3334 describe ( 'loadChatHistorySync' , ( ) => {
34- it ( 'returns null when localStorage is empty' , async ( ) => {
35- const { loadChatHistorySync } = await import ( '../chatHistoryStorage' ) ;
36- expect ( loadChatHistorySync ( ) ) . toBeNull ( ) ;
37- } ) ;
38-
39- it ( 'returns data from localStorage' , async ( ) => {
40- const data = makeSavedData ( ) ;
41- localStorage . setItem ( STORAGE_KEY , JSON . stringify ( data ) ) ;
42- const { loadChatHistorySync } = await import ( '../chatHistoryStorage' ) ;
43- const result = loadChatHistorySync ( ) ;
44- expect ( result ) . not . toBeNull ( ) ;
45- expect ( result ! . messages ) . toHaveLength ( 2 ) ;
46- expect ( result ! . chatHistory ) . toHaveLength ( 2 ) ;
47- expect ( result ! . version ) . toBe ( 1 ) ;
48- } ) ;
49-
50- it ( 'returns null for invalid JSON' , async ( ) => {
51- localStorage . setItem ( STORAGE_KEY , 'not-json' ) ;
52- const { loadChatHistorySync } = await import ( '../chatHistoryStorage' ) ;
53- expect ( loadChatHistorySync ( ) ) . toBeNull ( ) ;
54- } ) ;
55-
56- it ( 'returns null for wrong version' , async ( ) => {
57- localStorage . setItem (
58- STORAGE_KEY ,
59- JSON . stringify ( { version : 99 , savedAt : 0 , messages : [ ] , chatHistory : [ ] } ) ,
60- ) ;
35+ it ( 'returns null' , async ( ) => {
6136 const { loadChatHistorySync } = await import ( '../chatHistoryStorage' ) ;
62- expect ( loadChatHistorySync ( ) ) . toBeNull ( ) ;
37+ expect ( loadChatHistorySync ( SESSION_PATH ) ) . toBeNull ( ) ;
6338 } ) ;
6439 } ) ;
6540
66- // ============ loadChatHistory (async) ============
67-
6841 describe ( 'loadChatHistory' , ( ) => {
69- it ( 'loads from API and syncs to localStorage ' , async ( ) => {
42+ it ( 'loads from API' , async ( ) => {
7043 const data = makeSavedData ( ) ;
7144 fetchMock . mockResolvedValueOnce ( {
7245 ok : true ,
7346 json : ( ) => Promise . resolve ( data ) ,
7447 } ) ;
7548 const { loadChatHistory } = await import ( '../chatHistoryStorage' ) ;
7649
77- const result = await loadChatHistory ( ) ;
50+ const result = await loadChatHistory ( SESSION_PATH ) ;
7851
79- expect ( fetchMock ) . toHaveBeenCalledWith ( '/api/ chat-history' ) ;
52+ expect ( fetchMock ) . toHaveBeenCalledWith ( expectedUrl ( ' chat.json' ) ) ;
8053 expect ( result ) . not . toBeNull ( ) ;
8154 expect ( result ! . messages ) . toEqual ( sampleMessages ) ;
82- // Verify synced to localStorage
83- const stored = JSON . parse ( localStorage . getItem ( STORAGE_KEY ) ! ) ;
84- expect ( stored . version ) . toBe ( 1 ) ;
8555 } ) ;
8656
87- it ( 'falls back to localStorage when API returns non-ok' , async ( ) => {
88- const data = makeSavedData ( ) ;
89- localStorage . setItem ( STORAGE_KEY , JSON . stringify ( data ) ) ;
57+ it ( 'returns null when API returns non-ok' , async ( ) => {
9058 fetchMock . mockResolvedValueOnce ( { ok : false , status : 404 } ) ;
9159 const { loadChatHistory } = await import ( '../chatHistoryStorage' ) ;
9260
93- const result = await loadChatHistory ( ) ;
61+ const result = await loadChatHistory ( SESSION_PATH ) ;
9462
95- expect ( result ) . not . toBeNull ( ) ;
96- expect ( result ! . messages ) . toEqual ( sampleMessages ) ;
63+ expect ( result ) . toBeNull ( ) ;
9764 } ) ;
9865
99- it ( 'falls back to localStorage when fetch throws' , async ( ) => {
100- const data = makeSavedData ( ) ;
101- localStorage . setItem ( STORAGE_KEY , JSON . stringify ( data ) ) ;
66+ it ( 'returns null when fetch throws' , async ( ) => {
10267 fetchMock . mockRejectedValueOnce ( new Error ( 'network error' ) ) ;
10368 const { loadChatHistory } = await import ( '../chatHistoryStorage' ) ;
10469
105- const result = await loadChatHistory ( ) ;
70+ const result = await loadChatHistory ( SESSION_PATH ) ;
10671
107- expect ( result ) . not . toBeNull ( ) ;
108- expect ( result ! . messages ) . toEqual ( sampleMessages ) ;
72+ expect ( result ) . toBeNull ( ) ;
10973 } ) ;
11074
111- it ( 'returns null when both API and localStorage are empty' , async ( ) => {
75+ it ( 'returns null when API is empty' , async ( ) => {
11276 fetchMock . mockResolvedValueOnce ( { ok : false , status : 404 } ) ;
11377 const { loadChatHistory } = await import ( '../chatHistoryStorage' ) ;
11478
115- const result = await loadChatHistory ( ) ;
79+ const result = await loadChatHistory ( SESSION_PATH ) ;
11680 expect ( result ) . toBeNull ( ) ;
11781 } ) ;
11882 } ) ;
11983
120- // ============ saveChatHistory ============
121-
12284 describe ( 'saveChatHistory' , ( ) => {
123- it ( 'saves to localStorage and POSTs to API ' , async ( ) => {
85+ it ( 'POSTs to API with expected payload ' , async ( ) => {
12486 fetchMock . mockResolvedValueOnce ( { ok : true } ) ;
12587 const { saveChatHistory } = await import ( '../chatHistoryStorage' ) ;
12688
127- await saveChatHistory ( sampleMessages , sampleChatHistory ) ;
128-
129- // Check localStorage
130- const stored = JSON . parse ( localStorage . getItem ( STORAGE_KEY ) ! ) ;
131- expect ( stored . version ) . toBe ( 1 ) ;
132- expect ( stored . messages ) . toEqual ( sampleMessages ) ;
133- expect ( stored . chatHistory ) . toEqual ( sampleChatHistory ) ;
134- expect ( typeof stored . savedAt ) . toBe ( 'number' ) ;
89+ await saveChatHistory ( SESSION_PATH , sampleMessages , sampleChatHistory ) ;
13590
136- // Check fetch call
13791 expect ( fetchMock ) . toHaveBeenCalledOnce ( ) ;
13892 const [ url , options ] = fetchMock . mock . calls [ 0 ] ;
139- expect ( url ) . toBe ( '/api/ chat-history' ) ;
93+ expect ( url ) . toBe ( expectedUrl ( ' chat.json' ) ) ;
14094 expect ( options . method ) . toBe ( 'POST' ) ;
14195 const body = JSON . parse ( options . body ) ;
14296 expect ( body . version ) . toBe ( 1 ) ;
97+ expect ( body . messages ) . toEqual ( sampleMessages ) ;
98+ expect ( body . chatHistory ) . toEqual ( sampleChatHistory ) ;
14399 } ) ;
144100
145- it ( 'saves to localStorage even when fetch fails' , async ( ) => {
101+ it ( 'does not throw when fetch fails' , async ( ) => {
146102 fetchMock . mockRejectedValueOnce ( new Error ( 'network error' ) ) ;
147103 const { saveChatHistory } = await import ( '../chatHistoryStorage' ) ;
148104
149- await saveChatHistory ( sampleMessages , sampleChatHistory ) ;
150-
151- const stored = JSON . parse ( localStorage . getItem ( STORAGE_KEY ) ! ) ;
152- expect ( stored . messages ) . toEqual ( sampleMessages ) ;
105+ await expect (
106+ saveChatHistory ( SESSION_PATH , sampleMessages , sampleChatHistory ) ,
107+ ) . resolves . toBeUndefined ( ) ;
153108 } ) ;
154109 } ) ;
155110
156- // ============ clearChatHistory ============
157-
158111 describe ( 'clearChatHistory' , ( ) => {
159- it ( 'removes from localStorage and sends DELETE to API' , async ( ) => {
160- localStorage . setItem ( STORAGE_KEY , JSON . stringify ( makeSavedData ( ) ) ) ;
112+ it ( 'sends DELETE to API' , async ( ) => {
161113 fetchMock . mockResolvedValueOnce ( { ok : true } ) ;
162114 const { clearChatHistory } = await import ( '../chatHistoryStorage' ) ;
163115
164- await clearChatHistory ( ) ;
116+ await clearChatHistory ( SESSION_PATH ) ;
165117
166- expect ( localStorage . getItem ( STORAGE_KEY ) ) . toBeNull ( ) ;
167- expect ( fetchMock ) . toHaveBeenCalledWith ( '/api/chat-history' , { method : 'DELETE' } ) ;
118+ expect ( fetchMock ) . toHaveBeenCalledWith ( expectedUrl ( 'chat.json' ) , { method : 'DELETE' } ) ;
168119 } ) ;
169120
170- it ( 'clears localStorage even when DELETE fetch fails' , async ( ) => {
171- localStorage . setItem ( STORAGE_KEY , JSON . stringify ( makeSavedData ( ) ) ) ;
121+ it ( 'does not throw when DELETE fetch fails' , async ( ) => {
172122 fetchMock . mockRejectedValueOnce ( new Error ( 'network error' ) ) ;
173123 const { clearChatHistory } = await import ( '../chatHistoryStorage' ) ;
174124
175- await clearChatHistory ( ) ;
176-
177- expect ( localStorage . getItem ( STORAGE_KEY ) ) . toBeNull ( ) ;
125+ await expect ( clearChatHistory ( SESSION_PATH ) ) . resolves . toBeUndefined ( ) ;
178126 } ) ;
179127 } ) ;
180128} ) ;
0 commit comments