Demo images / Build & push demo images (push) Failing after 3m45s
A container that is up but hasn't answered its probe yet rendered the hard failure overlay — padlock icon, "App not reachable", "the container is stopped". Both bitcoind (RPC -28 for its whole warm-up) and lnd (unreachable until the wallet unlocks) sit in that window on every boot, so the node looked broken while it was working normally. The retry machinery was already correct: 6 × 10s of automatic re-checks, and the app appears on its own when it answers. Only the headline was wrong. While those retries are in flight AND the package reports running/starting/restarting (or health "starting"), the overlay now shows the app's own pulsing icon, "<App> is starting…", and says the container is running. Once retries are exhausted the failure is real again and the original copy returns. Follows the ElectrumX sync-screen precedent already in this file, which suppresses the same overlay for the same reason. The explicit blocked-reason and must-open-new-tab paths are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import AppSessionFrame from '../AppSessionFrame.vue'
|
|
|
|
// Regression cover for the operator-reported defect: a container that is up
|
|
// but has not finished warming up (bitcoind serving RPC -28, lnd before the
|
|
// wallet unlocks) rendered the hard "App not reachable" failure copy for the
|
|
// whole warm-up window. The retry machinery already tolerated it — only the
|
|
// headline lied.
|
|
|
|
function mountFrame(props: Record<string, unknown> = {}) {
|
|
return mount(AppSessionFrame, {
|
|
props: {
|
|
appUrl: 'http://localhost:8332/',
|
|
appId: 'bitcoin-knots',
|
|
appTitle: 'Bitcoin',
|
|
appIcon: '/icons/bitcoin.png',
|
|
loading: false,
|
|
iframeBlocked: true,
|
|
mustOpenNewTab: false,
|
|
autoRetryCount: 1,
|
|
refreshKey: 0,
|
|
...props,
|
|
},
|
|
global: { stubs: { AppLoadingScreen: true, Transition: false } },
|
|
})
|
|
}
|
|
|
|
describe('AppSessionFrame warm-up state', () => {
|
|
it('reads as starting, not unreachable, while the container is warming up', () => {
|
|
const text = mountFrame({ warmingUp: true }).text()
|
|
expect(text).toContain('Bitcoin is starting…')
|
|
expect(text).not.toContain('App not reachable')
|
|
})
|
|
|
|
it('says the container is running so the copy does not imply it is stopped', () => {
|
|
const text = mountFrame({ warmingUp: true }).text()
|
|
expect(text).toContain("container is running but hasn't finished warming up")
|
|
expect(text).not.toContain('the container is stopped')
|
|
})
|
|
|
|
it('still surfaces the automatic re-check while warming up', () => {
|
|
expect(mountFrame({ warmingUp: true, autoRetryCount: 3 }).text()).toContain(
|
|
'Checking again automatically (3)',
|
|
)
|
|
})
|
|
|
|
it('reverts to the real failure once warm-up is over (retries exhausted)', () => {
|
|
const text = mountFrame({ warmingUp: false, autoRetryCount: 6 }).text()
|
|
expect(text).toContain('App not reachable')
|
|
expect(text).not.toContain('is starting…')
|
|
})
|
|
|
|
it('leaves the explicit blocked-reason path untouched', () => {
|
|
const text = mountFrame({
|
|
warmingUp: false,
|
|
blockedReason: 'Waiting for Bitcoin to finish syncing.',
|
|
blockedTitle: 'Waiting for Bitcoin sync',
|
|
}).text()
|
|
expect(text).toContain('Waiting for Bitcoin sync')
|
|
expect(text).not.toContain('App not reachable')
|
|
})
|
|
|
|
it('leaves the new-tab path untouched', () => {
|
|
const text = mountFrame({ warmingUp: false, mustOpenNewTab: true }).text()
|
|
expect(text).toContain('This app opens in a new tab')
|
|
})
|
|
})
|