
    i5                     H   d Z ddlZddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZ ddlZ e	e      j                  j                  Zedz  Zej$                  j'                  de      Zeej*                  J ej$                  j-                  e      Zeej0                  d<   ej*                  j3                  e        G d d      Z G d	 d
      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z  G d d      Z! G d d      Z" G d d      Z#y)u2   Tests for diff-aware-qa.py — TDD (RED → GREEN)    N)Path)	MagicMockpatchzdiff-aware-qa.pydiff_aware_qac                   $    e Zd Zd Zd Zd Zd Zy)TestParseGitDiffc                 D    d}t         j                  |      }|ddgk(  sJ y )Nz2src/api/users.py
frontend/components/UserList.tsx
src/api/users.pyz frontend/components/UserList.tsxqaparse_diff_outputselfrawresults      Q/home/jay/workspace/.worktrees/task-2116-dev1/scripts/tests/test_diff_aware_qa.pytest_parse_normal_outputz)TestParseGitDiff.test_parse_normal_output   s,    D%%c*,.PQQQQ    c                 <    t         j                  d      }|g k(  sJ y )N r   r   r   s     r   test_parse_empty_outputz(TestParseGitDiff.test_parse_empty_output"   s    %%b)||r   c                 D    d}t         j                  |      }|ddgk(  sJ y )Nz)  src/api/users.py  
  frontend/app.ts  
r
   zfrontend/app.tsr   r   s      r   test_parse_strips_whitespacez-TestParseGitDiff.test_parse_strips_whitespace&   s,    ;%%c*,.?@@@@r   c                 D    d}t         j                  |      }|ddgk(  sJ y )Nzfile_a.py

file_b.ts

