#!/usr/bin/env python3 # -*- coding: utf-8 -*- """入口页资源版本号注入(破浏览器缓存)的纯逻辑测试。 从 drouter-web.py 抽出 _ASSET_RE / _inject_asset_version 两个片段, 在隔离命名空间里执行,不导入整个后端(那会连数据库、读证书)。 """ import ast import os import re import sys import tempfile SRC = os.path.join(os.path.dirname(__file__), '..', 'backend', 'drouter-web.py') code = open(SRC, encoding='utf-8').read() tree = ast.parse(code) want = {'_ASSET_RE', '_inject_asset_version'} picked = [] for node in tree.body: if isinstance(node, ast.Assign): for t in node.targets: if isinstance(t, ast.Name) and t.id in want: picked.append(node) break elif isinstance(node, ast.FunctionDef) and node.name in want: picked.append(node) found = set() for node in picked: if isinstance(node, ast.Assign): for t in node.targets: if isinstance(t, ast.Name): found.add(t.id) else: found.add(node.name) missing = want - found assert not missing, '未能从源码抽取:%s' % ', '.join(sorted(missing)) # 建一个假 WEB_DIR,里面放两个已知 mtime 的文件 tmp = tempfile.mkdtemp() for name in ('app.js', 'app.css'): p = os.path.join(tmp, name) open(p, 'w', encoding='utf-8').write('/* %s */' % name) os.utime(p, (1_700_000_000, 1_700_000_000)) ns = {'os': os, 're': re, 'WEB_DIR': tmp} # _inject_asset_version 从 1.0.9 起还会替换页脚版本号占位符, # 所以要提供 app_version()。**漏了会 NameError** —— 而这个判据只 # 抽了单个函数、不是整个模块,模块级定义一个都带不过来。 # 凡是改了被抽函数的签名或新引用的模块级符号,替身必须同步补。 ns['app_version'] = lambda: '9.9.9' exec(compile(ast.Module(body=picked, type_ignores=[]), SRC, 'exec'), ns) inject = ns['_inject_asset_version'] HTML = ('\n' '\n' '\n' '\n' 'v__APP_VERSION__\n' '\n') out = inject(HTML.encode('utf-8')).decode('utf-8') fails = [] def ck(name, cond, extra=''): print(' %-42s %s %s' % (name, 'OK ' if cond else 'FAIL', extra)) if not cond: fails.append(name) print('资源版本号注入:') ck('app.css 追加了 ?v=', '/app.css?v=1700000000' in out) ck('app.js 追加了 ?v=', '/app.js?v=1700000000' in out) ck('已带查询串的 theme.css 不被改动', '/theme.css?v=123' in out and '/theme.css?v=123?v=' not in out) ck('非 js/css 的 logo.svg 不动', '/logo.svg' in out and '/logo.svg?v=' not in out) ck('不会重复追加(幂等)', inject(out.encode('utf-8')).decode('utf-8').count('/app.js?v=') == 1) ck('输出仍是合法 utf-8 且保留 doctype', out.startswith('')) ck('单引号属性也能处理', "/app.css?v=" in inject(b"").decode('utf-8')) # 1.0.9:页脚版本号占位符也要被替换掉。 # 漏了不会报错,只是页面底下一直显示 v__APP_VERSION__ 字面量。 ck('页脚 __APP_VERSION__ 被替换成真实版本号', '__APP_VERSION__' not in out and 'v9.9.9' in out) print('\n结果:%d 项,失败 %d 项' % (8, len(fails))) raise SystemExit(1 if fails else 0)