275 lines
15 KiB
Bash
275 lines
15 KiB
Bash
#!/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_<timestamp>.json + results/evidence_<timestamp>/
|
|
# =============================================================================
|
|
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 '<path python>' 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 <code>" sau "ERR <msg>"
|
|
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
|