55This script tests the CV Analysis Service locally by:
661. Checking service health
772. Creating a sample CV
8- 3. Sending it for analysis
8+ 3. Sending it for analysis (base64 or multipart upload)
994. Validating the response
1010
1111Usage:
12- python test_service.py [--url URL] [--provider PROVIDER]
12+ python test_service.py [--url URL] [--provider PROVIDER] [--method METHOD]
1313
1414Examples:
1515 python test_service.py
1616 python test_service.py --url http://localhost:8000
1717 python test_service.py --provider anthropic
18+ python test_service.py --method multipart
19+ python test_service.py --method both
1820"""
1921
2022import requests
@@ -206,7 +208,7 @@ def test_health(base_url: str) -> bool:
206208
207209def test_analyze (base_url : str , llm_provider : str = "auto" ) -> bool :
208210 """
209- Test the CV analysis endpoint
211+ Test the CV analysis endpoint (Base64 method)
210212
211213 Args:
212214 base_url: Base URL of the service
@@ -215,7 +217,7 @@ def test_analyze(base_url: str, llm_provider: str = "auto") -> bool:
215217 Returns:
216218 True if successful, False otherwise
217219 """
218- print (f"\n { Colors .BOLD } Testing CV Analysis Endpoint{ Colors .END } " )
220+ print (f"\n { Colors .BOLD } Testing CV Analysis Endpoint (Base64) { Colors .END } " )
219221 print (f"{ Colors .BLUE } URL: { base_url } /api/v1/analyze{ Colors .END } " )
220222
221223 # Create sample CV
@@ -339,6 +341,146 @@ def test_analyze(base_url: str, llm_provider: str = "auto") -> bool:
339341 return False
340342
341343
344+ def test_analyze_multipart (base_url : str , llm_provider : str = "auto" ) -> bool :
345+ """
346+ Test the CV analysis endpoint (Multipart upload method)
347+
348+ Args:
349+ base_url: Base URL of the service
350+ llm_provider: LLM provider to use
351+
352+ Returns:
353+ True if successful, False otherwise
354+ """
355+ print (f"\n { Colors .BOLD } Testing CV Analysis Endpoint (Multipart Upload){ Colors .END } " )
356+ print (f"{ Colors .BLUE } URL: { base_url } /api/v1/analyze-upload{ Colors .END } " )
357+
358+ # Create sample CV
359+ print ("Creating sample CV..." )
360+ cv_bytes = create_sample_cv ()
361+ print (f" CV size: { len (cv_bytes )} bytes" )
362+
363+ # Prepare position framework and company criteria as JSON strings
364+ position_framework = {
365+ "role_title" : "Senior Backend Engineer" ,
366+ "key_requirements" : [
367+ "5+ years Python experience" ,
368+ "Microservices architecture expertise" ,
369+ "Database design and optimization" ,
370+ "REST API development" ,
371+ "Cloud infrastructure experience"
372+ ],
373+ "scoring_weights" : {
374+ "technical_skills" : 40 ,
375+ "experience" : 30 ,
376+ "education" : 15 ,
377+ "cultural_fit" : 15
378+ },
379+ "must_have_skills" : ["Python" , "REST API" , "Database Design" ],
380+ "nice_to_have_skills" : ["Docker" , "Kubernetes" , "AWS" ],
381+ "experience_years_required" : 5
382+ }
383+
384+ company_criteria = {
385+ "company_name" : "ACME Corp" ,
386+ "values" : ["Innovation" , "Collaboration" , "Ownership" , "Excellence" ],
387+ "evaluation_guidelines" : "Focus on problem-solving ability, architectural thinking, and team collaboration. Value candidates who demonstrate continuous learning and technical depth." ,
388+ "disqualifiers" : ["Less than 3 years experience" , "No backend development experience" ],
389+ "preferred_backgrounds" : ["Computer Science degree" , "Startup experience" ]
390+ }
391+
392+ # Prepare multipart form data
393+ files = {
394+ 'cv_file' : ('john_doe_cv.pdf' , cv_bytes , 'application/pdf' )
395+ }
396+
397+ data = {
398+ 'position_framework' : json .dumps (position_framework ),
399+ 'company_criteria' : json .dumps (company_criteria ),
400+ 'llm_provider' : llm_provider ,
401+ 'prompt_version' : 'v1' ,
402+ 'analysis_depth' : 'detailed'
403+ }
404+
405+ print (f"Sending multipart analysis request (Provider: { llm_provider } )..." )
406+
407+ try :
408+ response = requests .post (
409+ f"{ base_url } /api/v1/analyze-upload" ,
410+ files = files ,
411+ data = data ,
412+ timeout = 120
413+ )
414+ response .raise_for_status ()
415+
416+ result = response .json ()
417+
418+ print (f"\n { Colors .GREEN } ✓ Analysis completed successfully{ Colors .END } \n " )
419+
420+ # Display results
421+ print (f"{ Colors .BOLD } === ANALYSIS RESULTS ==={ Colors .END } \n " )
422+
423+ print (f"{ Colors .BOLD } Analysis ID:{ Colors .END } { result ['analysis_id' ]} " )
424+ print (f"{ Colors .BOLD } Overall Score:{ Colors .END } { result ['overall_score' ]} /100" )
425+ print (f"{ Colors .BOLD } Recommendation:{ Colors .END } { result ['recommendation' ]} " )
426+
427+ # Section Scores
428+ print (f"\n { Colors .BOLD } Section Scores:{ Colors .END } " )
429+ for section in result ['section_scores' ]:
430+ print (f"\n { Colors .BLUE } { section ['section' ]} { Colors .END } " )
431+ print (f" Score: { section ['score' ]} /100 (Weight: { section ['weight' ]} %)" )
432+ print (f" Weighted: { section ['weighted_score' ]:.2f} " )
433+ print (f" Rationale: { section ['rationale' ][:100 ]} ..." )
434+
435+ # Key Strengths
436+ print (f"\n { Colors .BOLD } Key Strengths:{ Colors .END } " )
437+ for i , strength in enumerate (result ['key_strengths' ], 1 ):
438+ print (f" { i } . { strength } " )
439+
440+ # Critical Gaps
441+ print (f"\n { Colors .BOLD } Critical Gaps:{ Colors .END } " )
442+ if result ['critical_gaps' ]:
443+ for i , gap in enumerate (result ['critical_gaps' ], 1 ):
444+ print (f" { i } . { gap } " )
445+ else :
446+ print (" None identified" )
447+
448+ # Follow-up Questions
449+ print (f"\n { Colors .BOLD } Follow-up Questions:{ Colors .END } " )
450+ for i , question in enumerate (result ['follow_up_questions' ], 1 ):
451+ print (f" { i } . { question } " )
452+
453+ # Metadata
454+ print (f"\n { Colors .BOLD } Analysis Metadata:{ Colors .END } " )
455+ metadata = result ['metadata' ]
456+ print (f" Provider: { metadata ['llm_provider' ]} " )
457+ print (f" Model: { metadata ['model' ]} " )
458+ print (f" Tokens Used: { metadata .get ('tokens_used' , 'N/A' )} " )
459+ print (f" Processing Time: { metadata ['processing_time_ms' ]} ms" )
460+ print (f" CV Pages: { metadata .get ('cv_pages' , 'N/A' )} " )
461+
462+ # Save full response to file
463+ output_file = "test_analysis_result_multipart.json"
464+ with open (output_file , 'w' ) as f :
465+ json .dump (result , f , indent = 2 , default = str )
466+ print (f"\n { Colors .GREEN } Full response saved to: { output_file } { Colors .END } " )
467+
468+ return True
469+
470+ except requests .exceptions .RequestException as e :
471+ print (f"{ Colors .RED } ✗ Analysis failed: { e } { Colors .END } " )
472+ if hasattr (e , 'response' ) and e .response is not None :
473+ try :
474+ error_detail = e .response .json ()
475+ print (f" Error details: { json .dumps (error_detail , indent = 2 )} " )
476+ except :
477+ print (f" Response text: { e .response .text [:500 ]} " )
478+ return False
479+ except Exception as e :
480+ print (f"{ Colors .RED } ✗ Unexpected error: { e } { Colors .END } " )
481+ return False
482+
483+
342484def main ():
343485 """Main test runner"""
344486 parser = argparse .ArgumentParser (
@@ -349,7 +491,9 @@ def main():
349491 python test_service.py
350492 python test_service.py --url http://localhost:8000
351493 python test_service.py --provider anthropic
352- python test_service.py --url http://localhost:8000 --provider openai
494+ python test_service.py --method multipart
495+ python test_service.py --url http://localhost:8000 --provider openai --method base64
496+ python test_service.py --method both
353497 """
354498 )
355499 parser .add_argument (
@@ -363,6 +507,12 @@ def main():
363507 choices = ['auto' , 'openai' , 'anthropic' , 'gemini' ],
364508 help = 'LLM provider to use (default: auto)'
365509 )
510+ parser .add_argument (
511+ '--method' ,
512+ default = 'multipart' ,
513+ choices = ['base64' , 'multipart' , 'both' ],
514+ help = 'Upload method to test (default: multipart, recommended for efficiency)'
515+ )
366516 parser .add_argument (
367517 '--health-only' ,
368518 action = 'store_true' ,
@@ -379,6 +529,7 @@ def main():
379529
380530 print (f"Service URL: { args .url } " )
381531 print (f"LLM Provider: { args .provider } " )
532+ print (f"Upload Method: { args .method } " )
382533
383534 # Test health endpoint
384535 health_ok = test_health (args .url )
@@ -394,21 +545,34 @@ def main():
394545 print (f"\n { Colors .GREEN } Health check only - skipping analysis test{ Colors .END } " )
395546 sys .exit (0 )
396547
397- # Test analysis endpoint
398- analysis_ok = test_analyze (args .url , args .provider )
548+ # Test analysis endpoint(s)
549+ base64_ok = True
550+ multipart_ok = True
551+
552+ if args .method in ['base64' , 'both' ]:
553+ base64_ok = test_analyze (args .url , args .provider )
554+
555+ if args .method in ['multipart' , 'both' ]:
556+ multipart_ok = test_analyze_multipart (args .url , args .provider )
399557
400558 # Summary
401559 print (f"\n { Colors .BOLD } { '=' * 60 } { Colors .END } " )
402560 print (f"{ Colors .BOLD } Test Summary{ Colors .END } " )
403561 print (f"{ '=' * 60 } " )
404562
405563 health_status = f"{ Colors .GREEN } PASS{ Colors .END } " if health_ok else f"{ Colors .RED } FAIL{ Colors .END } "
406- analysis_status = f"{ Colors .GREEN } PASS{ Colors .END } " if analysis_ok else f"{ Colors .RED } FAIL{ Colors .END } "
407-
408564 print (f"Health Check: { health_status } " )
409- print (f"CV Analysis: { analysis_status } " )
410565
411- if health_ok and analysis_ok :
566+ if args .method in ['base64' , 'both' ]:
567+ base64_status = f"{ Colors .GREEN } PASS{ Colors .END } " if base64_ok else f"{ Colors .RED } FAIL{ Colors .END } "
568+ print (f"CV Analysis (Base64): { base64_status } " )
569+
570+ if args .method in ['multipart' , 'both' ]:
571+ multipart_status = f"{ Colors .GREEN } PASS{ Colors .END } " if multipart_ok else f"{ Colors .RED } FAIL{ Colors .END } "
572+ print (f"CV Analysis (Multipart): { multipart_status } " )
573+
574+ all_ok = health_ok and base64_ok and multipart_ok
575+ if all_ok :
412576 print (f"\n { Colors .GREEN } { Colors .BOLD } ✓ All tests passed!{ Colors .END } " )
413577 sys .exit (0 )
414578 else :
0 commit comments