22
33import * as vscode from "vscode"
44import { ContextProxy } from "../ContextProxy"
5-
5+ import type { HistoryItem } from "../../../schemas"
66import { GLOBAL_STATE_KEYS , SECRET_STATE_KEYS } from "../../../schemas"
77
88jest . mock ( "vscode" , ( ) => ( {
99 Uri : {
1010 file : jest . fn ( ( path ) => ( { path } ) ) ,
11+ joinPath : jest . fn ( ( base , ...paths ) => ( { path : `${ base . path } /${ paths . join ( "/" ) } ` } ) ) ,
1112 } ,
1213 ExtensionMode : {
1314 Development : 1 ,
1415 Production : 2 ,
1516 Test : 3 ,
1617 } ,
18+ FileSystemError : class extends Error {
19+ code = "FileNotFound"
20+ } ,
21+ window : {
22+ showInformationMessage : jest . fn ( ) ,
23+ } ,
1724} ) )
1825
1926describe ( "ContextProxy" , ( ) => {
@@ -54,6 +61,18 @@ describe("ContextProxy", () => {
5461 // Create proxy instance
5562 proxy = new ContextProxy ( mockContext )
5663 await proxy . initialize ( )
64+
65+ // Ensure fs mock is properly initialized
66+ const mockFs = jest . requireMock ( "fs/promises" )
67+ mockFs . _setInitialMockData ( )
68+
69+ // Use it for vscode.workspace.fs operations
70+ jest . mock ( "vscode" , ( ) => ( {
71+ ...jest . requireMock ( "vscode" ) ,
72+ workspace : {
73+ fs : mockFs ,
74+ } ,
75+ } ) )
5776 } )
5877
5978 describe ( "read-only pass-through properties" , ( ) => {
@@ -102,39 +121,34 @@ describe("ContextProxy", () => {
102121 expect ( result ) . toBe ( "deepseek" )
103122 } )
104123
105- it ( "should bypass cache for pass-through state keys" , async ( ) => {
106- // Setup mock return value
107- mockGlobalState . get . mockReturnValue ( "pass-through-value" )
108-
109- // Use a pass-through key (taskHistory)
110- const result = proxy . getGlobalState ( "taskHistory" )
111-
112- // Should get value directly from original context
113- expect ( result ) . toBe ( "pass-through-value" )
114- expect ( mockGlobalState . get ) . toHaveBeenCalledWith ( "taskHistory" )
115- } )
116-
117- it ( "should respect default values for pass-through state keys" , async ( ) => {
118- // Setup mock to return undefined
119- mockGlobalState . get . mockReturnValue ( undefined )
120-
121- // Use a pass-through key with default value
122- const historyItems = [
124+ it ( "should read task history from file" , async ( ) => {
125+ const mockTasks : HistoryItem [ ] = [
123126 {
124127 id : "1" ,
125128 number : 1 ,
126- ts : 1 ,
129+ ts : Date . now ( ) ,
127130 task : "test" ,
128- tokensIn : 1 ,
129- tokensOut : 1 ,
130- totalCost : 1 ,
131+ tokensIn : 100 ,
132+ tokensOut : 50 ,
133+ totalCost : 0.001 ,
134+ cacheWrites : 0 ,
135+ cacheReads : 0 ,
131136 } ,
132137 ]
133138
134- const result = proxy . getGlobalState ( "taskHistory" , historyItems )
139+ const result = proxy . getGlobalState ( "taskHistory" )
140+ expect ( result ) . toEqual ( mockTasks )
141+ expect ( vscode . workspace . fs . readFile ) . toHaveBeenCalled ( )
142+ } )
143+
144+ it ( "should return empty array when task history file doesn't exist" , async ( ) => {
145+ const vscode = jest . requireMock ( "vscode" )
146+
147+ const error = new vscode . FileSystemError ( "File not found" )
148+ vscode . workspace . fs . readFile . mockRejectedValue ( error )
135149
136- // Should return default value when original context returns undefined
137- expect ( result ) . toBe ( historyItems )
150+ const result = proxy . getGlobalState ( "taskHistory" )
151+ expect ( result ) . toEqual ( [ ] )
138152 } )
139153 } )
140154
@@ -150,31 +164,37 @@ describe("ContextProxy", () => {
150164 expect ( storedValue ) . toBe ( "deepseek" )
151165 } )
152166
153- it ( "should bypass cache for pass-through state keys " , async ( ) => {
154- const historyItems = [
167+ it ( "should write task history to file " , async ( ) => {
168+ const historyItems : HistoryItem [ ] = [
155169 {
156170 id : "1" ,
157171 number : 1 ,
158- ts : 1 ,
172+ ts : Date . now ( ) ,
159173 task : "test" ,
160- tokensIn : 1 ,
161- tokensOut : 1 ,
162- totalCost : 1 ,
174+ tokensIn : 100 ,
175+ tokensOut : 50 ,
176+ totalCost : 0.001 ,
177+ cacheWrites : 0 ,
178+ cacheReads : 0 ,
163179 } ,
164180 ]
165181
166182 await proxy . updateGlobalState ( "taskHistory" , historyItems )
167183
168- // Should update original context
169- expect ( mockGlobalState . update ) . toHaveBeenCalledWith ( "taskHistory" , historyItems )
184+ // Should write to file
185+ const expectedContent = JSON . stringify ( historyItems [ 0 ] ) + "\n"
186+ expect ( vscode . workspace . fs . writeFile ) . toHaveBeenCalledWith (
187+ expect . objectContaining ( { path : expect . stringContaining ( "taskHistory.jsonl" ) } ) ,
188+ Buffer . from ( expectedContent ) ,
189+ )
170190
171- // Setup mock for subsequent get
172- mockGlobalState . get . mockReturnValue ( historyItems )
191+ // Should update cache
192+ expect ( proxy . getGlobalState ( "taskHistory" ) ) . toEqual ( historyItems )
193+ } )
173194
174- // Should get fresh value from original context
175- const storedValue = proxy . getGlobalState ( "taskHistory" )
176- expect ( storedValue ) . toBe ( historyItems )
177- expect ( mockGlobalState . get ) . toHaveBeenCalledWith ( "taskHistory" )
195+ it ( "should handle undefined task history" , async ( ) => {
196+ await proxy . updateGlobalState ( "taskHistory" , undefined )
197+ expect ( vscode . workspace . fs . writeFile ) . not . toHaveBeenCalled ( )
178198 } )
179199 } )
180200
@@ -390,6 +410,13 @@ describe("ContextProxy", () => {
390410 expect ( mockGlobalState . update ) . toHaveBeenCalledTimes ( expectedUpdateCalls )
391411 } )
392412
413+ it ( "should delete task history file when resetting state" , async ( ) => {
414+ await proxy . resetAllState ( )
415+ expect ( vscode . workspace . fs . delete ) . toHaveBeenCalledWith (
416+ expect . objectContaining ( { path : expect . stringContaining ( "taskHistory.jsonl" ) } ) ,
417+ )
418+ } )
419+
393420 it ( "should delete all secrets" , async ( ) => {
394421 // Setup initial secrets
395422 await proxy . storeSecret ( "apiKey" , "test-api-key" )
0 commit comments