Spaces:
Running
Running
| name: HF Spaces Integration Test | |
| on: | |
| push: | |
| branches: [main, develop, "feature/**"] | |
| pull_request: | |
| branches: [main, develop] | |
| workflow_dispatch: | |
| concurrency: | |
| group: hf-integration-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| core-ai-proxy: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Warm up ailixir-api Space | |
| run: | | |
| echo "Waiting for ailixir-api HF Space..." | |
| for i in $(seq 1 60); do | |
| if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then | |
| echo "ailixir-api is up" | |
| sleep 5 | |
| exit 0 | |
| fi | |
| echo "Attempt $i/60..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Warm up ailixir-drug-repurposing Space | |
| run: | | |
| echo "Waiting for ailixir-drug-repurposing HF Space..." | |
| for i in $(seq 1 60); do | |
| if curl -sf https://RottenShadow-ailixir-drug-repurposing.hf.space/health > /dev/null 2>&1; then | |
| echo "ailixir-drug-repurposing is up" | |
| sleep 3 | |
| exit 0 | |
| fi | |
| echo "Attempt $i/60..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Warm up ailixir-admet Space | |
| run: | | |
| echo "Waiting for ailixir-admet HF Space..." | |
| for i in $(seq 1 60); do | |
| if curl -sf https://shdwRow-ailixir-admet.hf.space/health > /dev/null 2>&1; then | |
| echo "ailixir-admet is up" | |
| sleep 3 | |
| exit 0 | |
| fi | |
| echo "Attempt $i/60..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Warm up ailixir-chemical-rag Space | |
| run: | | |
| echo "Waiting for ailixir-chemical-rag HF Space..." | |
| for i in $(seq 1 60); do | |
| if curl -sf https://RottenShadow-ailixir-chemical-rag.hf.space/health > /dev/null 2>&1; then | |
| echo "ailixir-chemical-rag is up" | |
| sleep 3 | |
| exit 0 | |
| fi | |
| echo "Attempt $i/60..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Login and get Bearer token | |
| run: | | |
| echo "POST /api/user/login" | |
| HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}') | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat login.json | |
| echo "" | |
| if [ "$HTTP_CODE" != "200" ]; then | |
| echo "Login failed with HTTP $HTTP_CODE" | |
| exit 1 | |
| fi | |
| python3 - <<'PY' | |
| import json, sys | |
| try: | |
| data = json.load(open("login.json")) | |
| except json.JSONDecodeError as e: | |
| print("Invalid JSON response:", e) | |
| with open("login.json") as f: | |
| print("Raw response:", f.read()) | |
| sys.exit(1) | |
| if not data.get("success"): | |
| print("Login failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| token = data.get("data", {}).get("token") | |
| if not token: | |
| print("No token in response:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| with open("token.txt", "w") as f: | |
| f.write(token) | |
| print("Login OK - token saved") | |
| PY | |
| - name: Test aggregated AI health via Laravel | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "GET /api/ai-services/health" | |
| HTTP_CODE=$(curl -s -o health.json -w "%{http_code}" https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/health \ | |
| -H "Authorization: Bearer $TOKEN") | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat health.json | |
| echo "" | |
| python3 - <<'PY' | |
| import json, sys | |
| data = json.load(open("health.json")) | |
| if not data.get("success"): | |
| print("AI services health check failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| print("All AI services healthy via Laravel proxy") | |
| PY | |
| - name: Test ADMET prediction via Laravel proxy | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/admet/predict" | |
| HTTP_CODE=$(curl -s -o admet.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/admet/predict \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"smiles": "c1ccccc1, CCO, CCC"}') | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat admet.json | |
| echo "" | |
| if [ "$HTTP_CODE" != "200" ]; then | |
| echo "ADMET prediction failed with HTTP $HTTP_CODE" | |
| exit 1 | |
| fi | |
| python3 - <<'PY' | |
| import json, sys | |
| try: | |
| data = json.load(open("admet.json")) | |
| except json.JSONDecodeError as e: | |
| print("Invalid JSON response:", e) | |
| with open("admet.json") as f: | |
| print("Raw response:", f.read()) | |
| sys.exit(1) | |
| if not data.get("success"): | |
| print("ADMET prediction test failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| results = data.get("data", {}).get("results", []) | |
| if len(results) < 3: | |
| print(f"Expected at least 3 results, got {len(results)}") | |
| sys.exit(1) | |
| print(f"ADMET prediction OK - {len(results)} compounds processed") | |
| PY | |
| - name: Test Chemical RAG proxy via Laravel | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/ai-services/test/chemical-search" | |
| HTTP_CODE=$(curl -s -o chemical.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/test/chemical-search \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"query": "malaria drug"}') | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat chemical.json | |
| echo "" | |
| python3 - <<'PY' | |
| import json, sys | |
| data = json.load(open("chemical.json")) | |
| if not data.get("success"): | |
| print("Chemical RAG proxy test failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| print("Chemical RAG proxy OK") | |
| PY | |
| - name: Test Drug Repurposing proxy via Laravel | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "GET /api/ai-services/test/drug-repurposing" | |
| HTTP_CODE=$(curl -s -o drug.json -w "%{http_code}" https://Ailixir-AI-Team-ailixir-api.hf.space/api/ai-services/test/drug-repurposing \ | |
| -H "Authorization: Bearer $TOKEN") | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat drug.json | |
| echo "" | |
| python3 - <<'PY' | |
| import json, sys | |
| data = json.load(open("drug.json")) | |
| if not data.get("success"): | |
| print("Drug Repurposing proxy test failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| print("Drug Repurposing proxy OK") | |
| PY | |
| chemical-search: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Warm up ailixir-api Space | |
| run: | | |
| for i in $(seq 1 60); do | |
| if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then | |
| echo "ailixir-api is up" | |
| sleep 5 | |
| exit 0 | |
| fi | |
| echo "Waiting ($i/60)..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Login and get Bearer token | |
| run: | | |
| HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}') | |
| if [ "$HTTP_CODE" != "200" ]; then exit 1; fi | |
| python3 - <<'PY' | |
| import json | |
| with open('login.json') as f: data = json.load(f) | |
| token = data.get('data', {}).get('token') | |
| if not token: exit(1) | |
| with open('token.txt','w') as f: f.write(token) | |
| print('Login OK') | |
| PY | |
| - name: Test retrieval-only chemical search | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/chemical-search (retrieval-only)" | |
| HTTP_CODE=$(curl -s -o search.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/chemical-search \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"smiles":"CC(=O)O"}') | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat search.json | |
| echo "" | |
| python3 - <<'PY' | |
| import json, sys | |
| data = json.load(open("search.json")) | |
| if not data.get("success"): | |
| print("Chemical search (retrieval-only) failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| if not isinstance(data.get("compounds"), list): | |
| print("Missing 'compounds' array in response") | |
| sys.exit(1) | |
| if data.get("metadata", {}).get("source") != "retrieval": | |
| print("Expected source='retrieval', got:", data.get("metadata", {}).get("source")) | |
| sys.exit(1) | |
| print("Chemical search (retrieval-only) OK - found", len(data["compounds"]), "compounds") | |
| PY | |
| - name: Test full RAG chemical search | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/chemical-search/full-rag" | |
| HTTP_CODE=$(curl -s -o rag.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/chemical-search/full-rag \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"smiles":"CC(=O)O"}') | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat rag.json | |
| echo "" | |
| python3 - <<'PY' | |
| import json, sys | |
| data = json.load(open("rag.json")) | |
| if not data.get("success"): | |
| print("Chemical search (full RAG) failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| if not isinstance(data.get("compounds"), list): | |
| print("Missing 'compounds' array in response") | |
| sys.exit(1) | |
| if data.get("metadata", {}).get("source") != "full_rag": | |
| print("Expected source='full_rag', got:", data.get("metadata", {}).get("source")) | |
| sys.exit(1) | |
| for compound in data["compounds"]: | |
| if not compound.get("explanation"): | |
| print("Missing explanation for compound:", compound.get("name")) | |
| sys.exit(1) | |
| print("Chemical search (full RAG) OK - found", len(data["compounds"]), "compounds with explanations") | |
| PY | |
| drug-repurposing-integration: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Warm up ailixir-api Space | |
| run: | | |
| for i in $(seq 1 60); do | |
| if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then | |
| echo "ailixir-api is up" | |
| sleep 5 | |
| exit 0 | |
| fi | |
| echo "Waiting ($i/60)..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Login and get Bearer token | |
| run: | | |
| HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}') | |
| if [ "$HTTP_CODE" != "200" ]; then exit 1; fi | |
| python3 - <<'PY' | |
| import json | |
| with open('login.json') as f: data = json.load(f) | |
| token = data.get('data', {}).get('token') | |
| if not token: exit(1) | |
| with open('token.txt','w') as f: f.write(token) | |
| print('Login OK') | |
| PY | |
| - name: Test unauthenticated DR request is rejected | |
| run: | | |
| echo "POST /api/drug-repurposing/targets (no auth)" | |
| HTTP_CODE=$(curl -s -o dr_unauth.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"disease_name":"Type 2 Diabetes"}') | |
| if [ "$HTTP_CODE" != "401" ]; then | |
| echo "Expected 401, got $HTTP_CODE" | |
| exit 1 | |
| fi | |
| python3 - <<'PY' | |
| import json | |
| with open('dr_unauth.json') as f: data = json.load(f) | |
| assert not data.get('success', True), 'Expected success=false' | |
| print('Unauthenticated DR request correctly rejected') | |
| PY | |
| - name: Test DR target lookup requires disease_name | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/drug-repurposing/targets (missing disease_name)" | |
| HTTP_CODE=$(curl -s -o dr_validation.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"top_n": 2}') | |
| if [ "$HTTP_CODE" != "422" ]; then | |
| echo "Expected 422, got $HTTP_CODE" | |
| exit 1 | |
| fi | |
| echo "Missing disease_name correctly rejected" | |
| - name: Test DR target lookup via Laravel proxy | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/drug-repurposing/targets" | |
| HTTP_CODE=$(curl -s -o dr_targets.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"disease_name":"Type 2 Diabetes","top_n":2}') | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat dr_targets.json | |
| echo "" | |
| if [ "$HTTP_CODE" != "200" ]; then | |
| echo "DR target lookup failed with HTTP $HTTP_CODE" | |
| exit 1 | |
| fi | |
| python3 - <<'PY' | |
| import json, sys, time, urllib.request | |
| data = json.load(open("dr_targets.json")) | |
| if not data.get("success"): | |
| print("DR target lookup failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| job_id = data.get("data", {}).get("job_id") | |
| status = data.get("data", {}).get("status") | |
| print(f"DR target lookup OK - job_id={job_id}, status={status}") | |
| # Poll for completion with exponential backoff | |
| base = "https://Ailixir-AI-Team-ailixir-api.hf.space/api/drug-repurposing/targets" | |
| with open("token.txt") as f: token = f.read().strip() | |
| max_attempts = 30 | |
| backoff_base = 5 # Start with 5 second delay | |
| retry_count = 0 | |
| max_retries = 3 | |
| for i in range(max_attempts): | |
| try: | |
| req = urllib.request.Request(f"{base}/{job_id}") | |
| req.add_header("Authorization", f"Bearer {token}") | |
| resp = urllib.request.urlopen(req) | |
| status_data = json.loads(resp.read()) | |
| retry_count = 0 # Reset retry count on success | |
| s = status_data.get("data", {}).get("status") | |
| if s == "completed": | |
| output = status_data.get("data", {}).get("output", {}) | |
| assert len(output.get("targets", [])) > 0, "No targets found" | |
| print(f"DR targets completed - {len(output['targets'])} targets found") | |
| sys.exit(0) | |
| elif s == "failed": | |
| print("DR target lookup failed:", json.dumps(status_data, indent=2)) | |
| sys.exit(1) | |
| print(f" Polling... attempt {i+1}/{max_attempts}, status={s}") | |
| time.sleep(backoff_base) | |
| except urllib.error.HTTPError as e: | |
| if e.code == 429 and retry_count < max_retries: | |
| # Rate limited - exponential backoff | |
| retry_count += 1 | |
| wait_time = backoff_base * (2 ** retry_count) | |
| print(f" Rate limited (429). Retry {retry_count}/{max_retries}, waiting {wait_time}s...") | |
| time.sleep(wait_time) | |
| else: | |
| print(f"HTTP Error {e.code}: {e.reason}") | |
| sys.exit(1) | |
| print("DR target lookup did not complete in time") | |
| sys.exit(1) | |
| PY | |
| docking-integration: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Warm up ailixir-api Space | |
| run: | | |
| for i in $(seq 1 60); do | |
| if curl -sf https://Ailixir-AI-Team-ailixir-api.hf.space/ > /dev/null 2>&1; then | |
| echo "ailixir-api is up" | |
| sleep 5 | |
| exit 0 | |
| fi | |
| echo "Waiting ($i/60)..." | |
| sleep 5 | |
| done | |
| exit 1 | |
| - name: Login and get Bearer token | |
| run: | | |
| HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/login \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}') | |
| if [ "$HTTP_CODE" != "200" ]; then exit 1; fi | |
| python3 - <<'PY' | |
| import json | |
| with open('login.json') as f: data = json.load(f) | |
| token = data.get('data', {}).get('token') | |
| if not token: exit(1) | |
| with open('token.txt','w') as f: f.write(token) | |
| print('Login OK') | |
| PY | |
| - name: Test docking submit | |
| run: | | |
| TOKEN=$(cat token.txt) | |
| echo "POST /api/docking/submit" | |
| HTTP_CODE=$(curl -s -o docking.json -w "%{http_code}" -X POST https://Ailixir-AI-Team-ailixir-api.hf.space/api/docking/submit \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -F "protein_name=EGFR" \ | |
| -F "protein_file=@tests/Fixtures/docking/egfr_clean.pdbqt" \ | |
| -F "ligand_name=Erlotinib" \ | |
| -F "ligand_file=@tests/Fixtures/docking/ligand.pdbqt" \ | |
| -F "center_x=10.0" \ | |
| -F "center_y=15.0" \ | |
| -F "center_z=20.0" \ | |
| -F "box_size_x=25.0" \ | |
| -F "box_size_y=25.0" \ | |
| -F "box_size_z=25.0" \ | |
| -F "exhaustiveness=8") | |
| echo "HTTP Status: $HTTP_CODE" | |
| cat docking.json | |
| echo "" | |
| if [ "$HTTP_CODE" != "200" ]; then | |
| echo "Docking submit failed with HTTP $HTTP_CODE" | |
| exit 1 | |
| fi | |
| python3 - <<'PY' | |
| import json, sys | |
| data = json.load(open("docking.json")) | |
| if not data.get("success"): | |
| print("Docking submit failed:", json.dumps(data, indent=2)) | |
| sys.exit(1) | |
| print("Docking submit OK - job created") | |
| PY | |