@@ -96,7 +96,7 @@ function domainAnswer(code: string) {
9696 } ;
9797}
9898
99- async function setup ( page : Page , cfg : RuntimeConfig | null , opts : { native ?: unknown ; signedIn ?: boolean ; provider ?: "anthropic" | "bedrock" ; blocked ?: boolean ; domain ?: string ; history ?: unknown [ ] ; version ?: Record < string , unknown > ; blockers ?: unknown [ ] ; onlyBlockers ?: unknown [ ] } = { } ) {
99+ async function setup ( page : Page , cfg : RuntimeConfig | null , opts : { native ?: unknown ; signedIn ?: boolean ; provider ?: "anthropic" | "bedrock" ; blocked ?: boolean ; domain ?: string ; history ?: unknown [ ] ; version ?: Record < string , unknown > ; checked ?: Record < string , unknown > ; blockers ?: unknown [ ] ; onlyBlockers ?: unknown [ ] } = { } ) {
100100 const calls : string [ ] = [ ] ;
101101 const domainState = { code : opts . domain ?? "none" } ;
102102 const checksState = { fixed : false , runs : 0 } ;
@@ -128,7 +128,8 @@ async function setup(page: Page, cfg: RuntimeConfig | null, opts: { native?: unk
128128 deployment_provider : opts . provider ?? "anthropic" ,
129129 } ) ;
130130 }
131- if ( p === "/api/system/version" ) {
131+ if ( p === "/api/system/version" || p === "/api/system/releases/check" ) {
132+ if ( p !== "/api/system/version" ) systemCalls . push ( { path : p , body : JSON . parse ( req . postData ( ) || "{}" ) } ) ;
132133 return json ( route , {
133134 self_hosted : true ,
134135 current : { version : "v1.2.0" , commit : "abc1234def" , released_at : "2026-10-01T00:00:00Z" , config_fingerprint : "0123456789ab" } ,
@@ -142,6 +143,7 @@ async function setup(page: Page, cfg: RuntimeConfig | null, opts: { native?: unk
142143 { id : "UPDATE#0" , version : "v1.1.5" , from_version : "v1.1.0" , actor : "owner@example.org" , mode : "update" , status : "rolled_back" , started_at : "2026-10-02T03:00:00Z" , reason : "health check failed after deploying v1.1.5; back on v1.1.0" } ,
143144 ] ,
144145 ...( opts . version ?? { } ) ,
146+ ...( p === "/api/system/version" ? { } : ( opts . checked ?? { } ) ) ,
145147 } ) ;
146148 }
147149 if ( p === "/api/system/auto-update" ) {
@@ -297,7 +299,8 @@ for (const [name, viewport] of [["desktop", { width: 1280, height: 800 }], ["pho
297299
298300 await page . getByRole ( "button" , { name : "Update now to v2.0.0" } ) . click ( ) ;
299301 await expect . poll ( ( ) => systemCalls . map ( ( c ) => c . path ) ) . toContain ( "/api/system/update" ) ;
300- expect ( systemCalls . find ( ( c ) => c . path === "/api/system/update" ) ! . body ) . toEqual ( { version : "v2.0.0" } ) ;
302+ // No version: the updater reads GitHub first and installs the newest release at that moment.
303+ expect ( systemCalls . find ( ( c ) => c . path === "/api/system/update" ) ! . body ) . toEqual ( { } ) ;
301304
302305 await page . getByRole ( "switch" , { name : "Install updates automatically" } ) . click ( ) ;
303306 await pickOption ( page . getByRole ( "combobox" , { name : "Day" } ) , "sat" ) ;
@@ -551,7 +554,7 @@ test("System: a one-stack install's Update applies it, and shows it running", as
551554 await expect ( page . getByRole ( "button" , { name : "Prepare the update to v2.0.0" } ) ) . toHaveCount ( 0 ) ;
552555 await page . getByRole ( "button" , { name : "Update to v2.0.0" } ) . click ( ) ;
553556 await expect . poll ( ( ) => systemCalls . map ( ( c ) => c . path ) ) . toContain ( "/api/system/update" ) ;
554- expect ( systemCalls . find ( ( c ) => c . path === "/api/system/update" ) ! . body ) . toEqual ( { version : "v2.0.0" } ) ;
557+ expect ( systemCalls . find ( ( c ) => c . path === "/api/system/update" ) ! . body ) . toEqual ( { } ) ;
555558} ) ;
556559
557560test ( "System: a running one-stack update says so, with nothing to do and no console step" , async ( { page } ) => {
@@ -756,6 +759,55 @@ for (const [name, viewport] of [["desktop", { width: 1280, height: 800 }], ["pho
756759 await page . screenshot ( { path : `${ SHOTS } /system-version-updating-${ name } .png` , fullPage : true } ) ;
757760 } ) ;
758761
762+ test ( "Check for updates reads GitHub now and shows the newer release, Checked just now" , async ( { page } ) => {
763+ const hourAgo = Math . floor ( Date . now ( ) / 1000 ) - 31 * 60 ;
764+ const { systemCalls } = await setup ( page , SELF , {
765+ signedIn : true ,
766+ onlyBlockers : [ ] ,
767+ version : { latest : null , update_available : false , releases : { fetched_at : hourAgo , stale : false , reason : null , error : null , behind : null } } ,
768+ checked : {
769+ latest : { version : "v1.3.0" , notes : "## Changes\n\n- Fixes." , published_at : "2026-11-04T17:22:00Z" , url : "https://example.org/r" , breaking : false } ,
770+ update_available : true ,
771+ releases : { fetched_at : Math . floor ( Date . now ( ) / 1000 ) , stale : false , reason : null , error : null , behind : null } ,
772+ } ,
773+ } ) ;
774+ await page . goto ( "/settings/system/" ) ;
775+ const status = page . getByTestId ( "update-status" ) ;
776+ await expect ( status ) . toHaveAttribute ( "data-state" , "current" ) ;
777+ const check = page . getByTestId ( "release-check" ) ;
778+ await expect ( check ) . toContainText ( "Checked 31 min ago" ) ;
779+ await check . getByRole ( "button" , { name : "Check for updates" } ) . click ( ) ;
780+ await expect ( check ) . toContainText ( "Checked just now" ) ;
781+ expect ( systemCalls . filter ( ( c ) => c . path === "/api/system/releases/check" ) ) . toHaveLength ( 1 ) ;
782+ await expect ( status ) . toHaveAttribute ( "data-state" , "available" ) ;
783+ await expect ( status . getByRole ( "button" , { name : "Update now to v1.3.0" } ) ) . toBeEnabled ( ) ;
784+ const width = await page . evaluate ( ( ) => document . documentElement . scrollWidth ) ;
785+ expect ( width ) . toBeLessThanOrEqual ( viewport . width ) ;
786+ await page . screenshot ( { path : `${ SHOTS } /system-version-checked-${ name } .png` , fullPage : true } ) ;
787+ } ) ;
788+
789+ test ( "a newer release GitHub doesn't list yet: no older Update button, and a failed check says so" , async ( { page } ) => {
790+ await setup ( page , SELF , {
791+ signedIn : true ,
792+ onlyBlockers : [ ] ,
793+ version : {
794+ latest : null ,
795+ update_available : false ,
796+ releases : {
797+ fetched_at : Math . floor ( Date . now ( ) / 1000 ) - 600 , stale : false , reason : null , error : "GitHub is down" ,
798+ behind : { version : "v1.3.0" , reason : "v1.3.0 is out, but GitHub's release list doesn't show it yet; it shows here once it does" } ,
799+ } ,
800+ } ,
801+ } ) ;
802+ await page . goto ( "/settings/system/" ) ;
803+ const status = page . getByTestId ( "update-status" ) ;
804+ await expect ( status ) . toHaveAttribute ( "data-state" , "behind" ) ;
805+ await expect ( status ) . toContainText ( "v1.3.0 is out" ) ;
806+ await expect ( page . locator ( "#version" ) . getByRole ( "button" , { name : / ^ U p d a t e / } ) ) . toHaveCount ( 0 ) ;
807+ await expect ( page . getByTestId ( "release-check" ) ) . toContainText ( "Checked 10 min ago" ) ;
808+ await expect ( page . getByTestId ( "release-check-error" ) ) . toContainText ( "couldn't reach GitHub" ) ;
809+ } ) ;
810+
759811 test ( "up to date: green, with the version" , async ( { page } ) => {
760812 await setup ( page , SELF , { signedIn : true , version : { latest : null , update_available : false } , onlyBlockers : [ ] } ) ;
761813 await page . goto ( "/settings/system/" ) ;
0 commit comments