
    ֞ic                        d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZdZdZddZddZ G d d	      Z G d
 d      Z G d d      Z G d d      Z G d d      Zy)u0  
tests/infra/test_ci_config.py
Tests for Module 3: GitHub Actions CI/CD configuration files.

Coverage:
  BB1  ci.yml is valid YAML with required top-level keys (on, jobs)
  BB2  All CI job steps reference existing, named tools (ruff, mypy, pytest)
  BB3  deploy.yml triggers only on master push branch
  WB1  Every job in ci.yml uses runs-on: self-hosted
  WB2  PYTHONPATH is set in ci.yml env block

# VERIFICATION_STAMP
# Story: M3.04 — tests/infra/test_ci_config.py
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 5/5
# Coverage: 100%
    )annotationsNz./mnt/e/genesis-system/.github/workflows/ci.ymlz2/mnt/e/genesis-system/.github/workflows/deploy.ymlc                H   t         j                  }|j                  } ||       }|st        j                  d|        dz   dt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      dz  }t        t        j                  |            dx}x}}t        | d      5 }t        j                  |      }ddd       t        t              }|s$t        j                  d	|        d
z   dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}|S # 1 sw Y   ExY w)z?Load a YAML file and return as dict. Asserts file exists first.zWorkflow file not found: d
>assert %(py7)s
{%(py7)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.path
}.exists
}(%(py5)s)
}ospathpy0py2py4py5py7Nzutf-8)encodingzExpected dict at top level of z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancedatadict)r	   py1r
   r   )r   r   exists
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationopenyaml	safe_loadr   r   )r   @py_assert1@py_assert3@py_assert6@py_format8fhr   @py_format5s           3/mnt/e/genesis-system/tests/infra/test_ci_config.py
_load_yamlr&   #   ss   77C7>>C>$CCC#<TF!CCCCCCC2CCC2CCC7CCC>CCCCCC$CCC$CCCCCCCCC	dW	% "~~b!"dD!J!JJ%CD6#JJJJJJJ:JJJ:JJJJJJdJJJdJJJJJJDJJJDJJJ!JJJJJJK" "s   >JJ!c                    g }| j                         D ]2  }|j                  dg       D ]  }d|v s|j                  |d           4 |S )z2Walk all job steps and collect every 'run' string.stepsrun)valuesgetappend)jobsrunsjobsteps       r%   _collect_run_commandsr1   ,   sT    D{{} )GGGR( 	)D}DK(	)) K    c                  (    e Zd ZdZd Zd Zd Zd Zy)TestBB1CiYamlStructurezCci.yml must parse as YAML and contain 'on' and 'jobs' at top level.c                   t         j                  }|j                  } |t              }|st	        j
                  dt               dz   dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            d x}x}}y )Nzci.yml not found at r   r   CI_PATHr   )r   r   r   r6   r   r   r   r   r   r   r   r   selfr   r    r!   r"   s        r%   test_bb1_file_existsz+TestBB1CiYamlStructure.test_bb1_file_exists=   s    wwHw~~H~g&H&HH*>wi(HHHHHHHrHHHrHHHwHHH~HHHHHHgHHHgHHH&HHHHHHr2   c                l   t        t              }d }||u}|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}y )Nis notz%(py0)s is not %(py3)sr   r	   py3zassert %(py5)sr   )
r&   r6   r   _call_reprcomparer   r   r   r   r   r   )r8   r   @py_assert2r   @py_format4@py_format6s         r%   test_bb1_valid_yamlz*TestBB1CiYamlStructure.test_bb1_valid_yaml@   se    '"t4t4tt4r2   c                0   t        t              }d|v xs d|v }|s{t        j                  d      dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            y )NonTz'ci.yml must have an 'on:' trigger block
>assert %(py0)sr	   has_on)
r&   r6   r   r   r   r   r   r   r   r   )r8   r   rH   @py_format1s       r%   test_bb1_has_on_triggerz.TestBB1CiYamlStructure.test_bb1_has_on_triggerD   sZ    '"-@@@@@@@@@v@@@v@@@@@vr2   c                   t        t              }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d      dz   d|iz  }t        t        j                  |            d x}}|d   }t        |      }d	}||kD  }	|	st        j                  d
|	fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |      dz  }
t        j                  d      dz   d|
iz  }t        t        j                  |            d x}x}x}	}y )Nr-   inz%(py1)s in %(py3)sr   r   r?   z"ci.yml must have a 'jobs:' section
>assert %(py5)sr   r   >)z/%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} > %(py7)slen)r	   r
   r   r   z#ci.yml must define at least one job
