mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-29 10:06:41 +00:00
ci: gate qualified offline Python and Node tests separately
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "proxmenux-offline-node-tests",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "proxmenux-offline-node-tests",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"lucide-react": "0.454.0",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"typescript": "5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.454.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz",
|
||||
"integrity": "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
|
||||
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "proxmenux-offline-node-tests",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "Only the dependencies of the qualified tests/*.cjs CI lane",
|
||||
"dependencies": {
|
||||
"typescript": "5.9.3",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"lucide-react": "0.454.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run only the independently qualified, host-independent tests/ fixtures.
|
||||
|
||||
No filesystem discovery widens this manifest. Run from the repository root;
|
||||
Python and Node have separate CI jobs so either failure blocks the check.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PYTHON_FILES = (
|
||||
"tests/test_audit_presentation.py",
|
||||
"tests/test_audit_safety_wording.py",
|
||||
"tests/test_audit_policy.py",
|
||||
"tests/test_audit_report.py",
|
||||
"tests/test_audit_catalog.py",
|
||||
"tests/storage/test_nvme_status_message.py",
|
||||
"tests/test_fastfetch_config_generation.py",
|
||||
)
|
||||
NODE_FILES = (
|
||||
"tests/lxc_updates/test_docker_delegated_ui.cjs",
|
||||
"tests/test_audit_diagnostic_document.cjs",
|
||||
"tests/test_audit_policy.cjs",
|
||||
"tests/test_audit_presentation.cjs",
|
||||
"tests/test_audit_safety_wording.cjs",
|
||||
"tests/test_audit_summary.cjs",
|
||||
"tests/test_backup_archives_empty.cjs",
|
||||
"tests/test_backup_destination_messages.cjs",
|
||||
"tests/test_borg_ssh_guidance.cjs",
|
||||
"tests/test_storage_messages.cjs",
|
||||
)
|
||||
|
||||
# unittest's CLI accepts a missing pattern as a successful zero-test run.
|
||||
# Load the exact file in a fresh interpreter and reject zero tests and skips.
|
||||
PYTHON_RUN = """import sys, unittest
|
||||
folder, filename = sys.argv[1:]
|
||||
suite = unittest.TestLoader().discover(start_dir=folder, pattern=filename)
|
||||
count = suite.countTestCases()
|
||||
if count == 0:
|
||||
raise SystemExit('No tests discovered: ' + folder + '/' + filename)
|
||||
print('DISCOVERED=' + str(count), flush=True)
|
||||
result = unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
sys.exit(0 if result.wasSuccessful() and result.testsRun == count and not result.skipped else 1)
|
||||
"""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--lane", required=True, choices=("python", "node"))
|
||||
args = parser.parse_args()
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
files = PYTHON_FILES if args.lane == "python" else NODE_FILES
|
||||
if not files or Path.cwd().resolve() != root:
|
||||
parser.error("nonempty manifest and repository-root working directory required")
|
||||
for path in files:
|
||||
if not (root / path).is_file():
|
||||
print(f"Missing qualified test: {path}", file=sys.stderr)
|
||||
return 1
|
||||
if args.lane == "node" and not (root / "AppImage/node_modules/typescript").is_dir():
|
||||
print("Install the locked offline-node dependencies first", file=sys.stderr)
|
||||
return 1
|
||||
for path in files:
|
||||
print(f"RUN {path}", flush=True)
|
||||
if args.lane == "python":
|
||||
folder, filename = str(Path(path).parent), Path(path).name
|
||||
command = [sys.executable, "-I", "-B", "-c", PYTHON_RUN, folder, filename]
|
||||
else:
|
||||
command = ["node", path]
|
||||
result = subprocess.run(command, cwd=root, env=os.environ.copy(), check=False)
|
||||
if result.returncode != 0:
|
||||
print(f"FAIL {path}: exit {result.returncode}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"PASS {path}", flush=True)
|
||||
print(f"PASS {args.lane}: {len(files)} qualified files", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Qualified offline tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/test-offline-qualified.yml'
|
||||
- '.github/scripts/run_offline_qualified.py'
|
||||
- '.github/ci/offline-node/**'
|
||||
- 'CONTRIBUTING.md'
|
||||
- 'tests/**'
|
||||
- 'AppImage/package.json'
|
||||
- 'AppImage/package-lock.json'
|
||||
- 'AppImage/scripts/*.py'
|
||||
- 'AppImage/app/**'
|
||||
- 'AppImage/components/**'
|
||||
- 'AppImage/hooks/**'
|
||||
- 'AppImage/lib/**'
|
||||
- 'AppImage/messages/**'
|
||||
- 'lang/*.json'
|
||||
- 'scripts/**/*.sh'
|
||||
- 'scripts/**/*.func'
|
||||
push:
|
||||
branches: [main, develop]
|
||||
paths:
|
||||
- '.github/workflows/test-offline-qualified.yml'
|
||||
- '.github/scripts/run_offline_qualified.py'
|
||||
- '.github/ci/offline-node/**'
|
||||
- 'CONTRIBUTING.md'
|
||||
- 'tests/**'
|
||||
- 'AppImage/package.json'
|
||||
- 'AppImage/package-lock.json'
|
||||
- 'AppImage/scripts/*.py'
|
||||
- 'AppImage/app/**'
|
||||
- 'AppImage/components/**'
|
||||
- 'AppImage/hooks/**'
|
||||
- 'AppImage/lib/**'
|
||||
- 'AppImage/messages/**'
|
||||
- 'lang/*.json'
|
||||
- 'scripts/**/*.sh'
|
||||
- 'scripts/**/*.func'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
offline-python:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Run seven qualified Python files
|
||||
env:
|
||||
PYTHONDONTWRITEBYTECODE: '1'
|
||||
run: python3 -I -B .github/scripts/run_offline_qualified.py --lane python
|
||||
|
||||
offline-node:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.14.0'
|
||||
- name: Install only locked fixture dependencies (no lifecycle scripts)
|
||||
run: |
|
||||
npm ci --prefix .github/ci/offline-node --legacy-peer-deps --ignore-scripts --no-audit --no-fund
|
||||
ln -s ../.github/ci/offline-node/node_modules AppImage/node_modules
|
||||
- name: Run ten qualified Node files
|
||||
env:
|
||||
NODE_PATH: ${{ github.workspace }}/AppImage/node_modules
|
||||
PYTHONDONTWRITEBYTECODE: '1'
|
||||
run: python3 -I -B .github/scripts/run_offline_qualified.py --lane node
|
||||
@@ -860,6 +860,42 @@ provisions Python and Node and runs this same command on relevant pull requests
|
||||
and main/develop pushes (or manually). It is separate from the translation
|
||||
publication workflow and has read-only repository permissions.
|
||||
|
||||
#### Qualified offline `tests/` CI lanes
|
||||
|
||||
`.github/workflows/test-offline-qualified.yml` runs **only** the explicitly listed
|
||||
seven Python files and ten Node files in `.github/scripts/run_offline_qualified.py`.
|
||||
Run the same allowlist locally from the root of a clean, disposable checkout
|
||||
with no `AppImage/node_modules`. Do not run this over a normal Monitor install:
|
||||
the commands refuse an existing directory, file, or symlink (including a
|
||||
dangling symlink) rather than overwrite or reuse it. In Bash:
|
||||
|
||||
```bash
|
||||
(
|
||||
set -e
|
||||
if [[ -e AppImage/node_modules || -L AppImage/node_modules ]]; then
|
||||
printf '%s\n' 'Refusing: AppImage/node_modules already exists; use a clean disposable checkout (nothing overwritten).' >&2
|
||||
exit 1
|
||||
fi
|
||||
npm ci --prefix .github/ci/offline-node --legacy-peer-deps --ignore-scripts --no-audit --no-fund
|
||||
ln -s ../.github/ci/offline-node/node_modules AppImage/node_modules
|
||||
python3 -I -B .github/scripts/run_offline_qualified.py --lane python
|
||||
NODE_PATH="$PWD/AppImage/node_modules" python3 -I -B .github/scripts/run_offline_qualified.py --lane node
|
||||
)
|
||||
```
|
||||
|
||||
Requires Python 3.11, Node 22.14, npm and Bash/coreutils for the bounded shell
|
||||
fixtures. The separate lockfile installs only TypeScript, React, React DOM,
|
||||
Lucide icons and the locked scheduler dependency; it does not build the Monitor
|
||||
or run npm lifecycle scripts. `--legacy-peer-deps` admits the existing Lucide
|
||||
React peer constraint against locked React 19; it does not resolve that mismatch. The runner fails on missing files, zero discovered Python
|
||||
tests, skipped Python tests, or a nonzero exit from either lane. Node fixtures
|
||||
use top-level assertions, not a runner that reports case/skip totals.
|
||||
These are local fixture tests, not the whole `tests/` tree, `AppImage/tests/`,
|
||||
`AppImage/scripts/tests/`, browser smoke, a live Proxmox host, or an AppImage
|
||||
build. The CI workflow has read-only permissions and runs on relevant PRs and
|
||||
main/develop pushes, or manually. Review the input paths and the test's imports
|
||||
before extending the explicit manifest.
|
||||
|
||||
#### Monitor checks
|
||||
|
||||
- **Python tests** — under `AppImage/scripts/tests/`. Run with `python3 -m unittest discover -s AppImage/scripts/tests`. Add a test file when you add non-trivial backend logic (auth, notifications, background checks).
|
||||
|
||||
Reference in New Issue
Block a user