This commit is contained in:
parent
d193324e6d
commit
78c5936388
59
test/deployment/test_frontend_review.py
Normal file
59
test/deployment/test_frontend_review.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""验证前端审查依赖的只读挂载范围,不启动本机应用。"""
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
source = Path(__file__).resolve().parents[1] / 'tools' / 'run_frontend_review.py'
|
||||
spec = importlib.util.spec_from_file_location('frontend_review', source)
|
||||
review = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(review)
|
||||
|
||||
|
||||
class DependencyBindingsTest(unittest.TestCase):
|
||||
root = Path('/var/tmp/smail-review-current')
|
||||
|
||||
def bindings(self, targets):
|
||||
with patch.object(Path, 'resolve', side_effect=map(Path, targets)):
|
||||
with patch.object(Path, 'is_dir', return_value=True):
|
||||
return review.dependency_bindings(self.root)
|
||||
|
||||
def test_local_dependencies_need_no_extra_mount(self):
|
||||
self.assertEqual(self.bindings([
|
||||
str(self.root / 'node-v22.23.2-linux-x64'),
|
||||
str(self.root / 'frontend/node_modules'),
|
||||
]), [])
|
||||
|
||||
def test_shared_dependencies_are_explicit(self):
|
||||
targets = ['/var/tmp/smail-review-previous/node',
|
||||
'/var/tmp/smail-review-previous/frontend/node_modules']
|
||||
self.assertEqual(self.bindings(targets), sorted(targets))
|
||||
|
||||
def test_duplicate_mounts_are_combined(self):
|
||||
target = '/var/tmp/smail-review-previous/dependencies'
|
||||
self.assertEqual(self.bindings([target, target]), [target])
|
||||
|
||||
def test_unsafe_targets_are_rejected(self):
|
||||
for target in ('/opt/smail', '/run/smail', '/var/tmp/unrelated/node',
|
||||
'/var/tmp/smail-review-previous',
|
||||
'/var/tmp/smail-review-previous/node:other',
|
||||
'/var/tmp/smail-review-previous/node other'):
|
||||
with self.subTest(target=target), self.assertRaises(ValueError):
|
||||
self.bindings([target])
|
||||
|
||||
def test_missing_dependency_fails_before_start(self):
|
||||
with patch.object(Path, 'resolve', side_effect=FileNotFoundError):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
review.dependency_bindings(self.root)
|
||||
|
||||
def test_file_dependency_is_rejected(self):
|
||||
with patch.object(Path, 'resolve', return_value=self.root / 'node'):
|
||||
with patch.object(Path, 'is_dir', return_value=False):
|
||||
with self.assertRaises(ValueError):
|
||||
review.dependency_bindings(self.root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@ -15,6 +15,7 @@ const { readFormConfig, getFieldGroups, updateFormConfig, validateFormConfig } =
|
||||
|
||||
test('公网 HTTPS 端口独立编辑且保留内部监听与模式切换', () => {
|
||||
const config = {
|
||||
database: { type: 'sqlite' },
|
||||
domains: { primary: 'example.com', web: 'mail.example.com', accepted: ['example.com'] },
|
||||
http: { port: 8001, httpsPort: 8002, httpsEnabled: true, tlsMode: 'direct' }
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ test('六个平台默认包含三个插件,构建和发布名称与后端目
|
||||
assert.match(await read('Makefile'), /package: build_server plugin/)
|
||||
assert.match(await read('.github/workflows/release.yml'), /node deploy\/build-plugins\.mjs --target=/)
|
||||
assert.match(await read('deploy/package.mjs'), /sha256: createHash\('sha256'\)/)
|
||||
assert.match(await read('deploy/package.mjs'), /files,\n createdAt/)
|
||||
assert.match(await read('deploy/package.mjs'), /files,\r?\n createdAt/)
|
||||
})
|
||||
|
||||
test('安全工作流必须先构建嵌入资源再执行 Go 测试', async () => {
|
||||
|
||||
@ -10,6 +10,20 @@ import subprocess
|
||||
import time
|
||||
|
||||
|
||||
def dependency_bindings(root):
|
||||
bindings = set()
|
||||
for relative in ('node-v22.23.2-linux-x64', 'frontend/node_modules'):
|
||||
target = (root / relative).resolve(strict=True)
|
||||
if (len(target.parts) < 5 or target.parts[1:3] != ('var', 'tmp')
|
||||
or not target.parts[3].startswith('smail-review-')
|
||||
or any(not re.fullmatch(r'[A-Za-z0-9_.-]+', part) for part in target.parts[3:])
|
||||
or not target.is_dir()):
|
||||
raise ValueError('测试依赖必须位于受控审查目录')
|
||||
if not target.is_relative_to(root):
|
||||
bindings.add(str(target))
|
||||
return sorted(bindings)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('directory')
|
||||
@ -21,6 +35,8 @@ def main():
|
||||
assert os.geteuid() == 0
|
||||
memory = dict(line.split(':', 1) for line in Path('/proc/meminfo').read_text().splitlines())
|
||||
assert int(memory['MemAvailable'].split()[0]) >= 256 * 1024, '可用内存不足'
|
||||
# PrivateTmp 会隐藏其他审查目录,只读挂载已验证的依赖,不开放整个临时目录。
|
||||
bindings = dependency_bindings(root)
|
||||
command = [
|
||||
'systemd-run', '--wait', '--pipe', '--unit=smail-frontend-tests-' + time.strftime('%Y%m%d-%H%M%S'),
|
||||
'-p', 'User=smail', '-p', 'Group=smail', '-p', 'MemoryMax=128M', '-p', 'MemorySwapMax=0',
|
||||
@ -29,6 +45,7 @@ def main():
|
||||
'-p', 'PrivateTmp=yes', '-p', 'PrivateNetwork=yes', '-p', 'BindPaths=' + str(root),
|
||||
'-p', 'ProtectSystem=strict', '-p', 'ProtectHome=yes', '-p', 'InaccessiblePaths=/opt/smail /run/smail',
|
||||
'-p', 'NoNewPrivileges=yes', '-p', 'ReadWritePaths=' + str(root), '-p', 'WorkingDirectory=' + str(root),
|
||||
*[argument for target in bindings for argument in ('-p', 'BindReadOnlyPaths=' + target)],
|
||||
'python3', str(root / 'run_frontend_review.py'), str(root), '--worker',
|
||||
]
|
||||
return subprocess.run(command).returncode
|
||||
|
||||
Loading…
Reference in New Issue
Block a user