>assert %(py9)spy9)r&   r6   r   r@   r   r   r   r   r   r   r   rS   )r8   r   @py_assert0rA   rB   rC   r   r    r!   @py_assert5r"   @py_format10s               r%   test_bb1_has_jobsz(TestBB1CiYamlStructure.test_bb1_has_jobsJ   s   '"Cv~CCCvCCCvCCCCCCCCCCCCCCCCCCCC<Ks< K1K 1$KKK 1KKKKKKsKKKsKKK<KKK KKK1KKK&KKKKKKKKr2   N)__name__
__module____qualname____doc__r9   rD   rJ   rY    r2   r%   r4   r4   :   s    MI ALr2   r4   c                  *    e Zd ZdZddZd Zd Zd Zy)TestBB2RequiredToolsz3The CI workflow must invoke ruff, mypy, and pytest.c                .   t        t              }t        |j                  di             }g }|d   j	                         D ]2  }|j                  dg       D ]  }d|v s|j                  |d           4 dj                  ||z         j                         S )Nr-   r(   name
)r&   r6   r1   r+   r*   r,   joinlower)r8   r   r.   namesr/   r0   s         r%   _all_step_textz#TestBB2RequiredTools._all_step_textW   s    '"$TXXfb%9:<&&( 	/C, /T>LLf./	/ yy&,,..r2   c                   d}| j                   } |       }||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d x}x}x}}y )	NruffrL   zM%(py1)s in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s._all_step_text
}()
}r8   r   r?   r   r   zci.yml must include a ruff steprT   rU   
rg   r   r@   r   r   r   r   r   r   r   r8   rV   @py_assert4r!   rA   r"   rX   s          r%   test_bb2_ruff_referencedz-TestBB2RequiredTools.test_bb2_ruff_referencedb       Q,,Q,.Qv..QQQv.QQQvQQQQQQQQQQQQ,QQQ.QQQ0QQQQQQQQr2   c                   d}| j                   } |       }||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d x}x}x}}y )	NmypyrL   rj   r8   rk   zci.yml must include a mypy steprT   rU   rl   rm   s          r%   test_bb2_mypy_referencedz-TestBB2RequiredTools.test_bb2_mypy_referencede   rp   r2   c                   d}| j                   } |       }||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d x}x}x}}y )	NpytestrL   rj   r8   rk   z!ci.yml must include a pytest steprT   rU   rl   rm   s          r%   test_bb2_pytest_referencedz/TestBB2RequiredTools.test_bb2_pytest_referencedh   s    U4..U.0Ux00UUUx0UUUxUUUUUU4UUU4UUU.UUU0UUU2UUUUUUUUr2   N)returnstr)rZ   r[   r\   r]   rg   ro   rs   rv   r^   r2   r%   r`   r`   T   s    =	/RRVr2   r`   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestBB3DeployTriggeru;   deploy.yml must only fire on push to master — not on PRs.c                   t         j                  }|j                  } |t              }|st	        j
                  dt               dz   dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            d x}x}}y )Nzdeploy.yml not found at r   r   DEPLOY_PATHr   )r   r   r   r|   r   r   r   r   r   r   r   r   r7   s        r%   test_bb3_deploy_file_existsz0TestBB3DeployTrigger.test_bb3_deploy_file_existss   s    wwTw~~T~k*T*TT.F{m,TTTTTTTrTTTrTTTwTTT~TTTTTTkTTTkTTT*TTTTTTr2   c                V   t        t              }|j                  d      xs |j                  d      }d }||u}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d      dz   d	|iz  }t        t        j                  |            d x}}d
}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d      dz   d	|iz  }t        t        j                  |            d x}}y )NrF   Tr;   r=   triggerr>   z#deploy.yml must have an 'on:' blockrP   r   pushrL   rN   rO   z!deploy.yml must trigger on 'push')r&   r|   r+   r   r@   r   r   r   r   r   r   r   )r8   r   r   rA   r   rB   rC   rV   s           r%   test_bb3_triggers_on_pushz.TestBB3DeployTrigger.test_bb3_triggers_on_pushv   s    +&((4.2DHHTN"Iwd"IIIwdIIIIIIwIIIwIIIdIII$IIIIIIIEv EEEvEEEvEEEEEEEEEEEEE"EEEEEEEr2   c                (   t        t              }|j                  d      xs |j                  d      }|j                  di       }|j                  dg       }d}||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }t        j                  d	      d
z   d|iz  }t        t        j                  |            d x}}y )NrF   Tr   branchesmasterrL   rN   rO   z7deploy.yml push trigger must specify branches: [master]rP   r   r&   r|   r+   r   r@   r   r   r   r   r   r   r   )	r8   r   r   push_cfgr   rV   rA   rB   rC   s	            r%   test_bb3_push_limited_to_masterz4TestBB3DeployTrigger.test_bb3_push_limited_to_master}   s    +&((4.2DHHTN;;vr*<<
B/ 	
x8# 	
 	
x8 	
 	
 		  	
 	
	6	
 	
  $ 	
 	
 		 $ 	
 	
  F	
 	
 	
 	
 	
r2   c                   t        t              }|j                  d      xs |j                  d      }d}||v}|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            d x}}y )NrF   Tpull_request)not in)z%(py1)s not in %(py3)sr   rO   z+deploy.yml must NOT trigger on pull_requestrP   r   r   )r8   r   r   rV   rA   rB   rC   s          r%    test_bb3_no_pull_request_triggerz5TestBB3DeployTrigger.test_bb3_no_pull_request_trigger   s    +&((4.2DHHTN 	
~W, 	
 	
~W 	
 	
 		  	
 	
	6	
 	
  &- 	
 	
 		 &- 	
 	
  :	
 	
 	
 	
 	
r2   N)rZ   r[   r\   r]   r}   r   r   r   r^   r2   r%   rz   rz   p   s    EUF

