Sanitize codebase, reorganize docs, and add missing deploy files
Remove dead code identified in docs/SANITIZATION-REVIEW.md:
- app/blueprints/content_old.py and app/blueprints/playlist.py
- app/models/group.py, app/utils/nginx_config_reader.py
- orphaned templates (content_list, edit_content, upload_content,
player_page) and the related group/Template references
Result: 6 blueprints, 82 routes, no dead modules or orphan templates.
Add files that deploy.sh and docker-entrypoint.sh already require but
which were never tracked:
- https_manager.py (referenced by deploy.sh, migrate_network.sh,
docker-entrypoint.sh)
- Caddyfile.example (seeded by deploy.sh; its absence aborts deploy)
Relocate generated Graphify artifacts from graphify-out/ to
docs/graphify-out/ (110 files, no content change) and archive the
superseded docs under docs/.
Ignore hygiene:
- ignore ad-hoc .env backups (.env.bak*) — they contain live secrets
- keep the pre-sanitization snapshots (docs/legacy code/,
docs/old_code_documentation/) on disk but out of the repo
Fix .env.example: drop a duplicated config block, genericize the
hardcoded host IP, and document HOSTNAME_INTERNAL.
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
"""Verify the background player-build flow: state machine + shallow git clone.
|
||||
|
||||
Exercises the real functions against a local throwaway bare git repo so no
|
||||
network is needed, then confirms:
|
||||
* shallow clone produces a usable working tree
|
||||
* _run_git never blocks on credentials (GIT_TERMINAL_PROMPT=0)
|
||||
* a broken/partial checkout is detected and replaced, not reused
|
||||
* the background job moves idle -> running -> success and records the version
|
||||
* a bad repo URL ends in 'error', not a hang or a traceback
|
||||
|
||||
Run: PYTHONPATH=$(pwd) ./.venv/bin/python docs/tools/test_player_build.py
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
TMP = tempfile.mkdtemp()
|
||||
os.environ['DATABASE_URL'] = f'sqlite:///{TMP}/pb.db'
|
||||
|
||||
from app.app import create_app # noqa: E402
|
||||
from app.extensions import db # noqa: E402
|
||||
from app.utils import player_build as pb # noqa: E402
|
||||
|
||||
app = create_app('production')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# ── Build a local origin repo (no network) ──────────────────────────────────
|
||||
ORIGIN = os.path.join(TMP, 'origin.git')
|
||||
SRC = os.path.join(TMP, 'src')
|
||||
subprocess.run(['git', 'init', '--bare', '-q', ORIGIN], check=True)
|
||||
subprocess.run(['git', 'init', '-q', SRC], check=True)
|
||||
for k, v in (('user.email', 't@t'), ('user.name', 'T')):
|
||||
subprocess.run(['git', '-C', SRC, 'config', k, v], check=True)
|
||||
os.makedirs(os.path.join(SRC, 'config'), exist_ok=True)
|
||||
open(os.path.join(SRC, 'config', 'app_config.json'), 'w').write('{}')
|
||||
open(os.path.join(SRC, 'main.py'), 'w').write('print("player")\n')
|
||||
subprocess.run(['git', '-C', SRC, 'add', '-A'], check=True)
|
||||
subprocess.run(['git', '-C', SRC, 'commit', '-qm', 'init'], check=True)
|
||||
subprocess.run(['git', '-C', SRC, 'branch', '-M', 'main'], check=True)
|
||||
subprocess.run(['git', '-C', SRC, 'remote', 'add', 'origin', ORIGIN], check=True)
|
||||
subprocess.run(['git', '-C', SRC, 'push', '-q', 'origin', 'main'], check=True)
|
||||
HEAD = subprocess.run(['git', '-C', SRC, 'rev-parse', '--short', 'HEAD'],
|
||||
capture_output=True, text=True).stdout.strip()
|
||||
|
||||
# file:// forces a real transport so --depth is honoured (a local path clone
|
||||
# silently ignores it, which would make the shallow assertion meaningless).
|
||||
ORIGIN_URI = 'file://' + ORIGIN
|
||||
|
||||
TARGET = os.path.join(TMP, 'staged')
|
||||
META = os.path.join(TMP, 'player_build.json')
|
||||
|
||||
failures = []
|
||||
|
||||
|
||||
def check(label, cond, detail=''):
|
||||
print(f" [{'PASS' if cond else 'FAIL'}] {label}" + (f' {detail}' if detail else ''))
|
||||
if not cond:
|
||||
failures.append(label)
|
||||
|
||||
|
||||
print('=' * 68)
|
||||
print('CASE 1 — fresh shallow clone')
|
||||
print('=' * 68)
|
||||
r = pb.build_player_files(TARGET, ORIGIN_URI, 'main')
|
||||
check('clone succeeded', r['success'], r['message'])
|
||||
check('version matches origin', r['version'] == HEAD, f"{r['version']} vs {HEAD}")
|
||||
check('working tree has files',
|
||||
os.path.isfile(os.path.join(TARGET, 'main.py')))
|
||||
check('checkout reported usable', pb.is_valid_checkout(TARGET))
|
||||
check('shallow (depth 1)',
|
||||
os.path.isfile(os.path.join(TARGET, '.git', 'shallow')),
|
||||
'file:// transport honours --depth')
|
||||
print()
|
||||
|
||||
print('=' * 68)
|
||||
print('CASE 2 — git never prompts for credentials')
|
||||
print('=' * 68)
|
||||
r = pb._run_git(['clone', '--depth', '1',
|
||||
'https://127.0.0.1:1/nope/nope.git',
|
||||
os.path.join(TMP, 'nope')], timeout=20)
|
||||
check('unreachable repo returns fast (no hang)', r.returncode != 0,
|
||||
f'rc={r.returncode}')
|
||||
check('GIT_TERMINAL_PROMPT=0 is set', pb._git_env().get('GIT_TERMINAL_PROMPT') == '0')
|
||||
print()
|
||||
|
||||
print('=' * 68)
|
||||
print('CASE 3 — broken/partial checkout is replaced, not reused')
|
||||
print('=' * 68)
|
||||
BROKEN = os.path.join(TMP, 'broken')
|
||||
shutil.rmtree(BROKEN, ignore_errors=True)
|
||||
os.makedirs(os.path.join(BROKEN, '.git'), exist_ok=True) # .git but no HEAD
|
||||
open(os.path.join(BROKEN, 'leftover.txt'), 'w').write('stale')
|
||||
check('broken dir is not considered usable', not pb.is_valid_checkout(BROKEN))
|
||||
r = pb.build_player_files(BROKEN, ORIGIN_URI, 'main')
|
||||
check('build recovers from broken dir', r['success'], r['message'])
|
||||
check('stale file removed',
|
||||
not os.path.exists(os.path.join(BROKEN, 'leftover.txt')))
|
||||
check('now a valid checkout', pb.is_valid_checkout(BROKEN))
|
||||
print()
|
||||
|
||||
print('=' * 68)
|
||||
print('CASE 4 — background job: idle -> running -> success')
|
||||
print('=' * 68)
|
||||
TARGET2 = os.path.join(TMP, 'staged2')
|
||||
pb._set_build_state(state='idle', step='', message='', version=None)
|
||||
check('starts idle', pb.get_build_state()['state'] == 'idle')
|
||||
|
||||
with app.app_context():
|
||||
started = pb.start_background_build(
|
||||
player_code_dir=TARGET2, repo_url=ORIGIN_URI, branch='main',
|
||||
config_payload={'server_ip': '192.168.0.152', 'port': '443',
|
||||
'use_https': True, 'verify_ssl': False,
|
||||
'orientation': 'Landscape', 'max_resolution': '1920x1080'},
|
||||
meta_path=META, built_by='tester')
|
||||
check('build accepted', started is True)
|
||||
check('second start refused while running',
|
||||
pb.start_background_build(TARGET2, ORIGIN_URI, 'main', None, META, 'x') is False)
|
||||
|
||||
for _ in range(120):
|
||||
if pb.get_build_state()['state'] in ('success', 'error'):
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
state = pb.get_build_state()
|
||||
check('finished successfully', state['state'] == 'success', state.get('message', ''))
|
||||
check('version recorded', state.get('version') == HEAD)
|
||||
check('config written to staged code',
|
||||
os.path.isfile(os.path.join(TARGET2, 'config', 'app_config.json')))
|
||||
check('build settings persisted', os.path.isfile(META))
|
||||
settings = pb.load_build_settings(META) or {}
|
||||
check('meta has server_ip', settings.get('server_ip') == '192.168.0.152',
|
||||
str(settings.get('server_ip')))
|
||||
print()
|
||||
|
||||
print('=' * 68)
|
||||
print('CASE 5 — bad repository URL ends in error (no hang, no traceback)')
|
||||
print('=' * 68)
|
||||
TARGET3 = os.path.join(TMP, 'staged3')
|
||||
pb._set_build_state(state='idle', step='', message='', version=None)
|
||||
with app.app_context():
|
||||
pb.start_background_build(TARGET3, os.path.join(TMP, 'does-not-exist.git'),
|
||||
'main', None, META, 'tester')
|
||||
for _ in range(120):
|
||||
if pb.get_build_state()['state'] in ('success', 'error'):
|
||||
break
|
||||
time.sleep(0.5)
|
||||
state = pb.get_build_state()
|
||||
check('reported as error', state['state'] == 'error')
|
||||
check('error has a message', bool(state.get('message')), state.get('message', '')[:70])
|
||||
check('no partial dir left behind', not os.path.exists(TARGET3))
|
||||
|
||||
shutil.rmtree(TMP, ignore_errors=True)
|
||||
print()
|
||||
if failures:
|
||||
print(f'FAILED: {failures}')
|
||||
sys.exit(1)
|
||||
print('ALL PLAYER-BUILD CASES PASSED')
|
||||
Reference in New Issue
Block a user