Files
2026-09-09 19:16:29 +02:00

104 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Start an isolated JVM process and verify HTTP readiness and PWA resources."""
import argparse
import json
import os
import re
from pathlib import Path
import socket
import subprocess
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('jar', type=Path)
parser.add_argument('--java', type=Path, default=Path(os.environ.get('JAVA_HOME', '/usr')) / 'bin/java')
parser.add_argument('--timeout', type=int, default=90)
args = parser.parse_args()
jar = args.jar.resolve(strict=True)
java = args.java.resolve(strict=True)
with socket.socket() as sock:
sock.bind(('127.0.0.1', 0))
port = sock.getsockname()[1]
base = f'http://127.0.0.1:{port}'
# Never inherit application secrets, OIDC settings or a developer's JAVA_HOME.
env = {'PATH': '/usr/bin:/bin', 'LANG': 'C.UTF-8'}
with tempfile.TemporaryDirectory(prefix='werkjournal-smoke-') as directory:
env['HOME'] = directory
mail_marker = Path(directory) / 'mail-invoked'
sendmail = Path(directory) / 'sendmail-trap'
sendmail.write_text('#!/bin/sh\n: > mail-invoked\nexit 1\n')
sendmail.chmod(0o700)
env['WERKJOURNAL_SENDMAIL'] = str(sendmail)
env['WERKJOURNAL_PUSH_ENABLED'] = 'false'
logfile = Path(directory) / 'server.log'
with logfile.open('wb') as log:
process = subprocess.Popen(
[str(java), '-Xmx256m', '-XX:MaxMetaspaceSize=192m', '-XX:ActiveProcessorCount=2', '-Djava.awt.headless=true', '-jar', str(jar), f'--server.port={port}', '--server.address=127.0.0.1'],
cwd=directory, env=env, stdout=log, stderr=subprocess.STDOUT,
)
try:
deadline = time.monotonic() + args.timeout
while True:
if process.poll() is not None:
raise RuntimeError(f'JVM process exited with {process.returncode}')
try:
with urllib.request.urlopen(base + '/api/v1/info', timeout=2) as response:
info = json.load(response)
if info.get('application') != 'werkjournal' or not info.get('version'):
raise RuntimeError(f'Unexpected readiness response: {info}')
break
except (urllib.error.URLError, TimeoutError):
if time.monotonic() >= deadline:
raise RuntimeError('JVM readiness timed out')
time.sleep(0.25)
for resource, expected in [('/', b'<html'), ('/offline.html', b'Werkjournal'), ('/sw.js', b'icons/journal.png')]:
with urllib.request.urlopen(base + resource, timeout=5) as response:
if expected not in response.read():
raise RuntimeError(f'Missing content in {resource}')
with urllib.request.urlopen(base + '/manifest.webmanifest', timeout=5) as response:
manifest = json.load(response)
if manifest.get('name') != 'Werkjournal' or not manifest.get('icons'):
raise RuntimeError('Incomplete PWA manifest')
for icon in manifest['icons']:
icon_url = urllib.parse.urljoin(base + '/', icon['src'])
if urllib.parse.urlparse(icon_url).netloc != urllib.parse.urlparse(base).netloc:
raise RuntimeError('Unexpected external PWA icon')
with urllib.request.urlopen(icon_url, timeout=5) as response:
if not response.headers.get('Content-Type', '').startswith('image/') or not response.read():
raise RuntimeError('PWA icon is missing or is not an image')
with urllib.request.urlopen(base + '/sw.js', timeout=5) as response:
if 'javascript' not in response.headers.get('Content-Type', '') or not response.read():
raise RuntimeError('Service worker is missing or is not JavaScript')
if process.poll() is not None:
raise RuntimeError('Process stopped during smoke checks')
except Exception:
print(logfile.read_text(errors='replace'))
raise
finally:
if process.poll() is None:
process.terminate()
try:
process.wait(timeout=20)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
# Exercise the actual packaged offline entry point against the migrated file DB.
# Capture the secret; never include it in diagnostics or smoke output.
previous = None
for _ in range(2):
recovery = subprocess.run([str(java), '-Xmx128m', '-jar', str(jar), '--prepare-admin-recovery'],
cwd=directory, env=env, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, text=True, timeout=30)
if recovery.returncode != 0 or re.fullmatch(r'[A-Za-z0-9_-]{43}', recovery.stdout) is None:
raise RuntimeError('Packaged offline administrator recovery failed')
if recovery.stdout == previous:
raise RuntimeError('Administrator recovery did not rotate the code')
previous = recovery.stdout
if mail_marker.exists():
raise RuntimeError('Smoke attempted mail delivery')
print(json.dumps({'mailInvocations': 0, 'pushDisabled': True, 'result': 'PASS', 'info': info, 'jar': str(jar), 'adminRecovery': 'PASS'}))