z	file_a.pyz	file_b.tsr   r   s      r   test_parse_ignores_blank_linesz/TestParseGitDiff.test_parse_ignores_blank_lines+   s*    *%%c*+{3333r   N)__name__
__module____qualname__r   r   r   r    r   r   r   r      s    R
A
4r   r   c                   N    e Zd Zd Zd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zy)TestClassifyFilesc                 B    t         j                  dg      }d|d   v sJ y )Nzsrc/routes/users.pybackendr   classify_filesr   catss     r   test_classify_python_as_backendz1TestClassifyFiles.test_classify_python_as_backend5   s(      "7!89$Y777r   c                 B    t         j                  dg      }d|d   v sJ y )Nzcomponents/Button.tsfrontendr%   r'   s     r   test_classify_ts_as_frontendz.TestClassifyFiles.test_classify_ts_as_frontend9   s)      "8!9:%j)9999r   c                 B    t         j                  dg      }d|d   v sJ y )Nzcomponents/Button.tsxr+   r%   r'   s     r   test_classify_tsx_as_frontendz/TestClassifyFiles.test_classify_tsx_as_frontend=   s)      "9!:;&$z*::::r   c                 B    t         j                  dg      }d|d   v sJ y )Nzcomponents/App.jsxr+   r%   r'   s     r   test_classify_jsx_as_frontendz/TestClassifyFiles.test_classify_jsx_as_frontendA   s)      "6!78#tJ'7777r   c                 B    t         j                  dg      }d|d   v sJ y )Nzstyles/main.cssstyler%   r'   s     r   test_classify_css_as_stylez,TestClassifyFiles.test_classify_css_as_styleE   s(      "3!45 DM111r   c                 B    t         j                  dg      }d|d   v sJ y )Nzstyles/theme.scssr2   r%   r'   s     r   test_classify_scss_as_stylez-TestClassifyFiles.test_classify_scss_as_styleI   s(      "5!67"d7m333r   c                 B    t         j                  dg      }d|d   v sJ y )Nztests/test_users.pytestr%   r'   s     r   test_classify_test_prefix_pyz.TestClassifyFiles.test_classify_test_prefix_pyM   s(      "7!89$V444r   c                 B    t         j                  dg      }d|d   v sJ y )Nzcomponents/Button.test.tsr7   r%   r'   s     r   test_classify_dottest_tsz*TestClassifyFiles.test_classify_dottest_tsQ   s(      "=!>?*d6l:::r   c                 V    t         j                  ddg      }d|d   v sJ d|d   v sJ y )Nconfig.jsonz	README.mdotherr%   r'   s     r   test_classify_otherz%TestClassifyFiles.test_classify_otherU   s;      -!=>W---d7m+++r   c                     g d}t         j                  |      }d|d   v sJ d|d   v sJ d|d   v sJ d|d	   v sJ d
|d   v sJ y )N)api.pyApp.tsx
styles.csstests/test_api.pyr<   r@   r$   rA   r+   rB   r2   rC   r7   r<   r=   r%   )r   filesr(   s      r   test_classify_mixed_filesz+TestClassifyFiles.test_classify_mixed_filesZ   sr    W  '4	?***D,,,,tG},,,"d6l222W---r   c                 n    t         j                  g       }t        |j                               h dk(  sJ y )N>   r7   r=   r2   r$   r+   )r   r&   setkeysr'   s     r   test_classify_returns_all_keysz0TestClassifyFiles.test_classify_returns_all_keysc   s,      $499;#TTTTr   N)r   r   r   r)   r,   r.   r0   r3   r5   r8   r:   r>   rE   rI   r    r   r   r"   r"   4   s:    8:;8245;,
.Ur   r"   c                   0    e Zd Zd Zd Zd Zd Zd Zd Zy)TestExtractRoutesc                     |dz  }|j                  t        j                  d             t        j	                  t        |            }d|v sJ y )Nviews.pyz                @app.route('/api/users', methods=['GET'])
                def get_users():
                    pass
                
/api/users
write_texttextwrapdedentr   extract_routes_from_filestrr   tmp_pathfroutess       r   test_flask_route_decoratorz,TestExtractRoutes.test_flask_route_decoratorl   sL    z!	X__ &  	
 ,,SV4v%%%r   c                     |dz  }|j                  t        j                  d             t        j	                  t        |            }d|v sJ y )N	router.pyz                @router.get('/items/{item_id}')
                async def read_item(item_id: int):
                    pass
                z/items/{item_id}rO   rU   s       r   test_fastapi_get_decoratorz,TestExtractRoutes.test_fastapi_get_decoratorv   sL    {"	X__ &  	
 ,,SV4!V+++r   c                 x    |dz  }|j                  d       t        j                  t        |            }d|v sJ y )Nr[   z5@router.post("/users")
async def create_user(): pass
z/usersrP   r   rS   rT   rU   s       r   test_fastapi_post_decoratorz-TestExtractRoutes.test_fastapi_post_decorator   s;    {"	NO,,SV46!!!r   c                     |dz  }|j                  t        j                  d             t        j	                  t        |            }d|v sJ d|v sJ y )NrM   z                @app.route('/api/users')
                def get_users(): pass

                @app.route('/api/products')
                def get_products(): pass
                rN   z/api/productsrO   rU   s       r   test_multiple_routes_in_filez.TestExtractRoutes.test_multiple_routes_in_file   sZ    z!	X__ &  	 ,,SV4v%%%&(((r   c                 z    |dz  }|j                  d       t        j                  t        |            }|g k(  sJ y )Nzcomponent.tsxz1export default function App() { return <div/>; }
r^   rU   s       r   "test_non_python_file_returns_emptyz4TestExtractRoutes.test_non_python_file_returns_empty   s9    &	IJ,,SV4||r   c                 <    t         j                  d      }|g k(  sJ y )Nz/nonexistent/path/file.py)r   rS   )r   rX   s     r   #test_nonexistent_file_returns_emptyz5TestExtractRoutes.test_nonexistent_file_returns_empty   s    ,,-HI||r   N)	r   r   r   rY   r\   r_   ra   rc   re   r    r   r   rK   rK   k   s     &,")r   rK   c                   *    e Zd Zd Zd Zd Zd Zd Zy)TestExtractComponentsc                 x    |dz  }|j                  d       t        j                  t        |            }d|v sJ y )NzUserList.tsxz4export default function UserList() { return null; }
UserListrP   r   extract_components_from_filerT   r   rV   rW   compss       r   test_export_default_functionz2TestExtractComponents.test_export_default_function   s;    ~%	LM//A7U"""r   c                 x    |dz  }|j                  d       t        j                  t        |            }d|v sJ y )Nz
Button.tsxz*export default const Button = () => null;
Buttonrj   rl   s       r   test_export_default_constz/TestExtractComponents.test_export_default_const   s;    |#	BC//A75   r   c                 x    |dz  }|j                  d       t        j                  t        |            }d|v sJ y )NzMyWidget.tsxz+// no explicit export default
const x = 1;
MyWidgetrj   rl   s       r   test_fallback_to_filenamez/TestExtractComponents.test_fallback_to_filename   s;    ~%	DE//A7U"""r   c                 z    |dz  }|j                  d       t        j                  t        |            }|g k(  sJ y )Nzutils.pyzdef helper(): pass
rj   rl   s       r   $test_non_frontend_file_returns_emptyz:TestExtractComponents.test_non_frontend_file_returns_empty   s9    z!	+,//A7{{r   c                 <    t         j                  d      }|g k(  sJ y )Nz/no/such/file.tsx)r   rk   )r   rm   s     r   re   z9TestExtractComponents.test_nonexistent_file_returns_empty   s    //0CD{{r   N)r   r   r   rn   rq   rt   rv   re   r    r   r   rg   rg      s    #!#r   rg   c                       e Zd Zd Zd Zy)TestEmptyDiffc                     t         j                  g       }|d   g k(  sJ |d   g k(  sJ |d   g k(  sJ dD ]  }|d   |   g k(  rJ  y )Nchanged_filesaffected_routesaffected_componentsr$   r+   r2   r7   r=   
categories)r   analyze_changes)r   r   keys      r   -test_empty_changed_files_gives_empty_analysisz;TestEmptyDiff.test_empty_changed_files_gives_empty_analysis   st    ##B'o&",,,'(B...+,222D 	3C,',222	3r   c                 ~    t         j                  g       }t        |d   t              sJ t	        |d         dkD  sJ y )Nsummaryr   )r   r   
isinstancerT   lenr   s     r   test_empty_summary_stringz'TestEmptyDiff.test_empty_summary_string   s?    ##B'&+S1116)$%)))r   N)r   r   r   r   r   r    r   r   ry   ry      s    3*r   ry   c                       e Zd Zd Zd Zy)TestNonGitDirectoryc                     t        j                  t        j                        5  t        j	                  t        |      d       d d d        y # 1 sw Y   y xY wNmainproject_dirbase_ref)pytestraisesr   GitErrorget_changed_filesrT   )r   rV   s     r   "test_get_changed_files_non_git_dirz6TestNonGitDirectory.test_get_changed_files_non_git_dir   sB    ]]2;;' 	M  S]V L	M 	M 	Ms   !AAc                     	 t         j                  t        |      d       y # t         j                  $ r}t        |      sJ Y d }~y d }~ww xY wr   )r   r   rT   r   )r   rV   excs      r   ,test_get_changed_files_returns_error_messagez@TestNonGitDirectory.test_get_changed_files_returns_error_message   s=    	  S]V L{{ 	s8O8	s    # AAAN)r   r   r   r   r   r    r   r   r   r      s    Mr   r   c                       e Zd Zd Zd Zy)TestBaseRefc                 l   t        d      5 }t        ddd      |_        t        j                         5 }t        |d      j                          t        j                  |d       d	d	d	       |j                  d   d   }t        d
 |D              sJ 	 d	d	d	       y	# 1 sw Y   9xY w# 1 sw Y   y	xY w)uQ   get_changed_files 가 올바른 base-ref 로 git 명령을 조합하는지 검증diff_aware_qa.subprocess.runr   zfile.py
