Skip to content

Commit 066de90

Browse files
authored
Merge pull request #6 from tekdi/claude/frappe-hrms-cv-upload-011CV5La1ED2RTd2Ycx4vWcQ
Add multipart upload testing to test script
2 parents 9cbe68f + 926a8ec commit 066de90

File tree

1 file changed

+175
-11
lines changed

1 file changed

+175
-11
lines changed

services/hrms-tools/tests/test_service.py

Lines changed: 175 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55
This script tests the CV Analysis Service locally by:
66
1. Checking service health
77
2. Creating a sample CV
8-
3. Sending it for analysis
8+
3. Sending it for analysis (base64 or multipart upload)
99
4. Validating the response
1010
1111
Usage:
12-
python test_service.py [--url URL] [--provider PROVIDER]
12+
python test_service.py [--url URL] [--provider PROVIDER] [--method METHOD]
1313
1414
Examples:
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

2022
import requests
@@ -206,7 +208,7 @@ def test_health(base_url: str) -> bool:
206208

207209
def 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+
342484
def 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

Comments
 (0)