@@ -245,7 +245,143 @@ describe('Sandbox Lifecycle E2E Tests', function () {
245245 } ) ;
246246 } ) ;
247247
248- describe ( 'Step 8: Delete Sandbox' , function ( ) {
248+ describe ( 'Step 8: Sandbox Usage' , function ( ) {
249+ it ( 'should retrieve sandbox usage in JSON format' , async function ( ) {
250+ // Skip if we don't have a valid sandbox ID
251+ if ( ! sandboxId ) {
252+ this . skip ( ) ;
253+ }
254+
255+ const result = await runCLIWithRetry ( [ 'sandbox' , 'usage' , sandboxId , '--json' ] , { verbose : true } ) ;
256+
257+ expect ( result . exitCode , `Usage failed: ${ toString ( result . stderr ) } ` ) . to . equal ( 0 ) ;
258+ expect ( result . stdout , 'Usage command should return JSON output' ) . to . not . be . empty ;
259+
260+ const response = parseJSONOutput ( result ) ;
261+ expect ( response , 'Usage response should be a valid object' ) . to . be . an ( 'object' ) ;
262+
263+ // Response can be either a usage model or a wrapper with data property
264+ const usage : any = 'data' in response ? ( response as any ) . data : response ;
265+ if ( usage && typeof usage === 'object' ) {
266+ if ( usage . sandboxSeconds !== undefined ) {
267+ expect ( usage . sandboxSeconds , 'sandboxSeconds should be a number when present' ) . to . be . a ( 'number' ) ;
268+ }
269+ if ( usage . minutesUp !== undefined ) {
270+ expect ( usage . minutesUp , 'minutesUp should be a number when present' ) . to . be . a ( 'number' ) ;
271+ }
272+ if ( usage . minutesDown !== undefined ) {
273+ expect ( usage . minutesDown , 'minutesDown should be a number when present' ) . to . be . a ( 'number' ) ;
274+ }
275+
276+ // Some backends may also provide aggregate sandbox counters; validate types when available
277+ if ( usage . activeSandboxes !== undefined ) {
278+ expect ( usage . activeSandboxes , 'activeSandboxes should be a number when present' ) . to . be . a ( 'number' ) ;
279+ }
280+ if ( usage . createdSandboxes !== undefined ) {
281+ expect ( usage . createdSandboxes , 'createdSandboxes should be a number when present' ) . to . be . a ( 'number' ) ;
282+ }
283+ if ( usage . deletedSandboxes !== undefined ) {
284+ expect ( usage . deletedSandboxes , 'deletedSandboxes should be a number when present' ) . to . be . a ( 'number' ) ;
285+ }
286+ }
287+ } ) ;
288+ } ) ;
289+
290+ describe ( 'Step 9: Sandbox Aliases' , function ( ) {
291+ let createdAliasId : string | undefined ;
292+ let createdAliasHostname : string | undefined ;
293+
294+ it ( 'should create an alias for the sandbox' , async function ( ) {
295+ // Skip if we don't have a valid sandbox ID
296+ if ( ! sandboxId ) {
297+ this . skip ( ) ;
298+ }
299+
300+ // Use a short, unique hostname per test run to avoid collisions and
301+ // stay well under Kubernetes label length limits (63 characters).
302+ const suffix = Date . now ( ) . toString ( 36 ) ;
303+ const aliasHostname = `e2e-${ suffix } .example.com` ;
304+
305+ const createResult = await runCLIWithRetry ( [ 'sandbox' , 'alias' , 'create' , sandboxId , aliasHostname , '--json' ] , {
306+ verbose : true ,
307+ } ) ;
308+
309+ expect ( createResult . exitCode , `Alias create failed: ${ toString ( createResult . stderr ) } ` ) . to . equal ( 0 ) ;
310+
311+ const createResponse = parseJSONOutput ( createResult ) as any ;
312+ expect ( createResponse , 'Alias create response should be an object' ) . to . be . an ( 'object' ) ;
313+ expect ( createResponse ) . to . have . property ( 'id' ) ;
314+ expect ( createResponse ) . to . have . property ( 'name' ) ;
315+
316+ createdAliasId = createResponse . id as string ;
317+ createdAliasHostname = createResponse . name as string ;
318+ } ) ;
319+
320+ it ( 'should list aliases for the sandbox including the created alias' , async function ( ) {
321+ // Skip if we don't have a valid sandbox ID or no alias was created
322+ if ( ! sandboxId || ! createdAliasId ) {
323+ this . skip ( ) ;
324+ }
325+
326+ const result = await runCLIWithRetry ( [ 'sandbox' , 'alias' , 'list' , sandboxId , '--json' ] , { verbose : true } ) ;
327+
328+ expect ( result . exitCode , `Alias list failed: ${ toString ( result . stderr ) } ` ) . to . equal ( 0 ) ;
329+
330+ const response = parseJSONOutput ( result ) ;
331+ // sandbox alias list --json returns an array of alias objects (possibly empty)
332+ expect ( response , 'Alias list response should be an array' ) . to . be . an ( 'array' ) ;
333+
334+ if ( Array . isArray ( response ) && response . length > 0 ) {
335+ const found = ( response as any [ ] ) . find (
336+ ( alias ) => alias . id === createdAliasId || alias . name === createdAliasHostname ,
337+ ) ;
338+ expect ( found , 'Expected alias list to include the created alias' ) . to . exist ;
339+ }
340+ } ) ;
341+
342+ it ( 'should delete the created alias for the sandbox' , async function ( ) {
343+ // Skip if we don't have a valid sandbox ID or no alias was created
344+ if ( ! sandboxId || ! createdAliasId ) {
345+ this . skip ( ) ;
346+ }
347+
348+ const deleteResult = await runCLIWithRetry (
349+ [ 'sandbox' , 'alias' , 'delete' , sandboxId , createdAliasId , '--force' , '--json' ] ,
350+ { verbose : true } ,
351+ ) ;
352+
353+ expect ( deleteResult . exitCode , `Alias delete failed: ${ toString ( deleteResult . stderr ) } ` ) . to . equal ( 0 ) ;
354+
355+ const deleteResponse = parseJSONOutput ( deleteResult ) as any ;
356+ expect ( deleteResponse ) . to . have . property ( 'success' , true ) ;
357+ } ) ;
358+ } ) ;
359+
360+ describe ( 'Step 10: Reset Sandbox' , function ( ) {
361+ it ( 'should trigger a sandbox reset operation in JSON mode' , async function ( ) {
362+ this . timeout ( TIMEOUTS . ODS_OPERATION ) ;
363+
364+ // Skip if we don't have a valid sandbox ID
365+ if ( ! sandboxId ) {
366+ this . skip ( ) ;
367+ }
368+
369+ const result = await runCLIWithRetry (
370+ [ 'sandbox' , 'reset' , sandboxId , '--wait' , '--poll-interval' , '10' , '--timeout' , '600' , '--force' , '--json' ] ,
371+ { timeout : TIMEOUTS . ODS_OPERATION , verbose : true } ,
372+ ) ;
373+
374+ expect ( result . exitCode , `Reset failed: ${ toString ( result . stderr ) } ` ) . to . equal ( 0 ) ;
375+ expect ( result . stdout , 'Reset command should return JSON output' ) . to . not . be . empty ;
376+
377+ const response = parseJSONOutput ( result ) ;
378+ expect ( response , 'Reset response should be a valid object' ) . to . be . an ( 'object' ) ;
379+ expect ( response ) . to . have . property ( 'operationState' ) ;
380+ expect ( response ) . to . have . property ( 'sandboxState' ) ;
381+ } ) ;
382+ } ) ;
383+
384+ describe ( 'Step 11: Delete Sandbox' , function ( ) {
249385 it ( 'should delete the sandbox' , async function ( ) {
250386 // Skip if we don't have a valid sandbox ID
251387 if ( ! sandboxId ) {
@@ -299,6 +435,66 @@ describe('Sandbox Lifecycle E2E Tests', function () {
299435 ) ;
300436 } ) ;
301437 } ) ;
438+
439+ describe ( 'Realm Management' , function ( ) {
440+ const realmId = process . env . TEST_REALM ;
441+
442+ before ( function ( ) {
443+ if ( ! realmId ) {
444+ this . skip ( ) ;
445+ }
446+ } ) ;
447+
448+ it ( 'should get realm details' , async function ( ) {
449+ const result = await runCLIWithRetry ( [ 'sandbox' , 'realm' , 'get' , realmId ! , '--json' ] , { verbose : true } ) ;
450+
451+ expect ( result . exitCode , `Realm get failed: ${ toString ( result . stderr ) } ` ) . to . equal ( 0 ) ;
452+
453+ const response = parseJSONOutput ( result ) ;
454+ expect ( response , 'Realm get response should be a valid object' ) . to . be . an ( 'object' ) ;
455+ expect ( response ) . to . have . property ( 'realm' ) ;
456+ } ) ;
457+
458+ it ( 'should fetch realm usage in JSON format' , async function ( ) {
459+ const result = await runCLIWithRetry ( [ 'sandbox' , 'realm' , 'usage' , realmId ! , '--json' ] , { verbose : true } ) ;
460+
461+ expect ( result . exitCode , `Realm usage failed: ${ toString ( result . stderr ) } ` ) . to . equal ( 0 ) ;
462+
463+ const response = parseJSONOutput ( result ) ;
464+ expect ( response , 'Realm usage response should be a valid object' ) . to . be . an ( 'object' ) ;
465+
466+ const usage : any = 'data' in response ? ( response as any ) . data : response ;
467+ if ( usage && typeof usage === 'object' ) {
468+ if ( usage . activeSandboxes !== undefined ) {
469+ expect ( usage . activeSandboxes , 'activeSandboxes should be a number when present' ) . to . be . a ( 'number' ) ;
470+ }
471+ if ( usage . createdSandboxes !== undefined ) {
472+ expect ( usage . createdSandboxes , 'createdSandboxes should be a number when present' ) . to . be . a ( 'number' ) ;
473+ }
474+ if ( usage . deletedSandboxes !== undefined ) {
475+ expect ( usage . deletedSandboxes , 'deletedSandboxes should be a number when present' ) . to . be . a ( 'number' ) ;
476+ }
477+ if ( usage . sandboxSeconds !== undefined ) {
478+ expect ( usage . sandboxSeconds , 'sandboxSeconds should be a number when present' ) . to . be . a ( 'number' ) ;
479+ }
480+ if ( usage . minutesUp !== undefined ) {
481+ expect ( usage . minutesUp , 'minutesUp should be a number when present' ) . to . be . a ( 'number' ) ;
482+ }
483+ if ( usage . minutesDown !== undefined ) {
484+ expect ( usage . minutesDown , 'minutesDown should be a number when present' ) . to . be . a ( 'number' ) ;
485+ }
486+ }
487+ } ) ;
488+
489+ it ( 'should fail realm update when no flags are provided' , async function ( ) {
490+ const result = await runCLI ( [ 'sandbox' , 'realm' , 'update' , realmId ! , '--json' ] ) ;
491+
492+ expect ( result . exitCode , 'Realm update without flags should fail' ) . to . not . equal ( 0 ) ;
493+
494+ const errorText = String ( result . stderr || result . stdout || '' ) ;
495+ expect ( errorText ) . to . match ( / N o u p d a t e f l a g s s p e c i f i e d / i) ;
496+ } ) ;
497+ } ) ;
302498 } ) ;
303499
304500 after ( function ( ) { } ) ;
0 commit comments