#!/usr/bin/env bash # ============================================================================= # DiDi — Teste de integrare Lot 1 (Platforma AI) <-> Lot 2 (Backend) # ============================================================================= # Demonstreaza ca cele doua loturi colaboreaza pe mediul live, non-distructiv. # # Trei niveluri: # A. Conectivitate & contract — agent-v3 ajunge la fiecare serviciu Lot 1 # B. End-to-end pe tip de continut — pipeline complet -> verdict persistat # C. Proprietati transversale — fail-open, rutare model local, gateway # # Fiecare test capteaza 3 artefacte: (1) request-ul, (2) access-log-ul # serviciului Lot 1 care dovedeste primirea, (3) verdictul persistat in PG. # # Utilizare: bash run-integration-tests.sh # Rezultate: results/results_.json + results/evidence_/ # ============================================================================= set -uo pipefail # --- Config ----------------------------------------------------------------- AGENT="${AGENT_URL:-http://localhost:24803}" KONG="${KONG_URL:-http://127.0.0.1:18000}" KC="${KEYCLOAK_URL:-http://localhost:28080}" USER_ID="${TEST_USER:-14142351-ad1e-466b-ac5d-4a7a0ff562bf}" PG_C="${PG_CONTAINER:-didi-postgres}" POLL_MAX="${POLL_MAX:-60}" # nr. maxim de poll-uri POLL_INT="${POLL_INT:-3}" # secunde intre poll-uri HERE="$(cd "$(dirname "$0")" && pwd)" FIX="$HERE/fixtures" TS="$(date +%Y%m%d_%H%M%S)" OUT="$HERE/results" EVID="$OUT/evidence_$TS" RESULTS="$OUT/results_$TS.json" mkdir -p "$EVID" PASS=0; FAIL=0; DEGR=0 echo "[]" > "$RESULTS" # --- Helpers ---------------------------------------------------------------- pg() { docker exec "$PG_C" psql -U bos_interface -d DIDI -tA -c "$1" 2>/dev/null; } # extrage un camp dintr-un JSON de pe stdin: jget '' ex: "data.status" jget() { python3 -c "import sys,json; try: d=json.load(sys.stdin) except: print(''); sys.exit() for k in '$1'.split('.'): d = d.get(k, '') if isinstance(d, dict) else '' print(d if d is not None else '')"; } # inregistreaza un rezultat de test in JSON-ul agregat rec() { # id level name requirement target status detail evidence_file python3 - "$RESULTS" "$@" <<'PY' import json,sys f=sys.argv[1]; ida,lvl,name,req,tgt,st,detail,ev=sys.argv[2:10] data=json.load(open(f)) data.append({"id":ida,"level":lvl,"name":name,"requirement":req, "target":tgt,"status":st,"detail":detail,"evidence":ev}) json.dump(data,open(f,'w'),ensure_ascii=False,indent=2) PY case "$6" in PASS) PASS=$((PASS+1));; DEGRADED) DEGR=$((DEGR+1));; *) FAIL=$((FAIL+1));; esac printf ' [%-8s] %-4s %s\n' "$6" "$1" "$3" } # probe de conectivitate din INTERIORUL agent-v3 (consumatorul real), via node fetch probe_from_agent() { # url -> printeaza "STATUS " sau "ERR " docker exec didi-agent-v3 node -e " fetch('$1',{signal:AbortSignal.timeout(6000)}) .then(r=>{console.log('STATUS '+r.status)}) .catch(e=>{console.log('ERR '+e.message)})" 2>/dev/null } # lanseaza o analiza async si asteapta verdictul; printeaza JSON-ul result analyze() { # media_type json_body local body="$2" local resp sid st resp="$(curl -sk --max-time 30 -X POST "$AGENT/api/v3/pipeline/analyze-async" \ -H 'Content-Type: application/json' -d "$body" 2>/dev/null)" sid="$(echo "$resp" | jget 'data.session_id')" if [ -z "$sid" ]; then echo "{\"error\":\"dispatch_failed\",\"raw\":$(echo "$resp"|python3 -c 'import sys,json;print(json.dumps(sys.stdin.read()))')}"; return; fi local i=0 while [ $i -lt "$POLL_MAX" ]; do st="$(curl -sk --max-time 10 "$AGENT/api/v3/pipeline/$sid/queue-status" | jget 'data.status')" [ -z "$st" ] && st="$(curl -sk --max-time 10 "$AGENT/api/v3/pipeline/$sid/queue-status" | jget 'status')" if [ "$st" = "completed" ] || [ "$st" = "failed" ]; then break; fi sleep "$POLL_INT"; i=$((i+1)) done curl -sk --max-time 10 "$AGENT/api/v3/pipeline/$sid/result" echo "$sid" > /tmp/.last_sid } # upload un fisier media -> printeaza public_url upload_media() { # filepath curl -sk --max-time 60 -X POST "$AGENT/api/v3/media/upload" \ -F "file=@$1" -F "user_id=$USER_ID" 2>/dev/null | jget 'data.public_url' } echo "============================================================" echo " DiDi — Teste integrare Lot1<->Lot2 $TS" echo " Agent: $AGENT User: ${USER_ID:0:8}... Evidence: $EVID" echo "============================================================" # ============================================================================= # NIVEL A — Conectivitate & contract (agent-v3 -> servicii Lot 1) # ============================================================================= echo; echo "### NIVEL A — Conectivitate din agent-v3 catre serviciile Lot 1" declare -A A_SVC=( [A1]="llm-api:14011|/health|LLM text (Qwen3.5)|extractoare/flux LLM" [A3]="audio-api:54300|/health|Whisper transcriere|extractor Whisper" [A4]="video-api:54600|/health|BusterX deepfake|extractor deepfake" [A5]="extractors:54400|/health|EXIF/NER/YOLO/OCR|extractoare NER/YOLO/OCR" [A6]="forensic:8080|/health|forensic media|analiza forensica media" [A7]="web-api:51100|/health|cautare web claims|modul web-crawl/evidence" [A8]="brain-api:8090|/health|RAG/fact-check cache|flux ML fact-check" ) for id in A1 A3 A4 A5 A6 A7 A8; do IFS='|' read -r hp path label req <<< "${A_SVC[$id]}" out="$(probe_from_agent "http://$hp$path")" echo "$id $hp$path -> $out" >> "$EVID/A_connectivity.log" if echo "$out" | grep -q "STATUS 200"; then rec "$id" "A" "$label ($hp)" "$req" "$hp" "PASS" "$out" "A_connectivity.log" else rec "$id" "A" "$label ($hp)" "$req" "$hp" "FAIL" "$out" "A_connectivity.log" fi done # A2 — LLM vision-capable: modelul qwen3.5 incarcat pe llm-api (rol vision) models="$(docker exec didi-agent-v3 node -e "fetch('http://llm-api:14011/v1/models',{signal:AbortSignal.timeout(6000)}).then(r=>r.json()).then(d=>console.log(JSON.stringify(d))).catch(e=>console.log('ERR'))" 2>/dev/null)" echo "A2 models: $models" >> "$EVID/A_connectivity.log" if echo "$models" | grep -q 'qwen3.5'; then rec "A2" "A" "LLM vision/OCR (llm-api)" "extractor OCR" "llm-api:14011" "PASS" "model qwen3.5 loaded" "A_connectivity.log" else rec "A2" "A" "LLM vision/OCR (llm-api)" "extractor OCR" "llm-api:14011" "FAIL" "$models" "A_connectivity.log" fi # A9 — domain-check T4: apel real POST /api/v1/check/check dc="$(docker exec didi-agent-v3 node -e " fetch('http://domain-check-api:11000/api/v1/check/check',{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({domain:'google.com',check_options:{whois:true,dns:true,ssl:true}}), signal:AbortSignal.timeout(30000)}) .then(r=>r.json()).then(d=>console.log(JSON.stringify({ok:d.success,risk:(d.data||{}).risk_score}))).catch(e=>console.log('ERR '+e.message))" 2>/dev/null)" echo "A9 domain-check: $dc" >> "$EVID/A_connectivity.log" if echo "$dc" | grep -q '"ok":true'; then rec "A9" "A" "Domain-check T4 (WHOIS/DNS/SSL)" "scor credibilitate sursa" "domain-check-api:11000" "PASS" "$dc" "A_connectivity.log" else rec "A9" "A" "Domain-check T4 (WHOIS/DNS/SSL)" "scor credibilitate sursa" "domain-check-api:11000" "FAIL" "$dc" "A_connectivity.log" fi # ============================================================================= # NIVEL B — End-to-end pe tip de continut # ============================================================================= echo; echo "### NIVEL B — Analize end-to-end (pipeline complet -> verdict)" run_e2e() { # id name media_type body req logcontainer logpattern local id="$1" name="$2" mt="$3" body="$4" req="$5" lc="$6" lp="$7" local since res verdict cat score status sid since="$(date -u +%Y-%m-%dT%H:%M:%S)" res="$(analyze "$mt" "$body")" sid="$(cat /tmp/.last_sid 2>/dev/null)" echo "$res" > "$EVID/B_${id}_result.json" status="$(echo "$res" | jget 'data.status')"; [ -z "$status" ] && status="$(echo "$res" | jget 'status')" score="$(echo "$res" | jget 'data.risk_score')"; [ -z "$score" ] && score="$(echo "$res" | jget 'risk_score')" cat="$(echo "$res" | jget 'data.risk_category')"; [ -z "$cat" ] && cat="$(echo "$res" | jget 'risk_category')" # dovada access-log Lot 1 if [ -n "$lc" ]; then docker logs --since "$since" "$lc" 2>&1 | grep -iE "$lp" | tail -5 > "$EVID/B_${id}_lot1_${lc}.log" 2>/dev/null fi # dovada verdict din PG pg "SELECT input_type||'|'||status||'|'||COALESCE(risk_category,'')||'|'||COALESCE(risk_score::text,'') FROM bos_analysis.analysis_session WHERE session_id='$sid';" > "$EVID/B_${id}_pg.txt" 2>/dev/null local pgrow; pgrow="$(cat "$EVID/B_${id}_pg.txt")" if [ "$status" = "completed" ]; then rec "$id" "B" "$name" "$req" "$mt" "PASS" "verdict=$cat score=$score | PG:$pgrow" "B_${id}_result.json" else rec "$id" "B" "$name" "$req" "$mt" "FAIL" "status=$status | PG:$pgrow" "B_${id}_result.json" fi } # B1 — text dezinformare run_e2e "B1" "Text dezinformare -> verdict LLM" "text" \ "{\"media_type\":\"text\",\"text\":\"OMS a confirmat oficial ca vaccinurile anti-COVID contin microcipuri 5G folosite pentru controlul mintal al intregii populatii prin unde radio.\",\"user_id\":\"$USER_ID\"}" \ "orchestrare + flux LLM" "didiAI-llm-api" "chat/completions" # B2 — URL real (domeniu + web) run_e2e "B2" "URL -> componenta domain + web" "url" \ "{\"media_type\":\"url\",\"url\":\"https://www.bbc.com/news\",\"user_id\":\"$USER_ID\"}" \ "web-crawl/evidence + credibilitate sursa" "didiAI-domain-check" "check/check" # B3 — imagine (OCR/vision) IMG_URL="$(upload_media "$FIX/fake_headline.jpg")" echo "B3 image url: $IMG_URL" > "$EVID/B_B3_upload.txt" run_e2e "B3" "Imagine -> OCR/vision + extractoare" "image" \ "{\"media_type\":\"image\",\"media_url\":\"$IMG_URL\",\"user_id\":\"$USER_ID\"}" \ "extractor OCR/vision" "didiAI-llm-api" "chat/completions" # B4 — audio (Whisper) AUD_URL="$(upload_media "$FIX/jfk_speech.wav")" echo "B4 audio url: $AUD_URL" > "$EVID/B_B4_upload.txt" run_e2e "B4" "Audio -> transcriere Whisper -> verdict" "audio" \ "{\"media_type\":\"audio\",\"media_url\":\"$AUD_URL\",\"user_id\":\"$USER_ID\"}" \ "extractor Whisper" "didiAI-audio" "transcriptions|POST" # B5 — video (BusterX) VID_URL="$(upload_media "$FIX/test_clip.mp4")" echo "B5 video url: $VID_URL" > "$EVID/B_B5_upload.txt" run_e2e "B5" "Video -> BusterX deepfake -> verdict" "video" \ "{\"media_type\":\"video\",\"media_url\":\"$VID_URL\",\"user_id\":\"$USER_ID\"}" \ "extractor deepfake" "didiAI-video-api" "POST|analyze|predict" # ============================================================================= # NIVEL C — Proprietati transversale # ============================================================================= echo; echo "### NIVEL C — Fail-open, rutare model local, gateway" # C1 — FAIL-OPEN: opresc web-api, rulez o analiza, verific ca se TERMINA (degradat) echo " [C1] opresc temporar didiAI-web-api pentru testul de fail-open..." docker stop didiAI-web-api >/dev/null 2>&1 sleep 2 since="$(date -u +%Y-%m-%dT%H:%M:%S)" res="$(analyze "text" "{\"media_type\":\"text\",\"text\":\"Presedintele a anuntat ieri o crestere economica de 15% intr-o singura luna, cel mai mare salt din istoria tarii.\",\"user_id\":\"$USER_ID\"}")" echo "$res" > "$EVID/C1_failopen_result.json" c1status="$(echo "$res" | jget 'data.status')"; [ -z "$c1status" ] && c1status="$(echo "$res" | jget 'status')" docker start didiAI-web-api >/dev/null 2>&1 echo " [C1] didiAI-web-api repornit." if [ "$c1status" = "completed" ]; then rec "C1" "C" "Fail-open (web-api oprit -> analiza se termina)" "reziliza/fail-open servicii AI" "didiAI-web-api" "DEGRADED" "analiza completa fara web-api: status=$c1status" "C1_failopen_result.json" else rec "C1" "C" "Fail-open (web-api oprit -> analiza se termina)" "reziliza/fail-open servicii AI" "didiAI-web-api" "FAIL" "status=$c1status (nu a degradat gratios)" "C1_failopen_result.json" fi # C2 — RUTARE MODEL LOCAL: analiza text, dovada apel local qwen3.5, zero OpenRouter since="$(date -u +%Y-%m-%dT%H:%M:%S)" res="$(analyze "text" "{\"media_type\":\"text\",\"text\":\"Guvernul a decis marirea salariului minim incepand cu luna urmatoare, conform anuntului oficial.\",\"user_id\":\"$USER_ID\"}")" sid="$(cat /tmp/.last_sid)" docker logs --since "$since" didiAI-llm-api 2>&1 | grep -iE "chat/completions" | tail -5 > "$EVID/C2_llm_access.log" llmhits="$(wc -l < "$EVID/C2_llm_access.log" 2>/dev/null | tr -d ' ')" usage="$(pg "SELECT COALESCE(llm_usage::text,'{}') FROM bos_analysis.analysis_session WHERE session_id='$sid';")" echo "$usage" > "$EVID/C2_llm_usage.json" # provider din DB pentru modelul primar prov="$(pg "SELECT p.provider_code||'|'||p.base_url FROM bos_parammgmt.llm_provider p JOIN bos_parammgmt.llm_model m ON m.provider_id=p.provider_id WHERE m.model_code='qwen3.5' LIMIT 1;")" echo "provider: $prov" >> "$EVID/C2_llm_usage.json" if [ "${llmhits:-0}" -ge 1 ] && echo "$prov" | grep -q 'llm-api:14011'; then rec "C2" "C" "Rutare model local (Qwen3.5, fara fallback platit)" "flux LLM local" "llm-api:14011" "PASS" "llm-api hits=$llmhits provider=$prov" "C2_llm_access.log" else rec "C2" "C" "Rutare model local (Qwen3.5, fara fallback platit)" "flux LLM local" "llm-api:14011" "FAIL" "llm-api hits=$llmhits provider=$prov" "C2_llm_access.log" fi # C3 — GATEWAY: Kong pazeste lantul AI (401 fara token pe calea pipeline) code_notoken="$(curl -sk --max-time 10 -H 'Host: localhost' -o /dev/null -w '%{http_code}' \ -X POST "$KONG/agent-v3/api/v3/pipeline/analyze-async" -H 'Content-Type: application/json' \ -d '{"media_type":"text","text":"aaaaaaaaaa","user_id":"x"}' 2>/dev/null)" echo "Kong /agent-v3/.../analyze-async fara token -> $code_notoken" > "$EVID/C3_gateway.log" if [ "$code_notoken" = "401" ]; then rec "C3" "C" "Gateway Kong pazeste lantul AI (401 fara JWT)" "API Gateway + securitate" "kong:8000" "PASS" "analyze-async fara token -> $code_notoken" "C3_gateway.log" else rec "C3" "C" "Gateway Kong pazeste lantul AI (401 fara JWT)" "API Gateway + securitate" "kong:8000" "FAIL" "cod neasteptat: $code_notoken" "C3_gateway.log" fi # ============================================================================= # Sumar # ============================================================================= echo; echo "============================================================" echo " SUMAR: PASS=$PASS DEGRADED=$DEGR FAIL=$FAIL" echo " JSON: $RESULTS" echo " Dovezi: $EVID" echo "============================================================" python3 - "$RESULTS" "$PASS" "$DEGR" "$FAIL" "$TS" <<'PY' import json,sys f,p,d,fa,ts=sys.argv[1:6] data=json.load(open(f)) out={"timestamp":ts,"summary":{"pass":int(p),"degraded":int(d),"fail":int(fa),"total":len(data)},"tests":data} json.dump(out,open(f,'w'),ensure_ascii=False,indent=2) print("Scris:",f) PY