@@ -24,11 +24,24 @@ test.beforeEach(async (t) => {
2424 sinon . stub ( Configuration , "fromFile" ) . resolves ( new Configuration ( { } ) ) ;
2525
2626 t . context . cleanCacheStub = sinon . stub ( ) ;
27+ t . context . getCacheInfoStub = sinon . stub ( ) ;
28+
29+ // Mock readline to simulate user confirmation
30+ const mockRLInterface = {
31+ question : sinon . stub ( ) ,
32+ close : sinon . stub ( )
33+ } ;
34+ t . context . readlineCreateInterfaceStub = sinon . stub ( ) . returns ( mockRLInterface ) ;
35+ t . context . mockRLInterface = mockRLInterface ;
2736
2837 t . context . cache = await esmock . p ( "../../../../lib/cli/commands/cache.js" , {
2938 "@ui5/project/config/Configuration" : t . context . Configuration ,
3039 "@ui5/project/build/cache/CacheCleanup" : {
3140 cleanCache : t . context . cleanCacheStub ,
41+ getCacheInfo : t . context . getCacheInfoStub ,
42+ } ,
43+ "node:readline" : {
44+ createInterface : t . context . readlineCreateInterfaceStub ,
3245 } ,
3346 } ) ;
3447} ) ;
@@ -38,24 +51,53 @@ test.afterEach.always((t) => {
3851 esmock . purge ( t . context . cache ) ;
3952} ) ;
4053
54+ test ( "Command builder" , async ( t ) => {
55+ // Import cache module directly for builder test (before beforeEach stubs are created)
56+ const cacheModule = await import ( "../../../../lib/cli/commands/cache.js" ) ;
57+ const cliStub = {
58+ demandCommand : sinon . stub ( ) . returnsThis ( ) ,
59+ command : sinon . stub ( ) . returnsThis ( ) ,
60+ example : sinon . stub ( ) . returnsThis ( ) ,
61+ } ;
62+ const result = cacheModule . default . builder ( cliStub ) ;
63+ t . is ( result , cliStub , "Builder returns cli instance" ) ;
64+ t . is ( cliStub . demandCommand . callCount , 1 , "demandCommand called once" ) ;
65+ t . is ( cliStub . command . callCount , 1 , "command called once" ) ;
66+ t . is ( cliStub . example . callCount , 1 , "example called once" ) ;
67+ } ) ;
68+
4169test . serial ( "ui5 cache clean: nothing to clean" , async ( t ) => {
42- const { cache, argv, stderrWriteStub, cleanCacheStub} = t . context ;
70+ const { cache, argv, stderrWriteStub, cleanCacheStub, getCacheInfoStub } = t . context ;
4371
44- cleanCacheStub . resolves ( { entries : [ ] , totalSize : 0 , totalCount : 0 } ) ;
72+ // Simulate no cache items
73+ getCacheInfoStub . resolves ( [ ] ) ;
4574
4675 argv [ "_" ] = [ "cache" , "clean" ] ;
4776 await cache . handler ( argv ) ;
4877
4978 t . is ( stderrWriteStub . firstCall . firstArg , "Nothing to clean\n" ) ;
79+ t . is ( cleanCacheStub . callCount , 0 , "cleanCache should not be called" ) ;
5080} ) ;
5181
5282test . serial ( "ui5 cache clean: removes entries and reports" , async ( t ) => {
53- const { cache, argv, stderrWriteStub, cleanCacheStub} = t . context ;
83+ const { cache, argv, stderrWriteStub, cleanCacheStub, getCacheInfoStub,
84+ mockRLInterface} = t . context ;
85+
86+ // Simulate existing cache items
87+ getCacheInfoStub . resolves ( [
88+ { path : "framework/" , size : 15 * 1024 * 1024 , type : "directory" } ,
89+ { path : "buildCache/ (database records)" , size : 8 * 1024 * 1024 , type : "database" } ,
90+ ] ) ;
91+
92+ // Mock user confirmation
93+ mockRLInterface . question . callsFake ( ( question , callback ) => {
94+ callback ( "y" ) ;
95+ } ) ;
5496
5597 cleanCacheStub . resolves ( {
5698 entries : [
57- { path : "@openui5/sap.ui.core/1.120.0 " , type : "framework" , size : 15 * 1024 * 1024 } ,
58- { path : "@openui5/sap.m/1.120.0 " , type : "framework " , size : 8 * 1024 * 1024 } ,
99+ { path : "framework " , type : "framework" , size : 15 * 1024 * 1024 } ,
100+ { path : "buildCache " , type : "buildCache " , size : 8 * 1024 * 1024 } ,
59101 ] ,
60102 totalSize : 23 * 1024 * 1024 ,
61103 totalCount : 2 ,
@@ -64,18 +106,156 @@ test.serial("ui5 cache clean: removes entries and reports", async (t) => {
64106 argv [ "_" ] = [ "cache" , "clean" ] ;
65107 await cache . handler ( argv ) ;
66108
67- // Should have 4 writes: 2 entries + 1 newline + summary
68- t . true ( stderrWriteStub . callCount >= 3 , "Multiple lines written to stderr" ) ;
69- // Check that summary mentions entries count
109+ // Check that confirmation was asked
110+ t . is ( mockRLInterface . question . callCount , 1 , "Should ask for confirmation" ) ;
111+ t . true ( mockRLInterface . question . firstCall . args [ 0 ] . includes ( "continue" ) ,
112+ "Confirmation question should ask to continue" ) ;
113+
114+ // Check that cleanCache was called
115+ t . is ( cleanCacheStub . callCount , 1 , "cleanCache should be called once" ) ;
116+
117+ // Check output
70118 const allOutput = stderrWriteStub . args . map ( ( a ) => a [ 0 ] ) . join ( "" ) ;
119+ t . true ( allOutput . includes ( "following items from cache will be removed" ) , "Shows items to be removed" ) ;
71120 t . true ( allOutput . includes ( "2 entries" ) , "Summary mentions entry count" ) ;
72- t . true ( allOutput . includes ( "23.0 MB " ) , "Summary mentions freed size " ) ;
121+ t . true ( allOutput . includes ( "Success " ) , "Shows success message " ) ;
73122} ) ;
74123
75- test ( "Command definition is correct" , ( t ) => {
124+ test . serial ( "ui5 cache clean: user cancels" , async ( t ) => {
125+ const { cache, argv, stderrWriteStub, cleanCacheStub, getCacheInfoStub,
126+ mockRLInterface} = t . context ;
127+
128+ // Simulate existing cache items
129+ getCacheInfoStub . resolves ( [
130+ { path : "framework/" , size : 5 * 1024 * 1024 , type : "directory" }
131+ ] ) ;
132+
133+ // Mock user cancellation
134+ mockRLInterface . question . callsFake ( ( question , callback ) => {
135+ callback ( "n" ) ;
136+ } ) ;
137+
138+ argv [ "_" ] = [ "cache" , "clean" ] ;
139+ await cache . handler ( argv ) ;
140+
141+ // Check that confirmation was asked
142+ t . is ( mockRLInterface . question . callCount , 1 , "Should ask for confirmation" ) ;
143+
144+ // Check that cleanCache was NOT called
145+ t . is ( cleanCacheStub . callCount , 0 , "cleanCache should not be called when user cancels" ) ;
146+
147+ // Check output
148+ const allOutput = stderrWriteStub . args . map ( ( a ) => a [ 0 ] ) . join ( "" ) ;
149+ t . true ( allOutput . includes ( "following items from cache will be removed" ) , "Shows items to be removed" ) ;
150+ t . true ( allOutput . includes ( "Cancelled" ) , "Shows cancelled message" ) ;
151+ t . false ( allOutput . includes ( "Success" ) , "Should not show success message" ) ;
152+ } ) ;
153+
154+ test . serial ( "Command definition is correct" , ( t ) => {
76155 // Import without esmock for structure check
77156 t . is ( t . context . cache . command , "cache" ) ;
78157 t . is ( t . context . cache . describe , "Manage UI5 CLI cache" ) ;
79158 t . is ( typeof t . context . cache . builder , "function" ) ;
80159 t . is ( typeof t . context . cache . handler , "function" ) ;
81160} ) ;
161+
162+ test . serial ( "ui5 cache clean: accepts 'yes' as confirmation" , async ( t ) => {
163+ const { cache, argv, cleanCacheStub, getCacheInfoStub, mockRLInterface} = t . context ;
164+
165+ getCacheInfoStub . resolves ( [
166+ { path : "framework/" , size : 1024 , type : "directory" }
167+ ] ) ;
168+
169+ mockRLInterface . question . callsFake ( ( question , callback ) => {
170+ callback ( "yes" ) ;
171+ } ) ;
172+
173+ cleanCacheStub . resolves ( {
174+ entries : [ { path : "framework" , type : "framework" , size : 1024 } ] ,
175+ totalSize : 1024 ,
176+ totalCount : 1 ,
177+ } ) ;
178+
179+ argv [ "_" ] = [ "cache" , "clean" ] ;
180+ await cache . handler ( argv ) ;
181+
182+ t . is ( cleanCacheStub . callCount , 1 , "cleanCache should be called with 'yes' confirmation" ) ;
183+ } ) ;
184+
185+ test . serial ( "ui5 cache clean: formats byte sizes correctly" , async ( t ) => {
186+ const { cache, argv, stderrWriteStub, cleanCacheStub, getCacheInfoStub, mockRLInterface} = t . context ;
187+
188+ // Test with small bytes (B), KB, and GB sizes
189+ getCacheInfoStub . resolves ( [
190+ { path : "small" , size : 512 , type : "directory" } , // < 1024 = B
191+ { path : "medium" , size : 50 * 1024 , type : "directory" } , // KB
192+ { path : "large" , size : 2 * 1024 * 1024 * 1024 , type : "directory" } , // GB
193+ ] ) ;
194+
195+ mockRLInterface . question . callsFake ( ( question , callback ) => {
196+ callback ( "y" ) ;
197+ } ) ;
198+
199+ cleanCacheStub . resolves ( {
200+ entries : [
201+ { path : "small" , type : "directory" , size : 512 } ,
202+ { path : "medium" , type : "directory" , size : 50 * 1024 } ,
203+ { path : "large" , type : "directory" , size : 2 * 1024 * 1024 * 1024 } ,
204+ ] ,
205+ totalSize : 2 * 1024 * 1024 * 1024 + 50 * 1024 + 512 ,
206+ totalCount : 3 ,
207+ } ) ;
208+
209+ argv [ "_" ] = [ "cache" , "clean" ] ;
210+ await cache . handler ( argv ) ;
211+
212+ const allOutput = stderrWriteStub . args . map ( ( a ) => a [ 0 ] ) . join ( "" ) ;
213+ t . true ( allOutput . includes ( "512 B" ) , "Shows bytes format" ) ;
214+ t . true ( allOutput . includes ( "50.0 KB" ) , "Shows KB format" ) ;
215+ t . true ( allOutput . includes ( "2.0 GB" ) , "Shows GB format" ) ;
216+ } ) ;
217+
218+ test . serial ( "ui5 cache clean: uses UI5_DATA_DIR from environment" , async ( t ) => {
219+ const { cache, argv, getCacheInfoStub} = t . context ;
220+ const originalEnv = process . env . UI5_DATA_DIR ;
221+
222+ try {
223+ process . env . UI5_DATA_DIR = "/custom/ui5/path" ;
224+
225+ getCacheInfoStub . resolves ( [ ] ) ;
226+
227+ argv [ "_" ] = [ "cache" , "clean" ] ;
228+ await cache . handler ( argv ) ;
229+
230+ t . is ( getCacheInfoStub . callCount , 1 , "getCacheInfo called" ) ;
231+ t . true ( getCacheInfoStub . firstCall . args [ 0 ] . ui5DataDir . includes ( "custom/ui5/path" ) ,
232+ "Uses environment variable path" ) ;
233+ } finally {
234+ if ( originalEnv ) {
235+ process . env . UI5_DATA_DIR = originalEnv ;
236+ } else {
237+ delete process . env . UI5_DATA_DIR ;
238+ }
239+ }
240+ } ) ;
241+
242+ test . serial ( "ui5 cache clean: uses config.getUi5DataDir when no env var" , async ( t ) => {
243+ const { cache, argv, getCacheInfoStub, Configuration} = t . context ;
244+ const originalEnv = process . env . UI5_DATA_DIR ;
245+
246+ try {
247+ delete process . env . UI5_DATA_DIR ;
248+
249+ Configuration . fromFile . resolves ( new Configuration ( { ui5DataDir : "/config/path" } ) ) ;
250+ getCacheInfoStub . resolves ( [ ] ) ;
251+
252+ argv [ "_" ] = [ "cache" , "clean" ] ;
253+ await cache . handler ( argv ) ;
254+
255+ t . is ( getCacheInfoStub . callCount , 1 , "getCacheInfo called" ) ;
256+ } finally {
257+ if ( originalEnv ) {
258+ process . env . UI5_DATA_DIR = originalEnv ;
259+ }
260+ }
261+ } ) ;
0 commit comments