feat(templates): add pagination with total counts to catalog and campaign picker
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Test Catalog's pagination showed only 'Page N' with no way to know how many pages existed, and the campaign 'Add Test' modal's template picker had no pagination at all — with 2800+ templates in the catalog, its flat 100-row fetch made most templates unreachable unless a search happened to match. Adds GET /test-templates/count (open to any authenticated user, mirrors the list endpoint's filters) and wires real 'Page N of M' + total-available counts into both.
This commit is contained in:
@@ -182,11 +182,14 @@ def test_soft_delete():
|
||||
|
||||
|
||||
def test_search_filter():
|
||||
from app.routers.test_templates import list_templates
|
||||
source = inspect.getsource(list_templates)
|
||||
from app.services.test_template_service import _build_template_query, list_templates
|
||||
# The actual filter-building (including search) now lives in the shared
|
||||
# _build_template_query() helper, reused by both list and count.
|
||||
source = inspect.getsource(_build_template_query)
|
||||
assert "search" in source
|
||||
assert "name" in source and "description" in source
|
||||
assert "ilike" in source
|
||||
assert "_build_template_query" in inspect.getsource(list_templates)
|
||||
print(" [PASS] Search filter searches in name and description")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""GET /test-templates/count — total matching the same filters as the list
|
||||
endpoint, so the catalog UI can compute a true page count."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def technique(api, auth_headers):
|
||||
resp = api(
|
||||
"post", "/api/v1/techniques", auth_headers,
|
||||
json={"mitre_id": "T1059.300", "name": "Count Endpoint Technique"},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()["id"]
|
||||
|
||||
|
||||
def _create_template(api, headers, mitre_technique_id, name):
|
||||
resp = api(
|
||||
"post", "/api/v1/test-templates", headers,
|
||||
json={"mitre_technique_id": mitre_technique_id, "name": name},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()["id"]
|
||||
|
||||
|
||||
def test_count_matches_number_of_matching_templates(
|
||||
client, db, api, auth_headers, red_lead_headers, technique,
|
||||
):
|
||||
_create_template(api, red_lead_headers, "T1059.300", "Count test template A")
|
||||
_create_template(api, red_lead_headers, "T1059.300", "Count test template B")
|
||||
|
||||
resp = api(
|
||||
"get", "/api/v1/test-templates/count", auth_headers,
|
||||
params={"mitre_technique_id": "T1059.300"},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["total"] == 2
|
||||
|
||||
|
||||
def test_count_respects_search_filter(client, db, api, auth_headers, red_lead_headers, technique):
|
||||
_create_template(api, red_lead_headers, "T1059.300", "Unique Zebra Template")
|
||||
_create_template(api, red_lead_headers, "T1059.300", "Something else entirely")
|
||||
|
||||
resp = api("get", "/api/v1/test-templates/count", auth_headers, params={"search": "Zebra"})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["total"] == 1
|
||||
|
||||
|
||||
def test_count_matches_list_length_when_under_page_size(
|
||||
client, db, api, auth_headers, red_lead_headers, technique,
|
||||
):
|
||||
_create_template(api, red_lead_headers, "T1059.300", "Parity check template")
|
||||
|
||||
listed = api(
|
||||
"get", "/api/v1/test-templates", auth_headers,
|
||||
params={"mitre_technique_id": "T1059.300"},
|
||||
)
|
||||
counted = api(
|
||||
"get", "/api/v1/test-templates/count", auth_headers,
|
||||
params={"mitre_technique_id": "T1059.300"},
|
||||
)
|
||||
assert len(listed.json()) == counted.json()["total"]
|
||||
|
||||
|
||||
def test_count_accessible_to_any_authenticated_role(client, db, api, red_tech_headers):
|
||||
"""Pagination is a basic UX need, not a lead-only privilege like /stats."""
|
||||
resp = api("get", "/api/v1/test-templates/count", red_tech_headers)
|
||||
assert resp.status_code == 200, resp.text
|
||||
@@ -147,8 +147,11 @@ def test_list_templates_with_filters():
|
||||
assert "search" in source, "List must accept search filter"
|
||||
assert "mitre_technique_id" in source, "List must accept mitre_technique_id filter"
|
||||
|
||||
# Verify ilike is used for search
|
||||
assert "ilike" in source, "Search should use ilike for case-insensitive matching"
|
||||
# Verify ilike is used for search — lives in the shared
|
||||
# _build_template_query() helper now, reused by list and count.
|
||||
from app.services.test_template_service import _build_template_query
|
||||
assert "ilike" in inspect.getsource(_build_template_query), \
|
||||
"Search should use ilike for case-insensitive matching"
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user