1+ # Copyright 2026 Query Farm LLC - https://query.farm
2+ #
3+ # Unit tests (pytest), lint, and the sqllogictest extension suite
4+ # (test/sql/*.test) run against the vgi-news worker through the real signed
5+ # `vgi` DuckDB community extension via a prebuilt standalone `haybarn-unittest`.
6+ # The SQL E2E redirects every provider at a local mock news API — no keys, no
7+ # cost, no live egress. See ci/README.md for the design.
18name : CI
29
310on :
411 push :
512 branches : [main]
6- paths-ignore : ['README.md', 'CLAUDE.md' ]
13+ paths-ignore : ['README.md']
714 pull_request :
815 branches : [main]
9- paths-ignore : ['README.md', 'CLAUDE.md' ]
16+ paths-ignore : ['README.md']
1017 workflow_dispatch :
1118
1219permissions :
1320 contents : read
1421
22+ concurrency :
23+ group : ci-${{ github.workflow }}-${{ github.ref }}
24+ cancel-in-progress : true
25+
1526jobs :
1627 test :
1728 name : Python ${{ matrix.python-version }} on ${{ matrix.os }}
@@ -21,60 +32,105 @@ jobs:
2132 matrix :
2233 os : [ubuntu-latest, macos-latest]
2334 python-version : ["3.13"]
35+
2436 steps :
25- - uses : actions/checkout@v4
37+ - uses : actions/checkout@v6
2638
2739 - name : Install uv
28- uses : astral-sh/setup-uv@v3
40+ uses : astral-sh/setup-uv@v8.2.0
2941
3042 - name : Set up Python ${{ matrix.python-version }}
3143 run : uv python install ${{ matrix.python-version }}
3244
33- # Install from PyPI (not the local ../vgi-python path used for dev), so CI
34- # is self-contained.
3545 - name : Install dependencies
36- run : |
37- uv venv
38- uv pip install vgi-python "httpx>=0.27" "pyarrow>=16" pytest ruff mypy
39- uv pip install -e . --no-deps
46+ run : uv sync --frozen --all-extras
4047
41- - name : Lint (ruff + mypy)
42- run : |
43- uv run --no-sync ruff check .
44- uv run --no-sync mypy vgi_news/ news_worker.py
45-
46- # Unit + mock-server + worker-integration tests. Live (network) tests are
47- # excluded by default (pyproject addopts: -m 'not live'), so CI never
48- # touches a real news API.
48+ # The `live` marker (real GDELT over the network) is excluded by default
49+ # via the pyproject addopts.
4950 - name : Run tests
5051 run : uv run --no-sync pytest -q
5152
52- e2e-sql :
53- name : SQL end-to-end (haybarn, mocked)
53+ lint :
54+ name : Lint
5455 runs-on : ubuntu-latest
5556 steps :
56- - uses : actions/checkout@v4
57+ - uses : actions/checkout@v6
5758
5859 - name : Install uv
59- uses : astral-sh/setup-uv@v3
60+ uses : astral-sh/setup-uv@v8.2.0
61+
62+ - name : Install dependencies
63+ run : uv sync --frozen --all-extras
64+
65+ - name : Check linting
66+ run : uv run --no-sync ruff check .
67+
68+ - name : Type check (mypy)
69+ run : uv run --no-sync mypy vgi_news/ news_worker.py
70+
71+ # Resolve the latest published haybarn release once, so the whole matrix tests
72+ # the same version (and we never hardcode/pin it).
73+ resolve-haybarn :
74+ name : Resolve haybarn release
75+ runs-on : ubuntu-latest
76+ outputs :
77+ release : ${{ steps.r.outputs.release }}
78+ steps :
79+ - id : r
80+ run : |
81+ REL=$(gh release view --repo Query-farm-haybarn/haybarn --json tagName --jq .tagName)
82+ echo "release=$REL" >> "$GITHUB_OUTPUT"
83+ echo "Latest haybarn release: $REL"
84+ env :
85+ GH_TOKEN : ${{ github.token }}
86+
87+ integration :
88+ name : End-to-end SQL on ${{ matrix.os }}
89+ needs : resolve-haybarn
90+ strategy :
91+ fail-fast : false
92+ matrix :
93+ include :
94+ - { os: ubuntu-latest, asset: haybarn_unittest-linux-amd64.zip }
95+ - { os: macos-latest, asset: haybarn_unittest-osx-arm64.zip }
96+ runs-on : ${{ matrix.os }}
97+ defaults :
98+ run :
99+ shell : bash
100+ steps :
101+ - uses : actions/checkout@v6
102+
103+ - name : Install uv
104+ uses : astral-sh/setup-uv@v8.2.0
60105
61106 - name : Set up Python 3.13
62107 run : uv python install 3.13
63108
64- - name : Install worker dependencies
109+ - name : Install the worker (from the lockfile)
110+ run : uv sync --frozen --python 3.13
111+
112+ - name : Download haybarn-unittest
65113 run : |
66- uv venv
67- uv pip install vgi-python "httpx>=0.27" "pyarrow>=16"
68- uv pip install -e . --no-deps
114+ gh release download "$HAYBARN_RELEASE" \
115+ --repo Query-farm-haybarn/haybarn \
116+ --pattern '${{ matrix.asset }}' \
117+ --output haybarn-unittest.zip --clobber
118+ mkdir -p hb && unzip -o -q haybarn-unittest.zip -d hb
119+ env :
120+ GH_TOKEN : ${{ github.token }}
121+ HAYBARN_RELEASE : ${{ needs.resolve-haybarn.outputs.release }}
69122
70- - name : Install haybarn-unittest
123+ - name : Resolve runner + worker paths
71124 run : |
72- uv tool install haybarn-unittest
73- echo "$HOME/.local/bin" >> "$GITHUB_PATH"
125+ # Absolute paths: run-integration.sh cd's into a staging dir before
126+ # invoking the runner, so relative paths would not resolve. The worker
127+ # runs from the synced .venv (deps resolved from PyPI via the lockfile);
128+ # plain `.venv/bin/python` ignores the PEP 723 header. run-integration.sh
129+ # starts the mock news API and redirects both providers at it.
130+ UNITTEST="$PWD/$(find hb -name 'haybarn-unittest' -type f | head -1)"
131+ chmod +x "$UNITTEST"
132+ echo "HAYBARN_UNITTEST=$UNITTEST" >> "$GITHUB_ENV"
133+ echo "VGI_NEWS_WORKER=$PWD/.venv/bin/python $PWD/news_worker.py" >> "$GITHUB_ENV"
74134
75- # Drives the worker (as a DuckDB subprocess) against a local mock news API.
76- # No network egress to GDELT/NewsAPI, no API keys.
77- - name : Run SQL end-to-end tests
78- env :
79- VGI_NEWS_WORKER : .venv/bin/python news_worker.py
80- run : uv run --no-sync python scripts/run_sql_e2e.py
135+ - name : Run extension integration suite
136+ run : ci/run-integration.sh
0 commit comments