@@ -367,6 +367,7 @@ def generate_diff(test_id: int, regression_test_id: int, output_id: int, to_view
367367
368368
369369@mod_test .route ('/log-files/<test_id>' )
370+ @login_required
370371def download_build_log_file (test_id ):
371372 """
372373 Serve download of build log.
@@ -464,18 +465,18 @@ def _artifact_redirect(blob_path, filename='artifact'):
464465
465466
466467@mod_test .route ('/<int:test_id>/binary' , methods = ['GET' ])
468+ @login_required
467469def download_binary (test_id ):
468- """Download the ccextractor binary used in a test (linux or windows)."""
469- from run import storage_client_bucket
470- # Try linux name first, then windows
471- for name in ['ccextractor' , 'ccextractor.exe' ]:
472- blob_path = f'test_artifacts/{ test_id } /{ name } '
473- if storage_client_bucket .blob (blob_path ).exists ():
474- return _artifact_redirect (blob_path , filename = name )
475- abort (404 )
470+ """Download the ccextractor binary used in a test."""
471+ test = Test .query .filter (Test .id == test_id ).first ()
472+ if test is None :
473+ abort (404 )
474+ name = 'ccextractor' if test .platform == TestPlatform .linux else 'ccextractor.exe'
475+ return _artifact_redirect (f'test_artifacts/{ test_id } /{ name } ' , filename = name )
476476
477477
478478@mod_test .route ('/<int:test_id>/coredump' , methods = ['GET' ])
479+ @login_required
479480def download_coredump (test_id ):
480481 """Download the coredump from a test, if one was produced."""
481482 return _artifact_redirect (
@@ -485,6 +486,7 @@ def download_coredump(test_id):
485486
486487
487488@mod_test .route ('/<int:test_id>/combined-stdout' , methods = ['GET' ])
489+ @login_required
488490def download_combined_stdout (test_id ):
489491 """Download the combined stdout/stderr log from all test invocations."""
490492 return _artifact_redirect (
@@ -494,6 +496,7 @@ def download_combined_stdout(test_id):
494496
495497
496498@mod_test .route ('/<int:test_id>/regression/<int:regression_test_id>/<int:output_id>/output-got' , methods = ['GET' ])
499+ @login_required
497500def download_output_got (test_id , regression_test_id , output_id ):
498501 """Download the actual output file from TestResults using DB hash."""
499502 rf = TestResultFile .query .filter (and_ (
@@ -511,6 +514,7 @@ def download_output_got(test_id, regression_test_id, output_id):
511514
512515
513516@mod_test .route ('/<int:test_id>/regression/<int:regression_test_id>/<int:output_id>/output-expected' , methods = ['GET' ])
517+ @login_required
514518def download_output_expected (test_id , regression_test_id , output_id ):
515519 """Download the expected output file from TestResults using DB hash."""
516520 rf = TestResultFile .query .filter (and_ (
@@ -526,8 +530,9 @@ def download_output_expected(test_id, regression_test_id, output_id):
526530 filename = f'output_expected_{ regression_test_id } _{ output_id } { ext } '
527531 )
528532@mod_test .route ('/<int:test_id>/sample/<int:sample_id>' , methods = ['GET' ])
533+ @login_required
529534def download_sample_ai (test_id , sample_id ):
530- """Download the sample file for a regression test (no auth required for AI workflow) ."""
535+ """Download the sample file for a regression test."""
531536 from mod_sample .models import Sample
532537 sample = Sample .query .filter (Sample .id == sample_id ).first ()
533538 if sample is None :
@@ -538,75 +543,69 @@ def download_sample_ai(test_id, sample_id):
538543 )
539544
540545
541- def _process_test_case (test_id , category_name , t_data ):
542- """Helper function to process a single test case."""
546+ def _build_output_entry (test_id , rt , expected_output , result_files ):
547+ """Build a single output entry dict for the ai.json response."""
548+ matched_rf = next (
549+ (rf for rf in result_files
550+ if rf .test_id != - 1 and rf .regression_test_output_id == expected_output .id ),
551+ None
552+ )
553+
554+ got_url = None
555+ diff_url = None
556+
557+ if matched_rf and matched_rf .got is not None :
558+ got_url = url_for (
559+ '.download_output_got' ,
560+ test_id = test_id ,
561+ regression_test_id = rt .id ,
562+ output_id = expected_output .id ,
563+ _external = True
564+ )
565+ diff_url = url_for (
566+ '.generate_diff' ,
567+ test_id = test_id ,
568+ regression_test_id = rt .id ,
569+ output_id = expected_output .id ,
570+ to_view = 0 ,
571+ _external = True
572+ )
573+
574+ return {
575+ 'output_id' : expected_output .id ,
576+ 'correct_extension' : expected_output .correct_extension ,
577+ 'expected_url' : url_for (
578+ '.download_output_expected' ,
579+ test_id = test_id ,
580+ regression_test_id = rt .id ,
581+ output_id = expected_output .id ,
582+ _external = True
583+ ),
584+ 'got_url' : got_url ,
585+ 'diff_url' : diff_url ,
586+ }
587+
588+
589+ def _process_test_case (test , category_name , t_data ):
590+ """Build a structured dict for a single test case in the ai.json response."""
543591 rt = t_data ['test' ]
544592 result = t_data ['result' ]
545593 is_error = t_data .get ('error' , False )
546594 result_files = t_data ['files' ]
547595
548- outputs = []
549- for expected_output in rt .output_files :
550- if expected_output .ignore :
551- continue
552-
553- matched_rf = None
554- for rf in result_files :
555- if rf .test_id != - 1 and rf .regression_test_output_id == expected_output .id :
556- matched_rf = rf
557- break
558-
559- got_url = None
560- diff_url = None
561-
562- if matched_rf and matched_rf .got is not None :
563- got_url = url_for (
564- '.download_output_got' ,
565- test_id = test_id ,
566- regression_test_id = rt .id ,
567- output_id = expected_output .id ,
568- _external = True
569- )
570- diff_url = url_for (
571- '.generate_diff' ,
572- test_id = test_id ,
573- regression_test_id = rt .id ,
574- output_id = expected_output .id ,
575- to_view = 0 ,
576- _external = True
577- )
578- else :
579- # If test passed, got and expected match exactly.
580- got_url = url_for (
581- '.download_output_expected' ,
582- test_id = test_id ,
583- regression_test_id = rt .id ,
584- output_id = expected_output .id ,
585- _external = True
586- )
587-
588- output_entry = {
589- 'output_id' : expected_output .id ,
590- 'correct_extension' : expected_output .correct_extension ,
591- 'expected_url' : url_for (
592- '.download_output_expected' ,
593- test_id = test_id ,
594- regression_test_id = rt .id ,
595- output_id = expected_output .id ,
596- _external = True
597- ),
598- 'got_url' : got_url ,
599- 'diff_url' : diff_url ,
600- }
601- outputs .append (output_entry )
596+ outputs = [
597+ _build_output_entry (test .id , rt , expected_output , result_files )
598+ for expected_output in rt .output_files
599+ if not expected_output .ignore
600+ ]
602601
603- return {
602+ response_dict = {
604603 'regression_test_id' : rt .id ,
605604 'category' : category_name ,
606605 'sample_filename' : rt .sample .original_name ,
607606 'sample_url' : url_for (
608607 '.download_sample_ai' ,
609- test_id = test_id ,
608+ test_id = test . id ,
610609 sample_id = rt .sample .id ,
611610 _external = True
612611 ),
@@ -616,11 +615,17 @@ def _process_test_case(test_id, category_name, t_data):
616615 'expected_exit_code' : result .expected_rc if result else None ,
617616 'runtime_ms' : result .runtime if result else None ,
618617 'outputs' : outputs ,
619- 'how_to_reproduce' : f'./ccextractor { rt .command } { rt .sample .original_name } ' ,
620618 }
621619
620+ # Format the reproduction command based on platform
621+ binary_name = './ccextractor' if test .platform == TestPlatform .linux else 'ccextractor.exe'
622+ response_dict ['how_to_reproduce' ] = f'{ binary_name } { rt .command } { rt .sample .original_name } '
623+
624+ return response_dict
625+
622626
623627@mod_test .route ('/<int:test_id>/ai.json' , methods = ['GET' ])
628+ @login_required
624629def ai_json_endpoint (test_id ):
625630 """Structured JSON with download URLs for all artifacts — for AI agents."""
626631 from run import storage_client_bucket
@@ -632,10 +637,8 @@ def ai_json_endpoint(test_id):
632637 def blob_exists (path ):
633638 return storage_client_bucket .blob (path ).exists ()
634639
635- has_binary = (
636- blob_exists (f'test_artifacts/{ test_id } /ccextractor' ) or
637- blob_exists (f'test_artifacts/{ test_id } /ccextractor.exe' )
638- )
640+ binary_name = 'ccextractor' if test .platform == TestPlatform .linux else 'ccextractor.exe'
641+ has_binary = blob_exists (f'test_artifacts/{ test_id } /{ binary_name } ' )
639642 has_coredump = blob_exists (f'test_artifacts/{ test_id } /coredump' )
640643 has_combined_stdout = blob_exists (f'test_artifacts/{ test_id } /combined_stdout.log' )
641644
@@ -653,7 +656,7 @@ def blob_exists(path):
653656 else :
654657 passed += 1
655658
656- test_cases .append (_process_test_case (test_id , category ['category' ].name , t_data ))
659+ test_cases .append (_process_test_case (test , category ['category' ].name , t_data ))
657660
658661 report = {
659662 'test_id' : test .id ,
0 commit comments