95 lines
3.5 KiB
Python
95 lines
3.5 KiB
Python
from pathlib import Path
|
|
import re
|
|
|
|
|
|
WORKFLOW = Path(".gitea/workflows/ci.yml")
|
|
ACTION_REFERENCE = re.compile(
|
|
r"^\s*- uses: (?P<action>[^\s@]+)@(?P<revision>[^\s#]+)\s+# (?P<version>v\d+(?:\.\d+){1,2})$",
|
|
re.MULTILINE,
|
|
)
|
|
DOWNLOAD_ARTIFACT = (
|
|
"actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2"
|
|
)
|
|
|
|
|
|
def test_ci_uses_gitea_compatible_artifact_action():
|
|
text = WORKFLOW.read_text()
|
|
assert "actions/upload-artifact@v4" not in text
|
|
assert re.search(
|
|
r"actions/upload-artifact@[0-9a-f]{40}\s+# v3\.\d+\.\d+", text
|
|
)
|
|
|
|
|
|
def test_ci_pins_every_external_action_to_a_reviewable_commit():
|
|
text = WORKFLOW.read_text()
|
|
uses_lines = [line for line in text.splitlines() if line.lstrip().startswith("- uses:")]
|
|
references = list(ACTION_REFERENCE.finditer(text))
|
|
|
|
assert len(references) == len(uses_lines)
|
|
assert references
|
|
for reference in references:
|
|
assert re.fullmatch(r"[0-9a-f]{40}", reference["revision"])
|
|
|
|
|
|
def test_ci_grants_repository_write_access_only_to_release_job():
|
|
text = WORKFLOW.read_text()
|
|
jobs = text[text.index("jobs:") :]
|
|
before_release, release = jobs.split(" release-candidate:", 1)
|
|
|
|
assert "permissions:\n contents: read" in text[: text.index("jobs:")]
|
|
assert "contents: write" not in before_release
|
|
assert "permissions:\n contents: write" in release
|
|
|
|
|
|
def test_ci_installs_declared_requirements_before_tests():
|
|
text = WORKFLOW.read_text()
|
|
install = text.index("pip install -r requirements.txt")
|
|
tests = text.index("python3 -m pytest tests/ -q")
|
|
assert install < tests
|
|
|
|
|
|
def test_ci_audits_pinned_runtime_dependencies_before_tests_and_release_build():
|
|
text = WORKFLOW.read_text()
|
|
lint = text[text.index(" lint:") : text.index(" build-release:")]
|
|
build = text[text.index(" build-release:") : text.index(" browser-journey:")]
|
|
|
|
audit_install = lint.index("pip install -r requirements-audit.txt")
|
|
audit = lint.index("python3 -m pip_audit -r requirements.txt --strict")
|
|
tests = lint.index("python3 -m pytest tests/ -q")
|
|
assert audit_install < audit < tests
|
|
assert "needs: lint" in build
|
|
|
|
|
|
def test_runtime_multipart_parser_uses_advisory_fixed_release():
|
|
requirements = Path("requirements.txt").read_text().splitlines()
|
|
assert "python-multipart==0.0.32" in requirements
|
|
|
|
|
|
def test_release_promotion_waits_for_tests_and_bundle():
|
|
text = WORKFLOW.read_text()
|
|
release = text[text.index(" release-candidate:") :]
|
|
|
|
assert "needs: [lint, build-release, browser-journey]" in release
|
|
assert "github.event_name == 'push'" in release
|
|
assert DOWNLOAD_ARTIFACT in release
|
|
assert (
|
|
'python3 scripts/verify_release.py --input-dir dist --commit "$TARGET" --repository .'
|
|
in release
|
|
)
|
|
assert release.index("sha256sum -c") < release.index("curl --fail-with-body")
|
|
|
|
|
|
def test_release_promotion_waits_for_every_packaged_mobile_journey():
|
|
text = WORKFLOW.read_text()
|
|
browser = text[text.index(" browser-journey:") : text.index(" release-candidate:")]
|
|
release = text[text.index(" release-candidate:") :]
|
|
|
|
assert "needs: build-release" in browser
|
|
assert DOWNLOAD_ARTIFACT in browser
|
|
assert "pip install -r requirements-e2e.txt" in browser
|
|
assert "python3 -m playwright install --with-deps chromium" in browser
|
|
assert 'STACKCHAIN_RUN_RELEASE_E2E: "1"' in browser
|
|
assert "python3 -m pytest tests/e2e -q" in browser
|
|
assert "test_mobile_" not in browser
|
|
assert "needs: [lint, build-release, browser-journey]" in release
|