diff --git a/.gitea/workflows/smoke.yml b/.gitea/workflows/smoke.yml new file mode 100644 index 0000000..e254579 --- /dev/null +++ b/.gitea/workflows/smoke.yml @@ -0,0 +1,24 @@ +name: Smoke Test + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + smoke-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install PyYAML + run: pip install pyyaml + + - name: Run smoke test + run: bash scripts/smoke.sh diff --git a/scripts/smoke.sh b/scripts/smoke.sh new file mode 100755 index 0000000..4c8fd6a --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# The Testament — Smoke Test +# Dead simple CI: parse check + secret scan. +# Ref: https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/issues/27 +set -euo pipefail + +PASS=0 +FAIL=0 + +pass() { echo " ✓ $1"; PASS=$((PASS + 1)); } +fail() { echo " ✗ $1"; FAIL=$((FAIL + 1)); } + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +# ─── Section 1: Parse checks ─────────────────────────────────────── +echo "── Parse Checks ──" + +# 1a. Chapter validation (structure, numbering, headers) +if python3 compile.py --validate 2>&1; then + pass "Chapter validation passed" +else + fail "Chapter validation failed" +fi + +# 1b. Build markdown combination +if python3 build/build.py --md >/dev/null 2>&1; then + pass "Markdown build passed" +else + fail "Markdown build failed" +fi + +# 1c. Verify compiled output exists and is non-empty +if [ -s build/the-testament-full.md ]; then + WORDS=$(wc -w < build/the-testament-full.md | tr -d ' ') + if [ "$WORDS" -gt 10000 ]; then + pass "Compiled manuscript: $WORDS words" + else + fail "Compiled manuscript suspiciously short: $WORDS words" + fi +else + fail "Compiled manuscript missing or empty" +fi + +# 1d. Python syntax check on all .py files +PY_OK=true +for f in $(find . -name "*.py" -not -path "./.git/*"); do + if ! python3 -c "import ast; ast.parse(open('$f').read())" 2>/dev/null; then + fail "Python syntax error in $f" + PY_OK=false + fi +done +if $PY_OK; then + pass "All Python files parse cleanly" +fi + +# 1e. YAML syntax check on workflow files +YAML_OK=true +for f in $(find .gitea -name "*.yml" -o -name "*.yaml" 2>/dev/null); do + if ! python3 -c "import yaml; yaml.safe_load(open('$f'))" 2>/dev/null; then + fail "YAML syntax error in $f" + YAML_OK=false + fi +done +if $YAML_OK; then + pass "All YAML files parse cleanly" +fi + +# ─── Section 2: Secret scan ──────────────────────────────────────── +echo "" +echo "── Secret Scan ──" + +# Patterns that should never appear in a book repo +SECRET_PATTERNS=( + "sk-ant-" + "sk-or-" + "sk-[a-zA-Z0-9]{20,}" + "ghp_[a-zA-Z0-9]{36}" + "gho_[a-zA-Z0-9]{36}" + "AKIA[0-9A-Z]{16}" + "AKIA[A-Z0-9]{16}" + "xox[bpsa]-" + "SG\." + "-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY" +) + +FOUND_SECRETS=false +for pattern in "${SECRET_PATTERNS[@]}"; do + # Search text files only, skip .git and binary files + HITS=$(grep -rn "$pattern" --include="*.md" --include="*.py" --include="*.sh" --include="*.yml" --include="*.yaml" --include="*.json" --include="*.html" --include="*.js" --include="*.css" --include="*.txt" --include="*.cfg" --include="*.ini" --exclude-dir=.git . 2>/dev/null | grep -v "scripts/smoke.sh" || true) + if [ -n "$HITS" ]; then + fail "Possible secret found: $pattern" + echo "$HITS" | head -5 + FOUND_SECRETS=true + fi +done +if ! $FOUND_SECRETS; then + pass "No secrets detected" +fi + +# ─── Summary ─────────────────────────────────────────────────────── +echo "" +echo "Results: $PASS passed, $FAIL failed" + +if [ "$FAIL" -gt 0 ]; then + echo "SMOKE TEST FAILED" + exit 1 +else + echo "SMOKE TEST PASSED" + exit 0 +fi