feat: 31 new tests, metrics LLM counters, Dockerfile caching, Makefile targets, compose limits, code fixes
This commit is contained in:
@@ -230,3 +230,97 @@ async def test_analyze_endpoint_failure(client, sample_finding):
|
||||
assert "failed" in resp.text.lower()
|
||||
|
||||
guarddog_nexus.config.config.llm_enabled = False
|
||||
|
||||
|
||||
# --- GET /analyze polling endpoint ---
|
||||
|
||||
|
||||
class TestAnalyzeStatusEndpoint:
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_finding_not_found(self, client):
|
||||
resp = await client.get("/api/v1/findings/99999/analyze")
|
||||
assert resp.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_returns_report_when_complete(self, client, sample_finding_with_report):
|
||||
import guarddog_nexus.config
|
||||
|
||||
guarddog_nexus.config.config.llm_enabled = True
|
||||
|
||||
resp = await client.get(f"/api/v1/findings/{sample_finding_with_report.id}/analyze")
|
||||
assert resp.status_code == 200
|
||||
assert "safe" in resp.text
|
||||
|
||||
guarddog_nexus.config.config.llm_enabled = False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_returns_spinner_when_no_report(self, client, sample_finding):
|
||||
import guarddog_nexus.config
|
||||
|
||||
guarddog_nexus.config.config.llm_enabled = True
|
||||
|
||||
resp = await client.get(f"/api/v1/findings/{sample_finding.id}/analyze")
|
||||
assert resp.status_code == 200
|
||||
assert "hx-get" in resp.text.lower()
|
||||
|
||||
guarddog_nexus.config.config.llm_enabled = False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_returns_spinner_when_analyzing(self, client, db_session, sample_finding):
|
||||
from sqlalchemy import select
|
||||
|
||||
from guarddog_nexus.db.models import Finding
|
||||
|
||||
finding = await db_session.scalar(select(Finding).where(Finding.id == sample_finding.id))
|
||||
finding.report = {"status": "analyzing"}
|
||||
await db_session.commit()
|
||||
|
||||
resp = await client.get(f"/api/v1/findings/{sample_finding.id}/analyze")
|
||||
assert resp.status_code == 200
|
||||
assert "hx-get" in resp.text.lower()
|
||||
|
||||
|
||||
# --- LLM retry exhaustion ---
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_analyze_finding_exhausts_all_retries():
|
||||
import guarddog_nexus.config
|
||||
from guarddog_nexus.core.llm import analyze_finding
|
||||
|
||||
guarddog_nexus.config.config.llm_api_key = "sk-test"
|
||||
|
||||
with patch("guarddog_nexus.core.llm._attempt_llm_call", return_value=None):
|
||||
with patch("guarddog_nexus.core.llm.asyncio.sleep") as mock_sleep:
|
||||
result = await analyze_finding({"rule": "test-rule"}, max_retries=2)
|
||||
|
||||
assert result is None
|
||||
assert mock_sleep.call_count == 1
|
||||
|
||||
guarddog_nexus.config.config.llm_api_key = ""
|
||||
|
||||
|
||||
# --- LLM lock cleanup ---
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cleanup_llm_locks_removes_unlocked():
|
||||
import asyncio
|
||||
|
||||
from guarddog_nexus.routes.web import _llm_lock, _llm_locks
|
||||
|
||||
async with _llm_lock:
|
||||
_llm_locks[100] = asyncio.Lock()
|
||||
_llm_locks[200] = asyncio.Lock()
|
||||
|
||||
await _llm_locks[100].acquire()
|
||||
|
||||
for key in list(_llm_locks.keys()):
|
||||
if not _llm_locks[key].locked():
|
||||
_llm_locks.pop(key, None)
|
||||
|
||||
assert 100 in _llm_locks
|
||||
assert 200 not in _llm_locks
|
||||
|
||||
_llm_locks[100].release()
|
||||
_llm_locks.clear()
|
||||
|
||||
Reference in New Issue
Block a user