@@ -37,6 +37,7 @@ def test_has_free_tier_job(self):
3737 def test_has_integration_postgres_job (self ):
3838 self .assertIn ("integration-postgres" , self .jobs )
3939
40+ def test_has_windows_postgres_job (self ):
4041 self .assertIn ("windows-postgres" , self .jobs )
4142
4243 def test_triggers_on_pull_request (self ):
@@ -50,32 +51,129 @@ def test_triggers_on_push_to_main(self):
5051 self .assertIn ("main" , push_branches )
5152
5253
54+ class TestWindowsPostgresJob (unittest .TestCase ):
55+ """The windows-postgres job must have all required steps and config."""
5356
54- class TestAllJobsHaveConsistentStructure (unittest .TestCase ):
55- """Every job must have checkout + install-deps. Python jobs additionally
56- need Python setup and an artifact upload; Node jobs need Node setup.
57+ @classmethod
58+ def setUpClass (cls ):
59+ if not WORKFLOW .exists ():
60+ raise AssertionError (f"Workflow file not found: { WORKFLOW } " )
61+ wf = _load_workflow ()
62+ cls .job = wf .get ("jobs" , {}).get ("windows-postgres" , {})
63+ cls .step_names = [
64+ s .get ("name" , "" ) for s in cls .job .get ("steps" , [])
65+ ]
66+
67+ def test_runs_on_windows (self ):
68+ self .assertEqual (self .job .get ("runs-on" ), "windows-latest" )
69+
70+ def test_sets_pg_env_vars (self ):
71+ env = self .job .get ("env" , {})
72+ for var in ("PGHOST" , "PGPORT" , "PGUSER" , "PGPASSWORD" , "PGDATABASE" ):
73+ self .assertIn (var , env , f"Missing env var: { var } " )
74+
75+ def test_has_checkout_step (self ):
76+ self .assertTrue (
77+ any ("Checkout" in n for n in self .step_names ),
78+ "Missing Checkout step" ,
79+ )
80+
81+ def test_has_python_setup_step (self ):
82+ self .assertTrue (
83+ any ("Setup Python" in n for n in self .step_names ),
84+ "Missing Setup Python step" ,
85+ )
86+
87+ def test_has_postgres_start_step (self ):
88+ self .assertTrue (
89+ any ("PostgreSQL" in n for n in self .step_names ),
90+ "Missing PostgreSQL start/verify step" ,
91+ )
92+
93+ def test_has_database_config_step (self ):
94+ self .assertTrue (
95+ any ("config" in n .lower () for n in self .step_names ),
96+ "Missing CI database config step" ,
97+ )
98+
99+ def test_has_create_databases_step (self ):
100+ self .assertTrue (
101+ any ("database" in n .lower () for n in self .step_names ),
102+ "Missing create environment databases step" ,
103+ )
104+
105+ def test_has_evals_step (self ):
106+ self .assertTrue (
107+ any ("Eval" in n for n in self .step_names ),
108+ "Missing evals step" ,
109+ )
110+
111+ def test_has_deploy_step (self ):
112+ self .assertTrue (
113+ any ("Deploy" in n for n in self .step_names ),
114+ "Missing deploy environments step" ,
115+ )
116+
117+ def test_has_test_suite_step (self ):
118+ self .assertTrue (
119+ any ("test suite" in n .lower () or "test_report" in n .lower ()
120+ for n in self .step_names ),
121+ "Missing full test suite step" ,
122+ )
123+
124+ def test_has_upload_artifacts_step (self ):
125+ self .assertTrue (
126+ any ("Upload" in n for n in self .step_names ),
127+ "Missing upload eval reports step" ,
128+ )
129+
130+ def test_postgres_step_uses_pwsh (self ):
131+ pg_steps = [
132+ s for s in self .job .get ("steps" , [])
133+ if "PostgreSQL" in s .get ("name" , "" )
134+ ]
135+ self .assertTrue (len (pg_steps ) > 0 )
136+ self .assertEqual (pg_steps [0 ].get ("shell" ), "pwsh" )
137+
138+ def test_postgres_step_uses_runner_temp (self ):
139+ pg_steps = [
140+ s for s in self .job .get ("steps" , [])
141+ if "PostgreSQL" in s .get ("name" , "" )
142+ ]
143+ self .assertTrue (len (pg_steps ) > 0 )
144+ run_content = pg_steps [0 ].get ("run" , "" )
145+ self .assertIn ("RUNNER_TEMP" , run_content ,
146+ "Must use $RUNNER_TEMP for writable data directory" )
147+
148+ def test_postgres_step_uses_initdb (self ):
149+ pg_steps = [
150+ s for s in self .job .get ("steps" , [])
151+ if "PostgreSQL" in s .get ("name" , "" )
152+ ]
153+ self .assertTrue (len (pg_steps ) > 0 )
154+ run_content = pg_steps [0 ].get ("run" , "" )
155+ self .assertIn ("initdb" , run_content ,
156+ "Must initialise data directory with initdb" )
157+
158+ def test_postgres_step_uses_pg_ctl (self ):
159+ pg_steps = [
160+ s for s in self .job .get ("steps" , [])
161+ if "PostgreSQL" in s .get ("name" , "" )
162+ ]
163+ self .assertTrue (len (pg_steps ) > 0 )
164+ run_content = pg_steps [0 ].get ("run" , "" )
165+ self .assertIn ("pg_ctl" , run_content ,
166+ "Must start PostgreSQL with pg_ctl" )
57167
58- BUG-023 added the Node-backed ``frontend-build`` job, so the old
59- "every job needs Python" contract needed to split by job kind. Any new
60- job MUST be classified in PYTHON_JOBS or NODE_JOBS below — an
61- unclassified job fails setUpClass, so a contributor can't quietly bypass
62- the structural rules.
63- """
64168
65- PYTHON_JOBS = { "free-tier" , "integration-postgres" , "windows-postgres" }
66- NODE_JOBS = { "frontend-build" }
169+ class TestAllJobsHaveConsistentStructure ( unittest . TestCase ):
170+ """All jobs must have checkout, python setup, and artifact upload."""
67171
68172 @classmethod
69173 def setUpClass (cls ):
70174 if not WORKFLOW .exists ():
71175 raise AssertionError (f"Workflow file not found: { WORKFLOW } " )
72176 cls .jobs = _load_workflow ().get ("jobs" , {})
73- unclassified = set (cls .jobs ) - cls .PYTHON_JOBS - cls .NODE_JOBS
74- if unclassified :
75- raise AssertionError (
76- f"quality-gate.yml has unclassified jobs: { sorted (unclassified )} . "
77- f"Add each to PYTHON_JOBS or NODE_JOBS in { __file__ } ."
78- )
79177
80178 def _step_names (self , job_id : str ) -> list [str ]:
81179 return [s .get ("name" , "" ) for s in self .jobs [job_id ].get ("steps" , [])]
@@ -88,44 +186,28 @@ def test_all_jobs_have_checkout(self):
88186 f"Job { job_id !r} missing Checkout step" ,
89187 )
90188
91- def test_all_jobs_install_deps (self ):
92- # "Install dependencies" (Node) and "Install dev dependencies" (Python)
93- # both match: literal "Install" plus lowercase "dep".
189+ def test_all_jobs_have_python_setup (self ):
94190 for job_id in self .jobs :
95191 names = self ._step_names (job_id )
96- self .assertTrue (
97- any ("Install" in n and "dep" in n .lower () for n in names ),
98- f"Job { job_id !r} missing install dependencies step" ,
99- )
100-
101- def test_python_jobs_have_python_setup (self ):
102- for job_id in self .PYTHON_JOBS :
103- if job_id not in self .jobs :
104- continue
105- names = self ._step_names (job_id )
106192 self .assertTrue (
107193 any ("Setup Python" in n or "Python" in n for n in names ),
108- f"Python job { job_id !r} missing Python setup step" ,
194+ f"Job { job_id !r} missing Python setup step" ,
109195 )
110196
111- def test_python_jobs_have_artifact_upload (self ):
112- for job_id in self .PYTHON_JOBS :
113- if job_id not in self .jobs :
114- continue
197+ def test_all_jobs_have_artifact_upload (self ):
198+ for job_id in self .jobs :
115199 names = self ._step_names (job_id )
116200 self .assertTrue (
117201 any ("Upload" in n for n in names ),
118- f"Python job { job_id !r} missing artifact upload step" ,
202+ f"Job { job_id !r} missing artifact upload step" ,
119203 )
120204
121- def test_node_jobs_have_node_setup (self ):
122- for job_id in self .NODE_JOBS :
123- if job_id not in self .jobs :
124- continue
205+ def test_all_jobs_install_dev_deps (self ):
206+ for job_id in self .jobs :
125207 names = self ._step_names (job_id )
126208 self .assertTrue (
127- any ("Setup Node " in n or "Node " in n for n in names ),
128- f"Node job { job_id !r} missing Node setup step" ,
209+ any ("Install " in n and "dep " in n . lower () for n in names ),
210+ f"Job { job_id !r} missing install dev dependencies step" ,
129211 )
130212
131213
0 commit comments