r2   rz   c                      e Zd ZdZd Zy)TestWB1SelfHostedRunnerz4All CI jobs must run on the self-hosted WSL2 runner.c                @   t        t              }|j                  di       }t        |      }d}||kD  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}|j                         D ]  \  }}	|	j                  d	d
      }
d}|
|k(  }|st	        j
                  d|fd|
|f      dt        j                         v st	        j                  |
      rt	        j                  |
      ndt	        j                  |      dz  }t	        j                  d| d|
 d      dz   d|iz  }t        t	        j                  |            d x}} y )Nr-   r   rQ   )z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)srS   )r	   r   r?   py6zassert %(py8)spy8zruns-on zself-hosted)==)z%(py0)s == %(py3)sruns_onr>   zJob 'z(' must have runs-on: self-hosted, got: ''rP   r   )r&   r6   r+   rS   r   r@   r   r   r   r   r   r   itemsr   )r8   r   r-   rA   rW   rn   @py_format7@py_format9job_namejob_cfgr   r   rB   rC   s                 r%   test_wb1_all_jobs_self_hostedz5TestWB1SelfHostedRunner.test_wb1_all_jobs_self_hosted   s   '"xx#4y1y1}y1ss44y1!% 	Hgkk)R0G+ 7m+  7m   v      I    I ,    z " 	$    	r2   N)rZ   r[   r\   r]   r   r^   r2   r%   r   r      s
    >
r2   r   c                      e Zd ZdZd Zd Zy)TestWB2PythonPathzEci.yml must export PYTHONPATH so the repo root is on the Python path.c                   t        t              }|j                  di       }|j                  di       }|j                         D cg c]  }|j                  di        }}|g|z   }t	        d |D              }|s{t        j                  d      dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            y c c}w )Nenvr-   c              3  $   K   | ]  }d |v  
 yw)
