@@ -2,6 +2,7 @@ import { describe, expect, it } from "@effect/vitest";
22import {
33 DEFAULT_SERVER_SETTINGS ,
44 DeviceId ,
5+ DeviceOperationError ,
56 LOCAL_DEVICE_HOST_ID ,
67 ThreadId ,
78 type DeviceServiceState ,
@@ -68,6 +69,7 @@ const fixture = Effect.fn("fixture")(function* (
6869 failListAfterShutdown = false ,
6970 runtimeFailure ?: NodeRuntimeUnavailableError | DeviceHost . DeviceHostError ,
7071 inspectError = false ,
72+ installTool ?: Parameters < typeof makeWithHosts > [ 3 ] ,
7173) {
7274 const settings = yield * Ref . make ( DEFAULT_SERVER_SETTINGS ) ;
7375 const starts : string [ ] = [ ] ;
@@ -129,7 +131,12 @@ const fixture = Effect.fn("fixture")(function* (
129131 starts . push ( "stop" ) ;
130132 } ) ,
131133 } ;
132- const service = yield * makeWithHosts ( new Map ( [ [ host . id , host ] ] ) ) . pipe (
134+ const service = yield * makeWithHosts (
135+ new Map ( [ [ host . id , host ] ] ) ,
136+ undefined ,
137+ undefined ,
138+ installTool ,
139+ ) . pipe (
133140 Effect . provideService ( DeviceHost . DeviceHost , host ) ,
134141 Effect . provideService (
135142 ServerSettingsService ,
@@ -660,3 +667,67 @@ it.effect("failed read-only discovery preserves lifecycle status and installed i
660667 expect ( starts ) . toEqual ( [ ] ) ;
661668 } ) . pipe ( Effect . scoped ) ,
662669) ;
670+
671+ it . effect (
672+ "manual updates install only the selected tool without enabling access or starting helpers" ,
673+ ( ) =>
674+ Effect . gen ( function * ( ) {
675+ const installed : string [ ] = [ ] ;
676+ const { service, starts, agentStarts, requests } = yield * fixture (
677+ Effect . void ,
678+ undefined ,
679+ false ,
680+ undefined ,
681+ false ,
682+ ( tool ) =>
683+ Effect . sync ( ( ) => {
684+ installed . push ( tool ) ;
685+ } ) ,
686+ ) ;
687+ const before = yield * service . state ;
688+ const state = yield * service . updateTool ( "agent" ) ;
689+ expect ( installed ) . toEqual ( [ "agent" ] ) ;
690+ expect ( state . supportsToolUpdate ) . toBe ( true ) ;
691+ expect ( state . hostStatus ) . toBe ( before . hostStatus ) ;
692+ expect ( state . agentAccessEnabled ) . toBe ( before . agentAccessEnabled ) ;
693+ expect ( state . revision ) . toBeGreaterThan ( before . revision ) ;
694+ expect ( starts ) . toEqual ( [ ] ) ;
695+ expect ( agentStarts ) . toEqual ( [ ] ) ;
696+ expect ( requests ) . toEqual ( [ ] ) ;
697+ yield * service . updateTool ( "hub" ) ;
698+ expect ( installed ) . toEqual ( [ "agent" , "hub" ] ) ;
699+ } ) . pipe ( Effect . scoped ) ,
700+ ) ;
701+
702+ it . effect ( "failed manual installation leaves lifecycle state unchanged and can be retried" , ( ) =>
703+ Effect . gen ( function * ( ) {
704+ let attempts = 0 ;
705+ const { service, starts, agentStarts } = yield * fixture (
706+ Effect . void ,
707+ undefined ,
708+ false ,
709+ undefined ,
710+ false ,
711+ ( ) =>
712+ Effect . suspend ( ( ) =>
713+ ++ attempts === 1
714+ ? Effect . fail (
715+ new DeviceOperationError ( {
716+ operation : "update device tool" ,
717+ reason : "command_failed" ,
718+ cause : new Error ( "offline" ) ,
719+ } ) ,
720+ )
721+ : Effect . void ,
722+ ) ,
723+ ) ;
724+ const before = yield * service . state ;
725+ const result = yield * service . updateTool ( "agent" ) . pipe ( Effect . result ) ;
726+ expect ( result . _tag ) . toBe ( "Failure" ) ;
727+ expect ( yield * service . state ) . toEqual ( before ) ;
728+ yield * service . updateTool ( "agent" ) ;
729+ expect ( attempts ) . toBe ( 2 ) ;
730+ expect ( starts ) . toEqual ( [ ] ) ;
731+ expect ( agentStarts ) . toEqual ( [ ] ) ;
732+ } ) . pipe ( Effect . scoped ) ,
733+ ) ;
0 commit comments