1- import * as path from "path"
21import { calculateTaskStorageSize , formatBytes } from "../task-storage-size"
32
43// Mock storage to avoid VS Code config access during tests
@@ -8,13 +7,12 @@ vi.mock("../storage", () => ({
87
98// Mock fs/promises
109const mockReaddir = vi . fn ( )
11- const mockStat = vi . fn ( )
1210
1311vi . mock ( "fs/promises" , ( ) => ( {
1412 readdir : ( ...args : unknown [ ] ) => mockReaddir ( ...args ) ,
15- stat : ( ...args : unknown [ ] ) => mockStat ( ...args ) ,
1613} ) )
1714
15+ // formatBytes is still exported for backwards compatibility but not used by calculateTaskStorageSize
1816describe ( "formatBytes" , ( ) => {
1917 it ( "should format 0 bytes" , ( ) => {
2018 expect ( formatBytes ( 0 ) ) . toBe ( "0 B" )
@@ -58,33 +56,29 @@ describe("calculateTaskStorageSize", () => {
5856 vi . clearAllMocks ( )
5957 } )
6058
61- it ( "should return zeros when tasks directory does not exist" , async ( ) => {
59+ it ( "should return zero count when tasks directory does not exist" , async ( ) => {
6260 mockReaddir . mockRejectedValue ( new Error ( "ENOENT: no such file or directory" ) )
6361
6462 const result = await calculateTaskStorageSize ( "/global/storage" )
6563
6664 expect ( result ) . toEqual ( {
67- totalBytes : 0 ,
6865 taskCount : 0 ,
69- formattedSize : "0 B" ,
7066 } )
7167 } )
7268
73- it ( "should calculate size of empty tasks directory" , async ( ) => {
69+ it ( "should return zero count for empty tasks directory" , async ( ) => {
7470 mockReaddir . mockResolvedValue ( [ ] )
7571
7672 const result = await calculateTaskStorageSize ( "/global/storage" )
7773
7874 expect ( result ) . toEqual ( {
79- totalBytes : 0 ,
8075 taskCount : 0 ,
81- formattedSize : "0 B" ,
8276 } )
8377 } )
8478
8579 it ( "should count task directories correctly" , async ( ) => {
8680 // Mock the tasks directory read
87- mockReaddir . mockImplementation ( ( dirPath : string , options ?: { withFileTypes : boolean } ) => {
81+ mockReaddir . mockImplementation ( ( dirPath : string ) => {
8882 const pathStr = typeof dirPath === "string" ? dirPath : String ( dirPath )
8983 if ( pathStr . endsWith ( "tasks" ) ) {
9084 // Return task directories
@@ -94,7 +88,6 @@ describe("calculateTaskStorageSize", () => {
9488 { name : "task-3" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
9589 ] )
9690 }
97- // Task subdirectories are empty
9891 return Promise . resolve ( [ ] )
9992 } )
10093
@@ -103,140 +96,69 @@ describe("calculateTaskStorageSize", () => {
10396 expect ( result . taskCount ) . toBe ( 3 )
10497 } )
10598
106- it ( "should calculate total size including files" , async ( ) => {
107- // Mock the tasks directory read
99+ it ( "should only count directories, not files in tasks folder" , async ( ) => {
108100 mockReaddir . mockImplementation ( ( dirPath : string ) => {
109101 const pathStr = typeof dirPath === "string" ? dirPath : String ( dirPath )
110102 if ( pathStr . endsWith ( "tasks" ) ) {
111103 return Promise . resolve ( [
112104 { name : "task-1" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
113- ] )
114- }
115- if ( pathStr . includes ( "task-1" ) ) {
116- return Promise . resolve ( [
117- { name : "file1.txt" , isDirectory : ( ) => false , isFile : ( ) => true , isSymbolicLink : ( ) => false } ,
118- { name : "file2.json" , isDirectory : ( ) => false , isFile : ( ) => true , isSymbolicLink : ( ) => false } ,
119- ] )
120- }
121- return Promise . resolve ( [ ] )
122- } )
123-
124- mockStat . mockImplementation ( ( filePath : string ) => {
125- if ( filePath . includes ( "file1.txt" ) ) {
126- return Promise . resolve ( { size : 1024 } )
127- }
128- if ( filePath . includes ( "file2.json" ) ) {
129- return Promise . resolve ( { size : 2048 } )
130- }
131- return Promise . resolve ( { size : 0 } )
132- } )
133-
134- const result = await calculateTaskStorageSize ( "/global/storage" )
135-
136- expect ( result . totalBytes ) . toBe ( 3072 )
137- expect ( result . formattedSize ) . toBe ( "3 KB" )
138- expect ( result . taskCount ) . toBe ( 1 )
139- } )
140-
141- it ( "should handle nested directories (like checkpoints)" , async ( ) => {
142- mockReaddir . mockImplementation ( ( dirPath : string ) => {
143- const pathStr = typeof dirPath === "string" ? dirPath : String ( dirPath )
144- if ( pathStr . endsWith ( "tasks" ) ) {
145- return Promise . resolve ( [
146- { name : "task-1" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
147- ] )
148- }
149- if ( pathStr . endsWith ( "task-1" ) && ! pathStr . includes ( "checkpoints" ) ) {
150- return Promise . resolve ( [
105+ { name : "task-2" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
151106 {
152- name : "api_conversation.json " ,
107+ name : "some-file.txt " ,
153108 isDirectory : ( ) => false ,
154109 isFile : ( ) => true ,
155110 isSymbolicLink : ( ) => false ,
156- } ,
157- { name : "checkpoints" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
158- ] )
159- }
160- if ( pathStr . includes ( "checkpoints" ) ) {
161- return Promise . resolve ( [
111+ } , // Should not count as task
162112 {
163- name : "checkpoint-1 .json" ,
113+ name : "another-file .json" ,
164114 isDirectory : ( ) => false ,
165115 isFile : ( ) => true ,
166116 isSymbolicLink : ( ) => false ,
167- } ,
117+ } , // Should not count as task
168118 ] )
169119 }
170120 return Promise . resolve ( [ ] )
171121 } )
172122
173- mockStat . mockImplementation ( ( filePath : string ) => {
174- if ( filePath . includes ( "api_conversation.json" ) ) {
175- return Promise . resolve ( { size : 5000 } )
176- }
177- if ( filePath . includes ( "checkpoint-1.json" ) ) {
178- return Promise . resolve ( { size : 10000 } )
179- }
180- return Promise . resolve ( { size : 0 } )
181- } )
182-
183123 const result = await calculateTaskStorageSize ( "/global/storage" )
184124
185- expect ( result . totalBytes ) . toBe ( 15000 )
186- expect ( result . taskCount ) . toBe ( 1 )
125+ // Only directories count as tasks
126+ expect ( result . taskCount ) . toBe ( 2 )
187127 } )
188128
189- it ( "should handle stat errors gracefully" , async ( ) => {
129+ it ( "should handle large task counts efficiently (does not recurse into subdirectories)" , async ( ) => {
130+ // Simulate 9000 task directories - this should be fast since we don't recurse
131+ const manyTasks = Array . from ( { length : 9000 } , ( _ , i ) => ( {
132+ name : `task-${ i } ` ,
133+ isDirectory : ( ) => true ,
134+ isFile : ( ) => false ,
135+ isSymbolicLink : ( ) => false ,
136+ } ) )
137+
190138 mockReaddir . mockImplementation ( ( dirPath : string ) => {
191139 const pathStr = typeof dirPath === "string" ? dirPath : String ( dirPath )
192140 if ( pathStr . endsWith ( "tasks" ) ) {
193- return Promise . resolve ( [
194- { name : "task-1" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
195- ] )
141+ return Promise . resolve ( manyTasks )
196142 }
197- return Promise . resolve ( [
198- { name : "broken-file.txt" , isDirectory : ( ) => false , isFile : ( ) => true , isSymbolicLink : ( ) => false } ,
199- ] )
143+ return Promise . resolve ( [ ] )
200144 } )
201145
202- mockStat . mockRejectedValue ( new Error ( "Permission denied" ) )
203-
146+ const startTime = Date . now ( )
204147 const result = await calculateTaskStorageSize ( "/global/storage" )
148+ const elapsed = Date . now ( ) - startTime
205149
206- // Should still return a result, just with 0 bytes for the failed stat
207- expect ( result . taskCount ) . toBe ( 1 )
208- expect ( result . totalBytes ) . toBe ( 0 )
150+ expect ( result . taskCount ) . toBe ( 9000 )
151+ // Should complete quickly since we're not recursing into directories
152+ expect ( elapsed ) . toBeLessThan ( 100 ) // Should be nearly instant
209153 } )
210154
211- it ( "should handle mixed files and directories in tasks folder" , async ( ) => {
212- mockReaddir . mockImplementation ( ( dirPath : string ) => {
213- const pathStr = typeof dirPath === "string" ? dirPath : String ( dirPath )
214- if ( pathStr . endsWith ( "tasks" ) ) {
215- return Promise . resolve ( [
216- { name : "task-1" , isDirectory : ( ) => true , isFile : ( ) => false , isSymbolicLink : ( ) => false } ,
217- {
218- name : "some-file.txt" ,
219- isDirectory : ( ) => false ,
220- isFile : ( ) => true ,
221- isSymbolicLink : ( ) => false ,
222- } , // Should not count as task
223- ] )
224- }
225- return Promise . resolve ( [ ] )
226- } )
227-
228- mockStat . mockImplementation ( ( filePath : string ) => {
229- if ( filePath . includes ( "some-file.txt" ) ) {
230- return Promise . resolve ( { size : 100 } )
231- }
232- return Promise . resolve ( { size : 0 } )
233- } )
155+ it ( "should handle readdir errors gracefully" , async ( ) => {
156+ mockReaddir . mockRejectedValue ( new Error ( "Permission denied" ) )
234157
235158 const result = await calculateTaskStorageSize ( "/global/storage" )
236159
237- // Only directories count as tasks
238- expect ( result . taskCount ) . toBe ( 1 )
239- // But file size should be included
240- expect ( result . totalBytes ) . toBe ( 100 )
160+ expect ( result ) . toEqual ( {
161+ taskCount : 0 ,
162+ } )
241163 } )
242164} )
0 commit comments