r   
returncodestdoutstderr.gitdevelopr   Nc              3   $   K   | ]  }d |v  
 yw)r   Nr    .0parts     r   	<genexpr>zCTestBaseRef.test_custom_base_ref_used_in_command.<locals>.<genexpr>   s     @TyD(@   r   r   return_valuetempfileTemporaryDirectoryr   mkdirr   r   	call_argsanyr   mock_runtmpdir
called_cmds       r   $test_custom_base_ref_used_in_commandz0TestBaseRef.test_custom_base_ref_used_in_command   s    12 	Ah$-;WY$ZH!,,. M&VV$**,$$)$LM
 "++A.q1J@Z@@@@	A 	AM M	A 	As"   (B*2B&.B*B'	#B**B3c                 j   t        d      5 }t        ddd      |_        t        j                         5 }t        |d      j                          t        j                  |       d d d        |j                  d   d   }t        d |D              sJ 	 d d d        y # 1 sw Y   9xY w# 1 sw Y   y xY w)Nr   r   r   r   r   )r   c              3   $   K   | ]  }d |v  
 yw)r   Nr    r   s     r   r   z<TestBaseRef.test_default_base_ref_is_main.<locals>.<genexpr>   s     =$v~=r   r   r   s       r   test_default_base_ref_is_mainz)TestBaseRef.test_default_base_ref_is_main   s    12 	>h$-2b$QH!,,. 9&VV$**,$$$89 "++A.q1J=*====	> 	>9 9	> 	>s"   (B)1B%.B)B&	"B))B2N)r   r   r   r   r   r    r   r   r   r      s    
