Skip to content

Commit df35f7b

Browse files
timon0305bradjin8
andauthored
ci: GitHub Actions Tests workflow — pytest matrix + mypy + gitleaks (closes #13) (#19)
* ci: add GitHub Actions workflow that runs the unittest suite (closes #13) There was no CI on this repository — 137 unit tests in tests/ were only ever run when a developer remembered to run them locally. A regression that broke CLI parity, exclusion rules, exporter output, alias inference, or search filtering could land on master with no gate. New workflow `.github/workflows/tests.yml`: - Triggers on every push to master and every pull request. - Single ubuntu-latest runner, Python 3.12. - Installs only what the tests need (flask, fpdf2). pywebview from requirements.txt is the desktop-launcher dep and pulls GTK / Qt system packages — out of scope for the unittest suite, so it is deliberately omitted from the CI install. The unittest suite imports neither. - Runs `python -m unittest discover tests -v`. Local sanity-check with the same command on Python 3.12: 137/137 OK. * ci: pin action versions to immutable commit SHAs (CodeRabbit on PR #14) Replace @v4 / @v5 tag refs with the matching commit SHAs on actions/checkout and actions/setup-python. Tags are mutable — a compromised maintainer can repoint them, silently swapping the code that runs in our CI runner. SHAs are immutable and remove that class of supply-chain risk. Verified each SHA against the live tag on github.com: gh api repos/actions/checkout/git/ref/tags/v4 \ --jq '.object.sha' # 34e114876b0b11c390a56381ad16ebd13914f8d5 gh api repos/actions/setup-python/git/ref/tags/v5 \ --jq '.object.sha' # a26af69be951a213d495a4c3e4e4022e16d87065 The trailing `# v4` / `# v5` comments preserve the major-version intent so future bumps stay deliberate. The leading comment block documents the bump procedure for the next person. * ci: expand to multi-OS + multi-Python matrix, add mypy + gitleaks (closes #13) The previous shape was a single ubuntu-latest / Python 3.12 unittest job. Expanded to match the broader gate quality the team adopted on the-claw: - unittest: 3 OSes × 3 Pythons = 9 cells (3.11 / 3.12 / 3.13 across ubuntu-latest, macos-latest, windows-latest). Catches Python version drift and the rare path / line-ending issue single-OS hides. fail-fast false so cells run independently. - typecheck: mypy on Python 3.12. Codebase already has 70+ typed functions across 30 .py files, so mypy actually does work. Lenient config (--ignore-missing-imports, --no-strict-optional) + continue-on-error step until the surface is clean. - secret-scan: gitleaks 8.21.2 with checksum verification (mirrors the-claw's setup verbatim). No project-specific .gitleaks.toml; uses defaults for standard credential patterns. Concurrency block added so a new push to the same ref cancels the in-flight run, reducing CI minutes. Action SHAs unchanged from the previous workflow (already pinned). * ci: explicit least-privilege GITHUB_TOKEN permissions (CodeRabbit on PR #19) Adds workflow-level `permissions: contents: read` so a compromised action step in any matrix cell can't write back to the repo. None of the jobs (unittest, typecheck, secret-scan) need write access — no commits, PR comments, or release publishes. Read-only is enough. * fix: remove other OS other than ubuntu --------- Co-authored-by: Monkey Dev <headit74@hotmail.com>
1 parent 634fcb8 commit df35f7b

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

.github/workflows/tests.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
8+
# Least-privilege GITHUB_TOKEN scope. None of these jobs need write access
9+
# (no commit, no PR comment, no release publish) — read-only is enough.
10+
# A compromised action in any matrix cell can't write back to the repo.
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
# ── Unit tests: matrix across OS and Python version ───────────────────────
20+
# Closes #13. The unittest suite is the merge gate. Multi-OS catches the
21+
# rare path / line-ending issue that a single-OS run hides; multi-Python
22+
# catches API drift across LTS / current / latest interpreters.
23+
unittest:
24+
name: Unit tests (${{ matrix.os }} / Python ${{ matrix.python-version }})
25+
runs-on: ${{ matrix.os }}
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
os: [ubuntu-latest]
30+
python-version: ["3.11", "3.12", "3.13"]
31+
steps:
32+
# Pinned to immutable commit SHAs (not @v4 / @v5) so a compromised tag
33+
# cannot silently swap the underlying action code on this CI runner.
34+
# When bumping, verify the new SHA via:
35+
# gh api repos/actions/<name>/git/ref/tags/<vN> --jq '.object.sha'
36+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
37+
38+
- name: Set up Python
39+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
40+
with:
41+
python-version: ${{ matrix.python-version }}
42+
43+
- name: Install runtime + test dependencies
44+
# Only what the tests actually exercise. `pywebview` from
45+
# requirements.txt is the desktop-launcher dep and pulls GTK / Qt
46+
# system packages on Linux — out of scope for the unittest suite.
47+
run: |
48+
python -m pip install --upgrade pip
49+
python -m pip install 'flask>=3.0' 'fpdf2>=2.7'
50+
51+
- name: Run unittest suite
52+
run: python -m unittest discover tests -v
53+
54+
# ── Typecheck: mypy ───────────────────────────────────────────────────────
55+
# Codebase already has type hints across most of the surface (~70+ typed
56+
# functions). Mypy runs in lenient mode (--ignore-missing-imports for
57+
# untyped third-party deps; no strict-optional) so the gate isn't a wall
58+
# of false positives on first run. continue-on-error keeps findings as
59+
# warnings during the surface-cleanup phase; flip to required by removing
60+
# continue-on-error once the surface is clean.
61+
typecheck:
62+
name: Typecheck (mypy)
63+
runs-on: ubuntu-latest
64+
steps:
65+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
66+
67+
- name: Set up Python
68+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
69+
with:
70+
python-version: "3.12"
71+
72+
- name: Install runtime deps + mypy
73+
run: |
74+
python -m pip install --upgrade pip
75+
python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'mypy>=1.10'
76+
77+
- name: Run mypy
78+
# Transitional only (maintainer consensus): keeps CI green until `mypy` exits
79+
# zero on this repo — then delete this line so type errors fail the job.
80+
continue-on-error: true
81+
run: mypy --ignore-missing-imports --no-strict-optional --pretty .
82+
83+
# ── Secret scan: gitleaks ─────────────────────────────────────────────────
84+
# Catches accidentally committed credentials. Runs over full git history
85+
# (fetch-depth: 0). No project-specific .gitleaks.toml — defaults cover
86+
# standard credential patterns (API keys, AWS, GitHub tokens, etc.).
87+
secret-scan:
88+
name: Secret scan (gitleaks)
89+
runs-on: ubuntu-latest
90+
steps:
91+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
92+
with:
93+
fetch-depth: 0
94+
95+
- name: Install gitleaks
96+
run: |
97+
GITLEAKS_VERSION=8.21.2
98+
base_url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
99+
tarball="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
100+
checksums="gitleaks_${GITLEAKS_VERSION}_checksums.txt"
101+
102+
# Download tarball and checksums file to temp; retries prevent
103+
# transient 5xx failures.
104+
curl --fail --location --silent --show-error \
105+
--retry 5 --retry-delay 2 --retry-all-errors \
106+
-o "/tmp/${tarball}" "${base_url}/${tarball}"
107+
curl --fail --location --silent --show-error \
108+
--retry 5 --retry-delay 2 --retry-all-errors \
109+
-o "/tmp/${checksums}" "${base_url}/${checksums}"
110+
111+
# Verify SHA-256 before extraction; fail and clean up on mismatch.
112+
expected=$(grep " ${tarball}$" "/tmp/${checksums}" | awk '{print $1}')
113+
if [ -z "${expected}" ]; then
114+
echo "::error::No checksum entry found for ${tarball}" >&2
115+
rm -f "/tmp/${tarball}" "/tmp/${checksums}"
116+
exit 1
117+
fi
118+
actual=$(sha256sum "/tmp/${tarball}" | awk '{print $1}')
119+
if [ "${expected}" != "${actual}" ]; then
120+
echo "::error::SHA-256 mismatch for ${tarball}: expected ${expected}, got ${actual}" >&2
121+
rm -f "/tmp/${tarball}" "/tmp/${checksums}"
122+
exit 1
123+
fi
124+
125+
tar -xz -f "/tmp/${tarball}" gitleaks
126+
sudo mv gitleaks /usr/local/bin/gitleaks
127+
rm -f "/tmp/${tarball}" "/tmp/${checksums}"
128+
129+
- name: Run gitleaks
130+
run: |
131+
gitleaks detect \
132+
--source . \
133+
--verbose \
134+
--redact \
135+
--exit-code 1

0 commit comments

Comments
 (0)