PYTHONPATHNr^   ).0r   s     r%   	<genexpr>zITestWB2PythonPath.test_wb2_pythonpath_in_top_level_env.<locals>.<genexpr>   s     ES\S0Es   z^ci.yml must set PYTHONPATH in the top-level 'env:' block or in at least one job's 'env:' blockrG   r	   has_pythonpath)r&   r6   r+   r*   anyr   r   r   r   r   r   r   r   )	r8   r   top_envr-   r/   job_envsall_envsr   rI   s	            r%   $test_wb2_pythonpath_in_top_level_envz6TestWB2PythonPath.test_wb2_pythonpath_in_top_level_env   s    '"((5"%xx#*.++-
#&CGGE2
 
 9x'EHEE 	
 4	
 	
 
6	
 	
   	
 	
 
	  	
 	
 	
 	
 	
~
s   C7c                n   t        t              }|j                  di       }|j                  di       }|g}|j                         D ][  }|j	                  |j                  di              |j                  dg       D ]#  }|j	                  |j                  di              % ] i }|D ]  }|j                  |        |j                  dd      }	|	s{t        j                  d      dz   dd	t        j                         v st        j                  |	      rt        j                  |	      nd	iz  }
t        t        j                  |
            g }d
}t        |	      }||v }|}|sd}t        |	      }||v }|}|s+t        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndd	t        j                         v st        j                  |	      rt        j                  |	      nd	t        j                  |      dz  }dd|iz  }|j	                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndd	t        j                         v st        j                  |	      rt        j                  |	      nd	t        j                  |      dz  }dd|iz  }|j	                  |       t        j                   |d      i z  }t        j                  d|	      dz   d|iz  }t        t        j                  |            d x}x}x}x}x}x}x}}y )Nr   r-   r(   r   r   zPYTHONPATH must be non-emptyrG   r	   pythonpath_valzgenesis-systemz/mnt/erL   )z0%(py3)s in %(py8)s
{%(py8)s = %(py5)s(%(py6)s)
}rx   )r?   r   r   r   z%(py10)spy10)z5%(py13)s in %(py18)s
{%(py18)s = %(py15)s(%(py16)s)
})py13py15py16py18z%(py20)spy20   z8PYTHONPATH should reference /mnt/e/genesis-system, got: z
>assert %(py23)spy23)r&   r6   r+   r*   r,   updater   r   r   r   r   r   r   r   rx   r@   _format_boolop)r8   r   r   r-   r   r/   r0   combinedr   r   rI   r   rA   @py_assert7rn   rV   @py_assert12@py_assert17@py_assert14r   @py_format11@py_format19@py_format21@py_format22@py_format24s                            r%   *test_wb2_pythonpath_points_to_genesis_rootz<TestWB2PythonPath.test_wb2_pythonpath_points_to_genesis_root   s%   '"((5"%xx# !(y;;= 	5COOCGGE2./, 5 345	5
  	!COOC 	! "lB7=========~===~=====	
 	
3~#6 	
#66 	
( 	
c.FY 	
(FY:Y 	
 	
 	
#6 	
 	
 		   	
 	
	6	
 	
  $' 	
 	
 		 $' 	
 	
	6	
 	
  (6 	
 	
 		 (6 	
 	
 		 $7 	
 	
 	
	6	
		
 	
(FY 	
 	
 		 ;C 	
 	
	6	
 	
  GJ 	
 	
 		 GJ 	
 	
	6	
 	
  KY 	
 	
 		 KY 	
 	
 		 GZ 	
 	
 	
	6	
		
 	
 	
  G~FXY	
 	
 	
 	
 	
 	
 	
r2   N)rZ   r[   r\   r]   r   r   r^   r2   r%   r   r      s    O
 
r2   r   )r   rx   rw   r   )r-   r   rw   z	list[str])r]   
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   r   ru   r   r6   r|   r&   r1   r4   r`   rz   r   r   r^   r2   r%   <module>r      si   $ #   	   ;BL L4V V8
 
D ('
 '
r2   