ailixir-api / .github /workflows /integration-test.yml
AILIXIR Bot
Auto-sync: 201d269f287526269b8ffded5f450ff36b46c740
907b200
Raw
History Blame Contribute Delete
19.3 kB
name: Docker Integration Test
on:
workflow_dispatch:
concurrency:
group: docker-integration-${{ github.ref }}
cancel-in-progress: true
jobs:
laravel-to-ai-services:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Pull and verify Git LFS files
run: |
git lfs version
git lfs pull
git lfs checkout
git lfs ls-files
python - <<'PY'
from pathlib import Path
paths = [
"ai_apps/generation/bundle/models/affinity/config.pkl",
"ai_apps/generation/bundle/models/affinity/model.pt",
"ai_apps/generation/bundle/models/generator/egfr_generator.chkpt",
"ai_apps/generation/bundle/models/generator/reinvent.prior",
]
for p in paths:
path = Path(p)
if not path.exists():
raise SystemExit(f"Missing required artifact: {p}")
data = path.read_bytes()[:80]
size = path.stat().st_size
print(f"{p}: {size} bytes")
if data.startswith(b"version https://git-lfs.github.com"):
raise SystemExit(f"{p} is still a Git LFS pointer file")
PY
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and start stack
run: |
# Clean up volumes from previous runs
docker compose down -v 2>/dev/null || true
docker builder prune -af || true
docker system df
COMPOSE_PARALLEL_LIMIT=1 docker compose build
docker system df
docker compose up -d --wait
# Run fresh migrations to ensure clean database state
docker compose exec -T laravel php artisan migrate:fresh --force
- name: Seed verified test user
run: |
echo "=== Creating verified test user via PHP script ==="
docker compose exec -T laravel php -r "
require 'vendor/autoload.php';
\$app = require_once 'bootstrap/app.php';
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
\$kernel->bootstrap();
use App\Models\User;
use Illuminate\Support\Facades\Hash;
\$user = User::firstOrCreate(
['email' => 'salehyasmeen080@gmail.com'],
[
'name' => 'Test User',
'password' => Hash::make('123456789'),
'role' => 'normal',
'is_verified' => true,
'email_verified_at' => now(),
'created_at' => now(),
'updated_at' => now()
]
);
echo 'User ID: ' . \$user->id . PHP_EOL;
echo 'Email: ' . \$user->email . PHP_EOL;
echo 'Verified: ' . (\$user->is_verified ? 'YES' : 'NO') . PHP_EOL;
"
- name: Login and get Bearer token
run: |
echo "POST /api/user/login"
HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST http://localhost:8080/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"
docker compose logs laravel
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: Show service status
if: always()
run: docker compose ps -a
- name: Wait for Laravel API
run: |
for i in $(seq 1 60); do
if curl -sf http://localhost:8080/ > /dev/null 2>&1; then
echo "Laravel is up"
exit 0
fi
echo "Waiting for Laravel ($i/60)..."
sleep 5
done
docker compose logs laravel
exit 1
- name: Wait for ADMET Service to be Healthy
run: |
echo "Waiting for ADMET service to be healthy..."
for i in $(seq 1 120); do
if curl -sf http://localhost:8002/health > /dev/null 2>&1; then
echo "ADMET service is healthy and responding on port 8002"
echo "Waiting additional 10 seconds for service to fully initialize..."
sleep 10
exit 0
fi
if docker exec ailixir-admet python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" > /dev/null 2>&1; then
echo "ADMET service is healthy internally inside container"
echo "Waiting additional 10 seconds for service to fully initialize..."
sleep 10
exit 0
fi
echo "Attempt $i/120: ADMET service not ready yet..."
sleep 5
done
echo "ADMET service failed to become healthy in time"
docker compose logs admet
exit 1
- name: Test aggregated AI health via Laravel
run: |
TOKEN=$(cat token.txt)
echo "GET /api/ai-services/health"
curl -sf http://localhost:8080/api/ai-services/health \
-H "Authorization: Bearer $TOKEN" | tee health.json
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")
PY
- name: Test ADMET prediction via
run: |
TOKEN=$(cat token.txt)
echo "POST /api/admet/predict"
HTTP_CODE=$(curl -s -o admet.json -w "%{http_code}" -X POST http://localhost:8080/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 request failed with HTTP $HTTP_CODE"
docker compose logs laravel
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 http://localhost:8080/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}" http://localhost:8080/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
- name: Dump logs on failure
if: failure()
run: |
docker compose logs --no-color
- name: Tear down
if: always()
run: docker compose down -v
drug-repurposing-integration-test:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and start stack
run: |
docker compose down -v 2>/dev/null || true
docker builder prune -af || true
docker system df
COMPOSE_PARALLEL_LIMIT=1 docker compose build
docker system df
docker compose up -d --wait
- name: Verify drug-repurposing reachable from Laravel container
run: |
echo "=== Laravel → drug-repurposing DNS ==="
docker compose exec -T laravel getent hosts drug-repurposing
echo "=== Health check from Laravel container ==="
docker compose exec -T laravel curl -sf http://drug-repurposing:8000/health | tee /tmp/drug-repurposing-health.json
python3 - <<'PY'
import json, sys
data = json.load(open("/tmp/drug-repurposing-health.json"))
if data.get("status") != "healthy":
print("Expected healthy status:", data)
sys.exit(1)
print("drug-repurposing reachable from Laravel:", data.get("service"))
PY
echo "=== DRUG_REPURPOSING_URL in Laravel env ==="
docker compose exec -T laravel php -r 'echo "DRUG_REPURPOSING_URL=" . getenv("DRUG_REPURPOSING_URL") . PHP_EOL;'
- name: Run drug repurposing integration tests
run: |
# Match phpunit.xml: sync queue + sqlite so jobs run inside the test process
docker compose exec -T \
-e QUEUE_CONNECTION=sync \
laravel php artisan test --filter=DrugRepurposingIntegrationTest
- name: Dump logs on failure
if: failure()
run: |
docker compose logs --no-color laravel drug-repurposing queue
- name: Tear down
if: always()
run: docker compose down -v
docking-integration-test:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and start stack
run: |
docker compose down -v 2>/dev/null || true
docker compose build --build-arg COMPOSER_DEV="" laravel
docker compose up -d laravel
- name: Verify RDKit, Vina, and Open Babel in container
run: |
echo "Verifying python3 import rdkit"
docker compose exec -T laravel python3 -c "import rdkit; print(rdkit.__version__)"
echo "Verifying vina --help"
docker compose exec -T laravel vina --help
echo "Verifying obabel -V"
docker compose exec -T laravel obabel -V
- name: Run Laravel Feature Tests
run: |
docker compose exec -T laravel php artisan test --filter=DockingSubmitTest
- name: Tear down
if: always()
run: docker compose down -v
rag-integration-test:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and start stack
run: |
docker compose down -v 2>/dev/null || true
docker builder prune -af || true
docker system df
COMPOSE_PARALLEL_LIMIT=1 docker compose build
docker system df
docker compose up -d --wait
# Run fresh migrations to ensure clean database state
docker compose exec -T laravel php artisan migrate:fresh --force
- name: Seed verified test user
run: |
echo "=== Creating verified test user via PHP script ==="
docker compose exec -T laravel php -r "
require 'vendor/autoload.php';
\$app = require_once 'bootstrap/app.php';
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
\$kernel->bootstrap();
use App\Models\User;
use Illuminate\Support\Facades\Hash;
\$user = User::firstOrCreate(
['email' => 'salehyasmeen080@gmail.com'],
[
'name' => 'Test User',
'password' => Hash::make('123456789'),
'role' => 'normal',
'is_verified' => true,
'email_verified_at' => now(),
'created_at' => now(),
'updated_at' => now()
]
);
echo 'User ID: ' . \$user->id . PHP_EOL;
echo 'Email: ' . \$user->email . PHP_EOL;
echo 'Verified: ' . (\$user->is_verified ? 'YES' : 'NO') . PHP_EOL;
"
- name: Login and get Bearer token
run: |
echo "POST /api/user/login"
HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST http://localhost:8080/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"
docker compose logs laravel
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: Show service status
if: always()
run: docker compose ps -a
- name: Wait for Laravel API
run: |
for i in $(seq 1 60); do
if curl -sf http://localhost:8080/ > /dev/null 2>&1; then
echo "Laravel is up"
exit 0
fi
echo "Waiting for Laravel ($i/60)..."
sleep 5
done
docker compose logs laravel
exit 1
- name: Test retrieval-only chemical search
run: |
TOKEN=$(cat token.txt)
echo "POST /api/chemical-search (retrieval-only)"
HTTP_CODE=$(curl -s -o chemical_search.json -w "%{http_code}" -X POST http://localhost:8080/api/chemical-search \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"smiles":"CC(=O)O"}')
echo "HTTP Status: $HTTP_CODE"
cat chemical_search.json
echo ""
python3 - <<'PY'
import json, sys
data = json.load(open("chemical_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 chemical_rag.json -w "%{http_code}" -X POST http://localhost:8080/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 chemical_rag.json
echo ""
python3 - <<'PY'
import json, sys
data = json.load(open("chemical_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)
# Verify explanations exist for full RAG
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
- name: Dump logs on failure
if: failure()
run: |
docker compose logs --no-color
- name: Tear down
if: always()
run: docker compose down -v