@@ -315,14 +315,11 @@ <h2>in-VM service</h2>
315315 ov . classList . add ( 'minimized' ) ;
316316 tog . textContent = '+' ;
317317 }
318- /* Auto-submit the default GET so the user lands on the
319- * picoforge login page without having to click "GET". The
320- * form defaults to port=8081 path=/-/login (the webapp HTML
321- * tier; the gateway on :8080 serves no GET pages). */
322- var form = document . getElementById ( 'svc-form' ) ;
323- if ( form ) {
324- form . dispatchEvent ( new Event ( 'submit' , { cancelable : true } ) ) ;
325- }
318+ /* Run the one-shot startup smoke: it waits for the gateway/backend,
319+ * exercises register/login/repos/create against :8081, reports
320+ * PASS/FAIL into this overlay, then leaves the app on /-/login so
321+ * the user can sign in. (Replaces the old bare auto-GET /-/login.) */
322+ runStartupSmoke ( ) ;
326323 }
327324 window . __renderStatus = function ( text , level , tag ) {
328325 var d = document . createElement ( 'div' ) ;
@@ -932,6 +929,80 @@ <h2>in-VM service</h2>
932929 return current ;
933930 }
934931
932+ /* ---- one-shot startup smoke ------------------------------------
933+ * Runs ONCE when the in-VM webapp comes up (kicked from
934+ * revealService). Waits for the gateway/backend to answer, then drives
935+ * the real endpoints through the slirp bridge — register a throwaway
936+ * user (picomesh auto-logs-in), confirm the authenticated /-/repos, and
937+ * create a repo — reporting PASS/FAIL into the boot overlay. No
938+ * clicking: open the page (make run-webserver) and watch it go green.
939+ * It leaves the app on /-/login so the user can sign in afterwards. */
940+ var smokeStarted = false ;
941+ function smokeLine ( text , ok ) {
942+ var d = document . createElement ( 'div' ) ;
943+ d . className = ok === true ? 'ok' : ok === false ? 'err' : '' ;
944+ d . textContent = ( ok === true ? ' PASS: ' : ok === false ? ' FAIL: ' : '[smoke] ' ) + text ;
945+ body . appendChild ( d ) ;
946+ body . scrollTop = body . scrollHeight ;
947+ console . log ( '[smoke]' , ( ok === undefined ? '' : ( ok ? 'PASS ' : 'FAIL ' ) ) + text ) ;
948+ }
949+ function smokeBodyText ( resp ) {
950+ try { return new TextDecoder ( 'utf-8' , { fatal : false } ) . decode ( resp . body || new Uint8Array ( 0 ) ) ; }
951+ catch ( e ) { return '' ; }
952+ }
953+ async function runStartupSmoke ( ) {
954+ if ( smokeStarted ) return ;
955+ smokeStarted = true ;
956+ var PORT = 8081 ; // the picoforge webapp HTML tier
957+ // Make the webapp the active port so the user's manual login/nav
958+ // (picomesh-submit / picomesh-nav handlers gate on svcLastPort > 0)
959+ // work after boot — this replaced the old auto-GET that used to set
960+ // it. Without this, every login/click is silently "ignored".
961+ svcLastPort = PORT ;
962+ var loggedIn = / S i g n o u t | \/ - \/ l o g o u t / ;
963+ smokeLine ( 'startup smoke: waiting for webapp :' + PORT + ' …' ) ;
964+
965+ // 1. Wait for the gateway/backend to be up (GET /-/login -> 200).
966+ var up = false ;
967+ for ( var i = 0 ; i < 150 && ! up ; i ++ ) {
968+ try { var r = await svcFetchOnce ( PORT , '/-/login' ) ; if ( r . status === 200 ) up = true ; }
969+ catch ( e ) { /* not up yet */ }
970+ if ( ! up ) await new Promise ( function ( res ) { setTimeout ( res , 2000 ) ; } ) ;
971+ }
972+ smokeLine ( 'webapp serves GET /-/login (200)' , up ) ;
973+ if ( ! up ) { smokeLine ( 'STARTUP SMOKE ABORTED — backend never came up' , false ) ; return ; }
974+
975+ var pass = 1 , total = 1 ;
976+ function tally ( ok ) { total ++ ; if ( ok ) pass ++ ; return ok ; }
977+ try {
978+ // 2. Register a throwaway user; picomesh auto-logs-in (303 + sid).
979+ var u = 'smoke' + ( Date . now ( ) % 1000000 ) ;
980+ var reg = await followFromResp ( PORT ,
981+ await svcFetchWithMethod ( PORT , 'POST' , '/-/register' , 'username=' + u + '&password=pw' ) , 5 ) ;
982+ smokeLine ( 'register ' + u + ' + auto-login (session established)' ,
983+ tally ( reg . status === 200 && loggedIn . test ( smokeBodyText ( reg ) ) ) ) ;
984+
985+ // 3. The session persists: authenticated /-/repos shows the shell.
986+ var repos = await svcFetchOnce ( PORT , '/-/repos' ) ;
987+ smokeLine ( 'authenticated GET /-/repos (logged in)' ,
988+ tally ( repos . status === 200 && loggedIn . test ( smokeBodyText ( repos ) ) ) ) ;
989+
990+ // 4. Create a repo: POST /-/repos/new -> 303 -> repo tree page.
991+ var mk = await followFromResp ( PORT ,
992+ await svcFetchWithMethod ( PORT , 'POST' , '/-/repos/new' , 'name=smoke-repo' ) , 5 ) ;
993+ smokeLine ( 'create repo ' + u + '/smoke-repo' ,
994+ tally ( mk . status === 200 && / s m o k e - r e p o / . test ( ( mk . path || '' ) + smokeBodyText ( mk ) ) ) ) ;
995+ } catch ( e ) {
996+ smokeLine ( 'smoke error: ' + ( e && e . message ? e . message : e ) , false ) ;
997+ }
998+ smokeLine ( 'STARTUP SMOKE ' + ( pass === total ? 'PASS' : 'FAIL' ) +
999+ ' — ' + pass + '/' + total + ' checks' , pass === total ) ;
1000+
1001+ // Leave the user on the login page (the smoke registered its own
1002+ // throwaway account; logging in below replaces that cookie).
1003+ try { renderResp ( PORT , await svcFetchOnce ( PORT , '/-/login' ) ) ; } catch ( e ) { /* ignore */ }
1004+ }
1005+
9351006 async function svcGo ( ev ) {
9361007 ev . preventDefault ( ) ;
9371008 if ( typeof Module === 'undefined' || ! Module . _tinyemu_session_open ) {
0 commit comments