forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
172 lines (162 loc) · 8.31 KB
/
Copy pathbackend-tests.yml
File metadata and controls
172 lines (162 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
name: Backend tests
on:
# `main` only. Matching every branch here would double every PR's CI:
# pushing to a PR branch fires BOTH the push event and the pull_request
# event for the same commit, running each job twice for an identical
# result. The pull_request trigger below already covers branches.
#
# This run is still worth keeping for main itself - it is post-merge
# validation against the real merge commit, which is exactly what caught
# the flaky staleness boundary test in #32 (green on the PR, red on the
# merge).
push:
branches: [main]
# Deliberately NOT narrowed with `paths:`, even though that is the obvious
# way to stop a docs-only PR standing up a Postgres container. A workflow
# skipped by a path filter reports no status at all, and a required check
# that reports no status blocks the PR forever rather than passing it - the
# same trap pr-issue-link.yml documents for job-level `if:`. The scoping
# happens inside each job instead, where it can still finish green.
pull_request:
branches: [main]
# The event a merge queue raises checks on. This has to be in place before
# the queue is switched on rather than after: a required check that never
# reports does not fail a queue entry, it hangs it until the queue times out
# and ejects the pull request. BRANCHING.md holds the rest of the setup.
#
# Deliberately unfiltered, unlike the two triggers above. A queue entry's ref
# is `refs/heads/gh-readonly-queue/main/pr-N-<sha>` rather than `main`, and
# `merge_group`'s `branches:` filter is undocumented as to which of those two
# it matches (github/docs#32879). A filter that guesses wrong costs the same
# hang as no trigger at all, and there is nothing to scope anyway - a queue
# is enabled per branch in the ruleset, and only `main` has one.
merge_group:
# Narrower than the default token. `pull-requests: read` is what lets the
# scope step ask which files the pull request touches.
permissions:
contents: read
pull-requests: read
jobs:
# One job, one database engine. There used to be a second job running the
# same suite against DuckDB, back when local dev ran on DuckDB and the pair
# existed to prove the two agreed. Local dev runs on a real Postgres now
# (backend/scripts/local-postgres.sh), so the DuckDB job was testing an
# engine nothing uses - and a green result there said nothing about the
# database this backend writes to. DuckDB stays where it earns its keep, in
# the analytics pipeline (pipeline/), which has its own workflow.
pytest-postgres:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
# Worth knowing before it looks like a bug: a service container starts
# with the job, before any step runs, so this Postgres still boots on a
# run that turns out to have nothing to test. Only a job-level `if:` could
# avoid that, and that is the one thing this file will not do (see the
# note on the trigger above - the steps carry the condition instead, so a
# PR touching nothing under backend/ still reports success having done
# nothing). A few seconds of an idle container is the price of a check
# that always reports.
services:
postgres:
# Matches the major version the Supabase project actually runs
# (17.6 as of 2026-08-07). It is the gate, so it is the one that
# should track production rather than whatever a developer's
# machine happens to have installed.
image: postgres:17
env:
POSTGRES_USER: ourhike
POSTGRES_PASSWORD: ourhike
POSTGRES_DB: ourhike_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
# The same URL shape tests/conftest.py falls back to locally, pointed at
# the service container above instead of a developer's own Postgres.
# Nothing else differs between a local run and this one - which is the
# point: the correctness gate and the loop you develop in are the same
# engine, differing only in where it runs. See backend/README.md.
DATABASE_URL: postgresql+psycopg://ourhike:ourhike@localhost:5432/ourhike_test
# The pooled counterpart, set up by the pgbouncer step below. Without
# it tests/test_pooler.py skips, and the failure it guards against -
# psycopg's prepared statements through a transaction pooler - is one
# that shows up in production and nowhere else.
POOLER_DATABASE_URL: postgresql+psycopg://ourhike:ourhike@127.0.0.1:6432/ourhike_test
steps:
# Checkout is unconditional because the scope action lives in this
# repository and has to be on disk before it can decide anything.
- uses: actions/checkout@v4
- id: scope
uses: ./.github/actions/changed-paths
with:
# backend/ carries its own requirements and pyproject, and nothing
# outside it is imported by the backend - checked, not assumed. The
# workflow and the action are in the list so a change to the gate
# itself still proves the suite it gates.
#
# The six client modules are here because three suites READ them as
# text. tests/test_preferences_contract.py,
# tests/test_client_report_contract.py and
# tests/test_client_response_contract.py compare the TypeScript
# halves of UserPreferences, ReportDraft, the moderation statuses,
# the photo cap, the closure vocabulary and every response shape
# against this backend's schemas and its OpenAPI document. The drift
# they exist to catch is far more likely to arrive in a client-only
# pull request than in a backend one; scoped out, the guard would run
# on every change except the change it is for. TESTING.md's rule, in
# the "Redundancy" section: **a suite's scope list includes every
# file its tests read.**
#
# Named files rather than `client/src/lib/`, which is what this said
# first. The directory is touched by most client work and the suite
# is a Postgres service plus 400 tests, so the broad prefix spent a
# backend CI run on nearly every client pull request to catch drift
# in a handful of rarely-edited modules. The narrow list is only
# honest while it is complete, which is not something to leave to
# memory - so tests/test_ci_scope.py reads this line back and fails
# if a contract test starts reading a client file that is not on it.
# It did, when api.ts and closureBanner.ts joined the list.
paths: >-
backend/
client/src/lib/api.ts
client/src/lib/closureBanner.ts
client/src/lib/outbox.ts
client/src/lib/reportPhoto.ts
client/src/lib/reportStatus.ts
client/src/lib/userPreferences.ts
.github/workflows/backend-tests.yml
.github/actions/changed-paths/
- uses: actions/setup-python@v5
if: steps.scope.outputs.run == 'true'
with:
python-version: "3.14"
- name: Install dependencies
if: steps.scope.outputs.run == 'true'
run: pip install -r requirements-dev.txt
- name: Start a transaction-mode pooler
if: steps.scope.outputs.run == 'true'
# The same script a developer runs, rather than a second copy of the
# configuration living in this file - the pooler's mode and its
# max_prepared_statements are the parts under test, and two copies of
# them is one copy that goes stale.
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq pgbouncer
# The package starts its own service on 6432 with a config that
# routes no databases. Ours needs the port, and the script refuses
# to assume an occupied one is usable.
sudo systemctl stop pgbouncer || true
bash scripts/local-pooler.sh
- name: Lint
if: steps.scope.outputs.run == 'true'
run: python -m ruff check .
- name: Check formatting
if: steps.scope.outputs.run == 'true'
run: python -m ruff format --check .
- name: Run tests
if: steps.scope.outputs.run == 'true'
run: python -m pytest -v