A>r   r   c                   L    e Zd Zh dZh dZd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zy)TestJsonOutputSchema>   r   r   
qa_targetsr{   r|   r}   >   r7   r=   r2   r$   r+   c                 ~    t         j                  g       }| j                  t        |j	                               k  sJ y )N)r   r   REQUIRED_KEYSrG   rH   r   s     r   +test_analyze_changes_has_all_top_level_keysz@TestJsonOutputSchema.test_analyze_changes_has_all_top_level_keys	  s1    ##B'!!S%7777r   c                     t         j                  g       }| j                  t        |d   j	                               k  sJ y )Nr   )r   r   CATEGORY_KEYSrG   rH   r   s     r   ,test_analyze_changes_categories_has_all_keyszATestJsonOutputSchema.test_analyze_changes_categories_has_all_keys  s9    ##B'!!S)=)B)B)D%EEEEr   c                 Z    t         j                  dg      }t        |d   t              sJ y )Na.pyr{   r   r   r   listr   s     r   test_changed_files_is_listz/TestJsonOutputSchema.test_changed_files_is_list  s)    ##VH-&14888r   c                 X    t         j                  g       }t        |d   t              sJ y )Nr|   r   r   s     r   test_affected_routes_is_listz1TestJsonOutputSchema.test_affected_routes_is_list  s(    ##B'&!23T:::r   c                 X    t         j                  g       }t        |d   t              sJ y )Nr}   r   r   s     r    test_affected_components_is_listz5TestJsonOutputSchema.test_affected_components_is_list  s(    ##B'&!67>>>r   c                 X    t         j                  g       }t        |d   t              sJ y )Nr   r   r   s     r   test_qa_targets_is_listz,TestJsonOutputSchema.test_qa_targets_is_list  s'    ##B'&.555r   c                 X    t         j                  g       }t        |d   t              sJ y )Nr   )r   r   r   rT   r   s     r   test_summary_is_stringz+TestJsonOutputSchema.test_summary_is_string!  s'    ##B'&+S111r   c                     t         j                  ddg      }t        j                  |      }t        j                  |      }|d   |d   k(  sJ y )Nr@   rA   r{   )r   r   jsondumpsloads)r   r   dumpedreloadeds       r   test_json_serializablez+TestJsonOutputSchema.test_json_serializable%  sK    ##Xy$9:F#::f%(F?,CCCCr   N)r   r   r   r   r   r   r   r   r   r   r   r   r   r    r   r   r   r      s;    M FM8F9;?62Dr   r   c                       e Zd Zd Zd Zd Zy)TestBuildSummaryc                 N    ddgg g g g d}t         j                  |      }d|v sJ y )Nr   zb.pyr~   2r   build_summaryr   r(   ss      r   #test_summary_mentions_backend_countz4TestBuildSummary.test_summary_mentions_backend_count0  s4    "F+bRT_abT"axxr   c                 N    g g dg g g d}t         j                  |      }d|v sJ y )N)zA.tsxzB.tsxzC.tsxr~   3r   r   s      r   $test_summary_mentions_frontend_countz5TestBuildSummary.test_summary_mentions_frontend_count5  s1    +FQS]_jlmT"axxr   c                 b    g g g g g d}t         j                  |      }t        |t              sJ y )Nr~   )r   r   r   rT   r   s      r   test_summary_zero_changesz*TestBuildSummary.test_summary_zero_changes:  s3    2BQSTT"!S!!!r   N)r   r   r   r   r   r   r    r   r   r   r   /  s    

"r   r   c                       e Zd Zd Zd Zd Zy)TestAnalyzeChangesIntegrationc                     |dz  }|j                  d       t        j                  t        |      g      }d|d   v sJ y )NrM   z%@app.route('/ping')
def ping(): pass
z/pingr|   rP   r   r   rT   r   rV   rW   r   s       r   "test_backend_files_populate_routesz@TestAnalyzeChangesIntegration.test_backend_files_populate_routesD  sC    z!	>?##SVH-&!23333r   c                     |dz  }|j                  d       t        j                  t        |      g      }d|d   v sJ y )NzCard.tsxz0export default function Card() { return null; }
Cardr}   r   r   s       r   'test_frontend_files_populate_componentszETestAnalyzeChangesIntegration.test_frontend_files_populate_componentsJ  sC    z!	HI##SVH- 56666r   c                     |dz  }|j                  d       t        j                  t        |      g      }t	        |d         dkD  sJ y )Nr@   z)@app.route('/health')
def health(): pass
r   r   )rP   r   r   rT   r   r   s       r   +test_qa_targets_non_empty_for_changed_fileszITestAnalyzeChangesIntegration.test_qa_targets_non_empty_for_changed_filesP  sG    x	BC##SVH-6,'(1,,,r   N)r   r   r   r   r   r   r    r   r   r   r   C  s    47-r   r   )$__doc__importlib.util	importlibr   
subprocesssysr   rQ   pathlibr   unittest.mockr   r   r   __file__parent_SCRIPTS_DIR_MODULE_PATHutilspec_from_file_locationspecloadermodule_from_specr   modulesexec_moduler   r"   rK   rg   ry   r   r   r   r   r   r    r   r   <module>r      s   8    
    *  H~$$++00~~--o|LDKK3 33^^$$T*!O     4 401U 1Un0 0l B* *&	 	> >4+D +Db" "(- -r   