test: qualify built demo images through mobile and upload flows
This commit is contained in:
@@ -14,8 +14,9 @@ whenever human acceptance is needed.
|
||||
|
||||
- Alpha backend: isolated suite **1,681 passed**, zero failed, four explicit
|
||||
ignores; separate container runtime suite **82 passed**. Optimized binary
|
||||
SHA256 `a5a6cfc7dcab011963a6f18dc570446b6fa624180d206a108fab27504f4fd41d`
|
||||
is deployed on dev, yaya and Framework with private rollback backups.
|
||||
SHA256 `560aa6006cd9ef8be95b1f7831cf3b53854e911622b50022bb4402ce0f8b010a`
|
||||
is deployed on dev, yaya and Shorty with private rollback backups. Framework
|
||||
retains the preceding A5 candidate; its earlier acceptance remains recorded.
|
||||
- Frontend: **1,222 passed across 150 files**; production build and real mobile/
|
||||
desktop media/menu checks pass. Dev serves index SHA256
|
||||
`c18b24024a78789fe65c74c5ce27efe2125ae869016ab65e33c5a2680b543f17`.
|
||||
@@ -45,9 +46,10 @@ whenever human acceptance is needed.
|
||||
acceptance; retain the completed
|
||||
fresh/nested disposable, public staging issuance/forced renewal and actual
|
||||
yaya state-preservation checks.
|
||||
- [ ] **Shorty NPM:** shop certificate12/Force SSL are operator accepted and
|
||||
independently verified; qualify and apply the manual-route migration. Preserve live
|
||||
management containment and current public app routing.
|
||||
- [ ] **Shorty NPM:** shop certificate12/Force SSL and manual-route migration pass. Final
|
||||
corrected backend restart, 302 continuous denial probes and the external
|
||||
32-case security matrix pass. Remaining: packaged boot/OTA acceptance.
|
||||
Preserve management containment and current public app routing.
|
||||
- [ ] **Security:** verify final deployed/booted artifacts against public raw IP,
|
||||
unknown Host/SNI, forged forwarding headers, IPv4/IPv6, assets/RPC/WS;
|
||||
preserve private access, ACME issuance/renewal and public app TLS/WSS.
|
||||
@@ -759,3 +761,26 @@ The release wrapper sets this path and the release harness includes the test.
|
||||
Accepted companion54 APK/metadata replace the stale copies in the packaging
|
||||
staging directory; exact SHA remains ceb58a7d…fab1a1. Final ISO and OTA package
|
||||
acceptance/signatures remain pending.
|
||||
|
||||
### Final demo images and exact-node follow-up
|
||||
|
||||
Shorty final backend restart follow-up passes the external32-case management
|
||||
denial matrix and trusted public TLS/WSS/official Angor browser fixture. Evidence:
|
||||
`/tmp/archy-190-final-shorty-public-security-after-restart.log` and
|
||||
`/tmp/archy-190-final-shorty-angor-browser.log`. This is the known recovered
|
||||
project, not all35-project discovery. Signed catalog479f6193 is privately active
|
||||
on dev, yaya and Shorty; no public catalog publication has occurred.
|
||||
|
||||
Built demo images pass isolated real-image qualification: optional upstream DNS
|
||||
failure returns502 for that service while the main demo remains200; fresh AIUI
|
||||
provenance, backend healthcheck, four upload protocol/recovery cases, and normal
|
||||
mobile intro/login/dashboard pass. The test uses a temporary container-only DNS
|
||||
file; host DNS and the live public demo are untouched. Test:
|
||||
`tests/lifecycle/demo-images.py`; evidence:
|
||||
`/tmp/archy-190-demo-images-acceptance-final-2.log`. Earlier failure in the added
|
||||
DNS test was an incorrect assumption about Podman's generated resolver, repaired
|
||||
by explicitly mounting the disposable resolver fixture.
|
||||
|
||||
Qualified web image169e59da is built from157c9ec0; backend2722fa29 frome6e46a14.
|
||||
Public demo deployment remains scheduled after release with rollback. RC2 raw
|
||||
ISO assembly is running; no boot/install or final OTA pass is claimed yet.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Opt-in built-demo acceptance; disposable containers, no production data mounts."""
|
||||
import pathlib,subprocess,tempfile,uuid,json,time,urllib.request,os
|
||||
if os.environ.get('ARCHY_ALLOW_DISPOSABLE_CONTAINERS') != '1':
|
||||
raise SystemExit('Set ARCHY_ALLOW_DISPOSABLE_CONTAINERS=1 for isolated demo tests')
|
||||
root=pathlib.Path(__file__).resolve().parents[2]
|
||||
web_image=os.environ['ARCHY_DEMO_WEB_IMAGE']
|
||||
backend_image=os.environ['ARCHY_DEMO_BACKEND_IMAGE']
|
||||
source_revision=os.environ['ARCHY_DEMO_SOURCE_REVISION']
|
||||
assert len(source_revision)==40 and all(c in '0123456789abcdef' for c in source_revision)
|
||||
uid=uuid.uuid4().hex[:10];net='archy-release-demo-'+uid;backend=net+'-backend';web=net+'-web'
|
||||
def run(*args):return subprocess.check_output(args,text=True,stderr=subprocess.STDOUT,timeout=60).strip()
|
||||
dns_fixture=tempfile.TemporaryDirectory(prefix='archy-demo-dns-')
|
||||
resolv=pathlib.Path(dns_fixture.name)/'resolv.conf'
|
||||
resolv.write_text('nameserver 127.0.0.1\n')
|
||||
try:
|
||||
run('podman','network','create',net)
|
||||
run('podman','run','-d','--name',backend,'--network',net,'--network-alias','neode-backend','--memory','1g','-p','127.0.0.1::5959','-e','DEMO=1','-e','DEMO_FILE_QUOTA_BYTES=8388608',backend_image)
|
||||
backend_config=json.loads(run('podman','inspect','--type','container',backend))[0]
|
||||
backend_ip=next(iter(backend_config['NetworkSettings']['Networks'].values()))['IPAddress']
|
||||
run('podman','run','-d','--name',web,'--dns','none','-v',str(resolv)+':/etc/resolv.conf:ro','--add-host','neode-backend:'+backend_ip,'-p','127.0.0.1::2101','--network',net,'--memory','256m','-p','127.0.0.1::80',web_image)
|
||||
front='http://'+run('podman','port',web,'80/tcp');back='http://'+run('podman','port',backend,'5959/tcp')
|
||||
end=time.monotonic()+120
|
||||
while time.monotonic()<end:
|
||||
try:
|
||||
with urllib.request.urlopen(back+'/health',timeout=3) as r:assert r.status==200
|
||||
with urllib.request.urlopen(front+'/',timeout=3) as r:assert r.status==200
|
||||
break
|
||||
except Exception:
|
||||
if run('podman','inspect','--type','container','--format','{{.State.Status}}',web)=='exited':
|
||||
print(run('podman','logs','--tail','12',web),flush=True)
|
||||
raise RuntimeError('Demo web exited before readiness')
|
||||
time.sleep(2)
|
||||
else:raise RuntimeError('Demo image health timeout')
|
||||
print('PASS actual demo web/backend images ready on private loopback ports',flush=True)
|
||||
assert 'nameserver 127.0.0.1' in run('podman','exec',web,'cat','/etc/resolv.conf')
|
||||
optional='http://'+run('podman','port',web,'2101/tcp')
|
||||
try:
|
||||
urllib.request.urlopen(optional,timeout=10)
|
||||
raise AssertionError('Expected unavailable optional upstream with DNS disabled')
|
||||
except urllib.error.HTTPError as error:assert error.code==502
|
||||
with urllib.request.urlopen(front+'/',timeout=5) as response:assert response.status==200
|
||||
print('PASS unavailable optional DNS returns502 while the demo stays200',flush=True)
|
||||
with urllib.request.urlopen(front+'/aiui/BUILD-INFO',timeout=5) as r:assert r.read().decode().startswith('commit='+source_revision+'\n')
|
||||
config=json.loads(run('podman','inspect','--type','container',backend))[0]
|
||||
assert config['Config']['Healthcheck']['Test']
|
||||
assert not config.get('Mounts'),'Demo must have no production socket/data mounts'
|
||||
print('PASS current AIUI provenance, backend healthcheck and no host-data mounts',flush=True)
|
||||
original=(root/'neode-ui/scripts/demo-upload-test.mjs').read_text()
|
||||
original=original.replace("const root = new URL('../', import.meta.url)","const root = new URL("+json.dumps((root/'neode-ui').as_uri()+'/')+")")
|
||||
start=original.index('before(async () => {');end=original.index('after(async () => {',start)
|
||||
original=original[:start]+"before(async () => { origin = "+json.dumps(front)+" })\n"+original[end:]
|
||||
original=original.replace('const response = await fetch(`${origin}/health`)', 'const response = await fetch('+json.dumps(back+'/health')+')')
|
||||
# Keep this file beside the source so Node resolves the project's TypeScript.
|
||||
test=root/'neode-ui/scripts'/('.demo-image-acceptance-'+uid+'.mjs')
|
||||
test.write_text(original)
|
||||
try:subprocess.run(['node','--test',str(test)],cwd=root,check=True,timeout=180)
|
||||
finally:test.unlink(missing_ok=True)
|
||||
print('PASS all production upload protocol cases through the built nginx image',flush=True)
|
||||
(pathlib.Path('/tmp/archy-190-demo-image-ports.json')).write_text(json.dumps({'front':front,'back':back,'web':web,'backend':backend}))
|
||||
# Browser smoke, using the real public-demo login page.
|
||||
script=r'''
|
||||
const {createRequire}=require('node:module');const req=createRequire(process.cwd()+'/neode-ui/package.json');const {chromium}=req('@playwright/test');
|
||||
(async()=>{const browser=await chromium.launch({headless:true});try {const page=await browser.newPage({viewport:{width:390,height:844}});await page.goto(process.env.DEMO_IMAGE_URL,{waitUntil:'domcontentloaded'});await page.locator('.tap-to-start-content').click({timeout:30000});await page.getByRole('button',{name:'Skip Intro',exact:true}).click();await page.waitForURL('**/onboarding/intro',{timeout:45000});await page.screenshot({path:'/tmp/archy-190-demo-entry.png',fullPage:true});console.log('Demo entry route:',new URL(page.url()).pathname);await page.getByRole('button',{name:'Enter the demo →',exact:true}).click();const password=page.locator('input[type=password]').first();await password.waitFor({timeout:30000});await password.fill('entertoexit');await password.press('Enter');await page.getByText('My Apps',{exact:true}).first().waitFor({timeout:45000});if(await password.isVisible())throw Error('Demo login did not reach dashboard');await page.screenshot({path:'/tmp/archy-190-demo-mobile.png',fullPage:true});console.log('PASS built demo mobile login and dashboard');}finally{await browser.close()}})().catch(e=>{console.error(e);process.exit(1)});
|
||||
'''
|
||||
subprocess.run(['node','-e',script],cwd=root,env=os.environ|{'DEMO_IMAGE_URL':front},check=True,timeout=120)
|
||||
finally:
|
||||
for name in [web,backend]:subprocess.run(['podman','rm','-f',name],capture_output=True)
|
||||
subprocess.run(['podman','network','rm',net],capture_output=True)
|
||||
dns_fixture.cleanup()
|
||||
print('Disposable demo containers and network removed',flush=True)
|
||||
Reference in New Issue
Block a user