@@ -17,6 +17,7 @@ Module.prototype.require = function hijacked(file) {
1717
1818import { BrightScriptCommands } from './BrightScriptCommands' ;
1919import { util } from './util' ;
20+ import { rokuDeploy } from 'roku-deploy' ;
2021
2122describe ( 'BrightScriptFileUtils ' , ( ) => {
2223 let commands : BrightScriptCommands ;
@@ -269,6 +270,179 @@ describe('BrightScriptFileUtils ', () => {
269270 } ) ;
270271 } ) ;
271272
273+ describe ( 'restartDevice / checkForUpdates' , ( ) => {
274+ let sandbox : sinon . SinonSandbox ;
275+ let localCommands : BrightScriptCommands ;
276+ let deviceManager : any ;
277+ let userInputManager : any ;
278+ let rebootStub : sinon . SinonStub ;
279+ let checkForUpdateStub : sinon . SinonStub ;
280+ let showWarningStub : sinon . SinonStub ;
281+ let showInfoStub : sinon . SinonStub ;
282+ let showErrorStub : sinon . SinonStub ;
283+
284+ const device = { ip : '1.2.3.4' , serialNumber : 'SN123' , deviceInfo : { } } ;
285+
286+ beforeEach ( ( ) => {
287+ sandbox = sinon . createSandbox ( ) ;
288+ deviceManager = {
289+ validateAndAddDevice : sandbox . stub ( ) . resolves ( device ) ,
290+ getDevice : sandbox . stub ( ) . returns ( device ) ,
291+ getDeviceDisplayName : sandbox . stub ( ) . returns ( 'Roku Express – 1.2.3.4' )
292+ } ;
293+ //password resolution is delegated to UserInputManager (tested in its own spec)
294+ userInputManager = {
295+ promptForHost : sandbox . stub ( ) . resolves ( '1.2.3.4' ) ,
296+ resolveDevicePassword : sandbox . stub ( ) . resolves ( { status : 'ok' , password : 'pw' } )
297+ } ;
298+ //ctor: remoteControlManager, whatsNewManager, context, deviceManager, userInputManager, localPackageManager, credentialStore
299+ localCommands = new BrightScriptCommands ( { } as any , { } as any , vscode . context , deviceManager , userInputManager , { } as any , { } as any ) ;
300+
301+ rebootStub = sandbox . stub ( rokuDeploy , 'rebootDevice' ) . resolves ( { } as any ) ;
302+ checkForUpdateStub = sandbox . stub ( rokuDeploy , 'checkForUpdate' ) . resolves ( { } as any ) ;
303+ showWarningStub = sandbox . stub ( vscode . window , 'showWarningMessage' ) as sinon . SinonStub ;
304+ showWarningStub . resolves ( 'Restart' ) ;
305+ showInfoStub = sandbox . stub ( vscode . window , 'showInformationMessage' ) as sinon . SinonStub ;
306+ showInfoStub . resolves ( 'Check for Updates' ) ;
307+ showErrorStub = sandbox . stub ( vscode . window , 'showErrorMessage' ) . resolves ( ) ;
308+ vscode . context . workspaceState [ '_data' ] = { } ;
309+ } ) ;
310+
311+ afterEach ( ( ) => {
312+ sandbox . restore ( ) ;
313+ } ) ;
314+
315+ it ( 'passes the resolved password to rokuDeploy.rebootDevice' , async ( ) => {
316+ userInputManager . resolveDevicePassword . resolves ( { status : 'ok' , password : 'pw' } ) ;
317+
318+ await localCommands . restartDevice ( '1.2.3.4' ) ;
319+
320+ assert . isTrue ( rebootStub . calledOnce ) ;
321+ assert . equal ( rebootStub . firstCall . args [ 0 ] . host , '1.2.3.4' ) ;
322+ assert . equal ( rebootStub . firstCall . args [ 0 ] . password , 'pw' ) ;
323+ assert . isFalse ( showErrorStub . called ) ;
324+ } ) ;
325+
326+ it ( 'resolves the password against the probed device, offering remotePassword as an extra candidate' , async ( ) => {
327+ vscode . context . workspaceState [ '_data' ] . remotePassword = 'global-pw' ;
328+
329+ await localCommands . restartDevice ( '1.2.3.4' ) ;
330+
331+ assert . isTrue ( deviceManager . validateAndAddDevice . calledWith ( '1.2.3.4' ) ) ;
332+ assert . isTrue ( userInputManager . resolveDevicePassword . calledOnce ) ;
333+ const args = userInputManager . resolveDevicePassword . firstCall . args [ 0 ] ;
334+ assert . equal ( args . host , '1.2.3.4' ) ;
335+ assert . equal ( args . serialNumber , 'SN123' ) ;
336+ assert . deepEqual ( args . extraCandidates , [ 'global-pw' ] ) ;
337+ } ) ;
338+
339+ it ( 'aborts without resolving a password or rebooting when the confirmation is dismissed' , async ( ) => {
340+ showWarningStub . resolves ( undefined ) ;
341+
342+ await localCommands . restartDevice ( '1.2.3.4' ) ;
343+
344+ assert . isFalse ( userInputManager . resolveDevicePassword . called , 'password should not be resolved when cancelled' ) ;
345+ assert . isFalse ( rebootStub . called ) ;
346+ } ) ;
347+
348+ it ( 'cancels when password resolution is cancelled' , async ( ) => {
349+ userInputManager . resolveDevicePassword . resolves ( { status : 'cancelled' } ) ;
350+
351+ await localCommands . restartDevice ( '1.2.3.4' ) ;
352+
353+ assert . isFalse ( rebootStub . called ) ;
354+ } ) ;
355+
356+ it ( 'shows an error and does not reboot when the device is unreachable' , async ( ) => {
357+ userInputManager . resolveDevicePassword . resolves ( { status : 'unreachable' } ) ;
358+
359+ await localCommands . restartDevice ( '1.2.3.4' ) ;
360+
361+ assert . isFalse ( rebootStub . called ) ;
362+ assert . isTrue ( showErrorStub . calledOnce ) ;
363+ } ) ;
364+
365+ it ( 'always prompts for the device with the picker when no host is provided' , async ( ) => {
366+ await localCommands . restartDevice ( ) ;
367+
368+ assert . isTrue ( userInputManager . promptForHost . calledOnce ) ;
369+ assert . isTrue ( rebootStub . calledOnce ) ;
370+ assert . equal ( rebootStub . firstCall . args [ 0 ] . host , '1.2.3.4' ) ;
371+ } ) ;
372+
373+ it ( 'cancels when the device picker is dismissed' , async ( ) => {
374+ userInputManager . promptForHost . rejects ( new Error ( 'No host was selected' ) ) ;
375+
376+ await localCommands . restartDevice ( ) ;
377+
378+ assert . isFalse ( rebootStub . called ) ;
379+ assert . isFalse ( deviceManager . validateAndAddDevice . called ) ;
380+ assert . isFalse ( userInputManager . resolveDevicePassword . called ) ;
381+ } ) ;
382+
383+ it ( 'checkForUpdates passes the resolved password to rokuDeploy.checkForUpdate' , async ( ) => {
384+ userInputManager . resolveDevicePassword . resolves ( { status : 'ok' , password : 'pw' } ) ;
385+
386+ await localCommands . checkForUpdates ( '1.2.3.4' ) ;
387+
388+ assert . isTrue ( checkForUpdateStub . calledOnce ) ;
389+ assert . equal ( checkForUpdateStub . firstCall . args [ 0 ] . host , '1.2.3.4' ) ;
390+ assert . equal ( checkForUpdateStub . firstCall . args [ 0 ] . password , 'pw' ) ;
391+ } ) ;
392+
393+ it ( 'checkForUpdates aborts when the confirmation is dismissed' , async ( ) => {
394+ showInfoStub . resolves ( undefined ) ;
395+
396+ await localCommands . checkForUpdates ( '1.2.3.4' ) ;
397+
398+ assert . isFalse ( checkForUpdateStub . called ) ;
399+ } ) ;
400+
401+ it ( 'surfaces a rokuDeploy failure as an error message' , async ( ) => {
402+ rebootStub . rejects ( new Error ( 'boom' ) ) ;
403+
404+ await localCommands . restartDevice ( '1.2.3.4' ) ;
405+
406+ assert . isTrue ( showErrorStub . calledOnce ) ;
407+ assert . include ( showErrorStub . firstCall . args [ 0 ] , 'boom' ) ;
408+ } ) ;
409+
410+ describe ( 'command registration' , ( ) => {
411+ let capturedCommands : Record < string , ( ...args : any [ ] ) => any > ;
412+ let restartStub : sinon . SinonStub ;
413+ let updatesStub : sinon . SinonStub ;
414+
415+ beforeEach ( ( ) => {
416+ capturedCommands = { } ;
417+ sandbox . stub ( vscode . commands as any , 'registerCommand' ) . callsFake ( ( name : any , cb : any ) => {
418+ capturedCommands [ name ] = cb ;
419+ } ) ;
420+ restartStub = sandbox . stub ( localCommands , 'restartDevice' ) . resolves ( ) ;
421+ updatesStub = sandbox . stub ( localCommands , 'checkForUpdates' ) . resolves ( ) ;
422+ localCommands . registerDevicesViewCommands ( { toggleFilter : ( ) => { } , resetFilters : ( ) => { } } as any ) ;
423+ } ) ;
424+
425+ it ( 'maps the tree element key to the device ip' , async ( ) => {
426+ deviceManager . getDevice . withArgs ( 'SN123' ) . returns ( { ip : '1.2.3.4' } ) ;
427+
428+ await capturedCommands [ 'extension.brightscript.devicesView.restartDevice' ] ( { key : 'SN123' } ) ;
429+ await capturedCommands [ 'extension.brightscript.devicesView.checkAndInstallUpdates' ] ( { key : 'SN123' } ) ;
430+
431+ assert . isTrue ( restartStub . calledOnce ) ;
432+ assert . equal ( restartStub . firstCall . args [ 0 ] , '1.2.3.4' ) ;
433+ assert . isTrue ( updatesStub . calledOnce ) ;
434+ assert . equal ( updatesStub . firstCall . args [ 0 ] , '1.2.3.4' ) ;
435+ } ) ;
436+
437+ it ( 'passes undefined (picker fallback) when invoked with no element' , async ( ) => {
438+ await capturedCommands [ 'extension.brightscript.devicesView.restartDevice' ] ( ) ;
439+
440+ assert . isTrue ( restartStub . calledOnce ) ;
441+ assert . equal ( restartStub . firstCall . args [ 0 ] , undefined ) ;
442+ } ) ;
443+ } ) ;
444+ } ) ;
445+
272446 describe ( 'onToggleXml ' , ( ) => {
273447 it ( 'does nothing when no active document' , ( ) => {
274448 vscode . window . activeTextEditor = undefined ;
0 commit comments