Skip to content

feat: add load testing infrastructure with Locust#164

Open
aanyacloud wants to merge 2 commits into
TENET-DEV-AI:mainfrom
aanyacloud:issue-131-load-testing
Open

feat: add load testing infrastructure with Locust#164
aanyacloud wants to merge 2 commits into
TENET-DEV-AI:mainfrom
aanyacloud:issue-131-load-testing

Conversation

@aanyacloud

@aanyacloud aanyacloud commented Jun 12, 2026

Copy link
Copy Markdown

Summary

Added load testing infrastructure for the TENET AI ingest service using Locust.

Key Changes

  • Added tests/load/locustfile.py for Locust-based load testing
  • Added tests/load/README.md with load testing instructions and scenarios
  • Added Locust dependency to requirements-dev.txt
  • Defined sustained load, spike load, and concurrent connection test scenarios
  • Documented performance metrics and acceptance criteria

Related Issue

Fixes #131


Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • CI/CD Improvement
  • Added Tests

Screenshots / Logs (Optional)

N/A


How Has This Been Tested?

  • Added Locust load-testing configuration

  • Verified Python syntax for locustfile.py

  • Verified project structure and test documentation

  • Unit tests

  • Integration tests

  • Manual testing


Checklist

  • My code follows the project's code style
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Additional Notes (Optional)

This PR introduces the initial load-testing framework for the ingest service. Future performance testing can be executed using Locust to validate latency, throughput, and error-rate targets under different traffic patterns.


Summary by cubic

Add load testing for the ingest service using locust. Includes scripts and docs to run sustained, spike, and concurrent tests against core endpoints to measure latency, throughput, and errors (addresses #131).

  • New Features

    • Added tests/load/locustfile.py with tasks for GET /health, GET /v1/stats, GET /v1/events, POST /v1/events/llm.
    • Added tests/load/README.md with run instructions, scenarios, and metrics (P50/P95/P99, throughput, error rate) plus acceptance criteria (P95 < 500 ms, error rate < 0.1%).
  • Dependencies

    • Added locust>=2.31.0 to requirements-dev.txt.

Written for commit 6a6442e. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Tests

    • Added load testing infrastructure with test scenarios for sustained load, traffic spikes, and concurrent requests.
    • Load tests measure latency, throughput, and error rates against API endpoints.
  • Documentation

    • Added load testing guide with setup instructions, available scenarios, and performance acceptance criteria.
  • Chores

    • Added Locust load testing framework to development dependencies.

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

@aanyacloud is attempting to deploy a commit to the s3dfx-cyber's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR establishes load testing infrastructure by introducing Locust as a development dependency, documenting test scenarios and acceptance criteria, and implementing a TENETLoadUser class with weighted task methods to test health, stats, events, and LLM event endpoints.

Changes

Load Testing Infrastructure Setup

Layer / File(s) Summary
Load testing specification and dependencies
tests/load/README.md, requirements-dev.txt
README documents Locust-based testing setup including tested endpoints, sustained/spike/concurrent load scenarios, collected metrics (latency percentiles, throughput, error rate), and acceptance thresholds. Locust >=2.31.0 added to dev dependencies.
Load test user implementation
tests/load/locustfile.py
TENETLoadUser extends HttpUser with API key constant, configured wait time, default JSON headers, and four weighted tasks: GET health check, GET stats, GET events listing, and POST LLM event submission with hardcoded payload.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • #131: This PR implements the load testing infrastructure requested in issue #131, adding Locust setup, documented test scenarios (sustained, spike, concurrent load), and endpoint testing as specified in the acceptance criteria.

Poem

🐰 A load test hops and bounds,
Testing endpoints all around,
Locust swarms with steady beat,
Making sure the API's fleet! 🏋️

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR addresses most core objectives from issue #131: load testing tool setup (Locust), test scenarios (sustained, spike, concurrent), endpoint coverage, and metrics documentation. However, actual performance testing execution and bottleneck fixes remain incomplete as noted in PR objectives. Clarify whether this PR is the infrastructure setup only (appropriate as-is) or should include actual test execution results and remediation of identified bottlenecks per issue #131 requirements.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: introducing load testing infrastructure using Locust.
Out of Scope Changes check ✅ Passed All changes directly align with issue #131 objectives: Locust setup, test scenarios for critical endpoints, metrics documentation, and infrastructure under tests/load/. No out-of-scope modifications detected.
Description check ✅ Passed The pull request description follows the template with all key sections completed, including summary, key changes, related issue, type of change, testing approach, and checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
tests/load/README.md (2)

14-18: ⚡ Quick win

Clarify installation instructions to align with development workflow.

The installation section instructs users to manually install Locust via pip install locust, but the dependency is already declared in requirements-dev.txt. This could confuse developers about the preferred installation method.

📝 Suggested documentation update
 ## Installation
 
 ```bash
-pip install locust
+pip install -r requirements-dev.txt

+Or if you only need Locust:
+
+bash +pip install locust>=2.31.0 +

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @tests/load/README.md around lines 14 - 18, Update the Installation section
in tests/load/README.md to instruct developers to install dependencies via the
project's dev requirements file instead of a direct pip install of Locust;
replace the single-line instruction pip install locust with guidance to run
pip install -r requirements-dev.txt and add an optional note showing how to
install only Locust (e.g., pip install locust>=2.31.0) for users who don't
want all dev dependencies.


</details>

<!-- cr-comment:v1:1eedec5f3d1221f27478cc59 -->

---

`30-46`: _⚡ Quick win_

**Provide concrete guidance for executing each test scenario.**

The test scenarios describe the goals (sustained load, spike load, concurrent connections) but don't explain how to configure Locust to achieve them. Users need specific parameter values (users, spawn rate, duration) for each scenario.




<details>
<summary>📝 Suggested documentation addition</summary>

```diff
 ## Test Scenarios
 
 ### Sustained Load Test
 
 - Target: 100 requests/second
 - Duration: 10 minutes
+
+**Locust configuration:**
+```bash
+locust -f tests/load/locustfile.py --host=http://localhost:8000 \
+  --users 50 --spawn-rate 10 --run-time 10m --headless
+```
 
 ### Spike Load Test
 
 - Simulate sudden bursts of traffic
 - Increase users rapidly
+
+**Locust configuration:**
+```bash
+locust -f tests/load/locustfile.py --host=http://localhost:8000 \
+  --users 200 --spawn-rate 50 --run-time 5m --headless
+```
 
 ### Concurrent Connection Test
 
 - Run multiple concurrent users
 - Observe latency and error rates
+
+**Locust configuration:**
+```bash
+locust -f tests/load/locustfile.py --host=http://localhost:8000 \
+  --users 100 --spawn-rate 20 --run-time 5m --headless
+```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/README.md` around lines 30 - 46, Add concrete Locust CLI
configurations for each scenario so users can reproduce the tests: update the
Sustained Load, Spike Load, and Concurrent Connection sections to include
example locust command invocations (e.g., the existing locust -f
tests/load/locustfile.py --host=... command) with explicit --users,
--spawn-rate, and --run-time values (suggested: Sustained: --users 50
--spawn-rate 10 --run-time 10m --headless; Spike: --users 200 --spawn-rate 50
--run-time 5m --headless; Concurrent: --users 100 --spawn-rate 20 --run-time 5m
--headless), and add a brief note on what each parameter controls so readers
know how to tune them.
tests/load/locustfile.py (4)

6-7: ⚡ Quick win

Add host attribute to support default target configuration.

The TENETLoadUser class lacks a host attribute, requiring users to always specify --host on the command line. Providing a sensible default improves developer experience while still allowing override.

🔧 Proposed addition
 class TENETLoadUser(HttpUser):
+    host = os.getenv("TENET_HOST", "http://localhost:8000")
     wait_time = between(1, 3)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` around lines 6 - 7, TENETLoadUser lacks a default
target; add a host attribute on the TENETLoadUser class (e.g., host =
"http://localhost:8000" or another sensible default) so locust has a default
target while still allowing CLI --host to override it; update the TENETLoadUser
class definition to include this host attribute (reference: TENETLoadUser,
HttpUser).

14-20: 💤 Low value

Remove unnecessary authentication from health check endpoint.

The health_check task sends the x-api-key header to /health, but health endpoints typically don't require authentication. This adds unnecessary overhead and may not reflect realistic production traffic patterns.

⚡ Optional optimization
     `@task`(3)
     def health_check(self):
-        self.client.get(
-            "/health",
-            headers=self.headers,
-            name="GET /health"
-        )
+        self.client.get("/health", name="GET /health")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` around lines 14 - 20, The health_check task is
sending authentication via self.headers when calling self.client.get("/health")
which is unnecessary; modify the health_check method (the health_check task that
invokes self.client.get) to omit the headers/x-api-key for the "/health" request
so the call uses no authentication headers (remove passing self.headers or pass
headers=None) and keep the request name "GET /health" intact.

38-56: ⚡ Quick win

Vary test payload data to exercise realistic code paths.

The submit_llm_event task always submits identical prompts, which doesn't test how the system handles diverse inputs. From services/ingest/app.py:308-358, the endpoint runs quick_heuristic_check(request.prompt) that returns blocked, risk_score, and verdict—testing with varied prompts ensures realistic load distribution across these code paths.

🎲 Proposed enhancement with varied prompts
+import random
+
+SAMPLE_PROMPTS = [
+    "What is machine learning?",
+    "Explain quantum computing",
+    "How does blockchain work?",
+    "Describe neural networks",
+    "What are the benefits of cloud computing?",
+]
+
 # ...
 
     `@task`(1)
     def submit_llm_event(self):
         payload = {
             "source_type": "chat",
             "source_id": "load-test-user",
             "model": "gpt-4",
-            "prompt": "This is a load testing request",
+            "prompt": random.choice(SAMPLE_PROMPTS),
             "system_prompt": "You are a helpful assistant",
             "metadata": {
                 "environment": "load-test"
             }
         }
 
-        self.client.post(
+        with self.client.post(
             "/v1/events/llm",
             json=payload,
             headers=self.headers,
-            name="POST /v1/events/llm"
-        )
+            name="POST /v1/events/llm",
+            catch_response=True
+        ) as response:
+            if response.status_code not in (200, 201):
+                response.failure(f"Got status code {response.status_code}")
+            elif "event_id" not in response.json():
+                response.failure("Missing expected field: event_id")
+            else:
+                response.success()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` around lines 38 - 56, The submit_llm_event task
always posts the same payload; modify submit_llm_event to generate varied
payloads (randomize prompt text from a small list of benign, risky, and blocked
examples, vary source_id and model occasionally, and tweak metadata) so the
/v1/events/llm endpoint (exercise quick_heuristic_check) sees diverse inputs and
triggers different code paths; update the function submit_llm_event to pick a
prompt at random and adjust other fields per request to spread load across
blocked/risk_score/verdict branches.

30-36: ⚡ Quick win

Test query parameters to validate pagination logic.

The list_events endpoint supports limit and offset query parameters (from services/ingest/app.py:418-455), but the load test always calls it without parameters. Testing pagination validates that the endpoint scales correctly under load.

🔄 Suggested enhancement
+import random
+
 # ... 
 
     `@task`(2)
     def list_events(self):
+        limit = random.choice([10, 50, 100])
+        offset = random.randint(0, 50)
         with self.client.get(
             "/v1/events",
+            params={"limit": limit, "offset": offset},
             headers=self.headers,
             name="GET /v1/events",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` around lines 30 - 36, The load test's list_events
method always calls "/v1/events" with no query params, so it doesn't exercise
pagination; update the list_events task in locustfile.py to send randomized
limit and offset query parameters (e.g., limit in a sensible range like 1-100
and offset across expected dataset size) on each request to simulate paginated
access; construct the request using either a params dict passed to
self.client.get or append a query string to "/v1/events", and update the request
name to reflect the templated query (so metrics show which pagination values
were used). Include a reference comment or tie to services/ingest/app.py
pagination behavior for maintainers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/load/locustfile.py`:
- Around line 22-36: Wrap the GET calls in get_stats and list_events with
client.get(..., catch_response=True) and validate the response inside that
context: check resp.status_code (expect 200) and call resp.success() on valid
responses or resp.failure(...) with an informative message on non-200 or
unexpected body; also catch exceptions around the request and call
resp.failure(...) (or mark failure) so Locust records errors for those
authenticated endpoints (functions get_stats and list_events, and the
self.client.get calls using self.headers).
- Line 3: Replace the hardcoded API_KEY constant in tests/load/locustfile.py
with a value read from an environment variable (e.g., TENET_LOAD_TEST_API_KEY);
update the code that references API_KEY so it raises a clear error or exits if
the env var is missing, and ensure any locust task functions (where API_KEY is
used) continue to reference the same symbol (API_KEY) so no other changes are
required; also add a brief README note or comment showing how to export
TENET_LOAD_TEST_API_KEY before running locust.
- Around line 9-12: The class-level mutable dict headers should be converted to
an instance attribute or property to avoid sharing it across Locust users;
update the Locust user class (where headers is declared) to initialize
self.headers in an on_start method (or implement a `@property` that returns a
fresh dict) using API_KEY and "Content-Type": "application/json", and update any
references from headers to self.headers so each user gets its own dict instance
and concurrent modifications won't affect other users.

In `@tests/load/README.md`:
- Around line 20-28: Update the tests/load/README.md to include required host
and auth setup: instruct users to pass --host (example:
--host=http://localhost:8000) when running locust with tests/load/locustfile.py
and add an "Authentication" section telling them to set the API_KEY constant in
locustfile.py to a valid test API key with the needed permissions (ingest for
POST /v1/events/llm and read for GET /v1/stats and GET /v1/events); reference
locustfile.py and the API_KEY constant so it's clear where to configure these
values.

---

Nitpick comments:
In `@tests/load/locustfile.py`:
- Around line 6-7: TENETLoadUser lacks a default target; add a host attribute on
the TENETLoadUser class (e.g., host = "http://localhost:8000" or another
sensible default) so locust has a default target while still allowing CLI --host
to override it; update the TENETLoadUser class definition to include this host
attribute (reference: TENETLoadUser, HttpUser).
- Around line 14-20: The health_check task is sending authentication via
self.headers when calling self.client.get("/health") which is unnecessary;
modify the health_check method (the health_check task that invokes
self.client.get) to omit the headers/x-api-key for the "/health" request so the
call uses no authentication headers (remove passing self.headers or pass
headers=None) and keep the request name "GET /health" intact.
- Around line 38-56: The submit_llm_event task always posts the same payload;
modify submit_llm_event to generate varied payloads (randomize prompt text from
a small list of benign, risky, and blocked examples, vary source_id and model
occasionally, and tweak metadata) so the /v1/events/llm endpoint (exercise
quick_heuristic_check) sees diverse inputs and triggers different code paths;
update the function submit_llm_event to pick a prompt at random and adjust other
fields per request to spread load across blocked/risk_score/verdict branches.
- Around line 30-36: The load test's list_events method always calls
"/v1/events" with no query params, so it doesn't exercise pagination; update the
list_events task in locustfile.py to send randomized limit and offset query
parameters (e.g., limit in a sensible range like 1-100 and offset across
expected dataset size) on each request to simulate paginated access; construct
the request using either a params dict passed to self.client.get or append a
query string to "/v1/events", and update the request name to reflect the
templated query (so metrics show which pagination values were used). Include a
reference comment or tie to services/ingest/app.py pagination behavior for
maintainers.

In `@tests/load/README.md`:
- Around line 14-18: Update the Installation section in tests/load/README.md to
instruct developers to install dependencies via the project's dev requirements
file instead of a direct pip install of Locust; replace the single-line
instruction `pip install locust` with guidance to run `pip install -r
requirements-dev.txt` and add an optional note showing how to install only
Locust (e.g., `pip install locust>=2.31.0`) for users who don't want all dev
dependencies.
- Around line 30-46: Add concrete Locust CLI configurations for each scenario so
users can reproduce the tests: update the Sustained Load, Spike Load, and
Concurrent Connection sections to include example locust command invocations
(e.g., the existing locust -f tests/load/locustfile.py --host=... command) with
explicit --users, --spawn-rate, and --run-time values (suggested: Sustained:
--users 50 --spawn-rate 10 --run-time 10m --headless; Spike: --users 200
--spawn-rate 50 --run-time 5m --headless; Concurrent: --users 100 --spawn-rate
20 --run-time 5m --headless), and add a brief note on what each parameter
controls so readers know how to tune them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bf3eaf50-a609-4756-95a1-165960ca1801

📥 Commits

Reviewing files that changed from the base of the PR and between 721686f and 57544ee.

📒 Files selected for processing (3)
  • requirements-dev.txt
  • tests/load/README.md
  • tests/load/locustfile.py

Comment thread tests/load/locustfile.py
@@ -0,0 +1,56 @@
from locust import HttpUser, task, between

API_KEY = "test-api-key"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace hardcoded API key with environment variable.

The hardcoded test-api-key constant poses security and configuration risks: it may be committed to version control and cannot be changed per environment without modifying code. From the relevant code snippets (services/ingest/app.py), the API requires valid authenticated keys with specific permissions (ingest for POST /v1/events/llm, read for GET endpoints).

🔐 Proposed fix using environment variable
+import os
 from locust import HttpUser, task, between
 
-API_KEY = "test-api-key"
+API_KEY = os.getenv("TENET_LOAD_TEST_API_KEY", "test-api-key")

Then document in README.md:

export TENET_LOAD_TEST_API_KEY="your-valid-test-key"
locust -f tests/load/locustfile.py --host=http://localhost:8000
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
API_KEY = "test-api-key"
import os
from locust import HttpUser, task, between
API_KEY = os.getenv("TENET_LOAD_TEST_API_KEY", "test-api-key")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` at line 3, Replace the hardcoded API_KEY constant
in tests/load/locustfile.py with a value read from an environment variable
(e.g., TENET_LOAD_TEST_API_KEY); update the code that references API_KEY so it
raises a clear error or exits if the env var is missing, and ensure any locust
task functions (where API_KEY is used) continue to reference the same symbol
(API_KEY) so no other changes are required; also add a brief README note or
comment showing how to export TENET_LOAD_TEST_API_KEY before running locust.

Comment thread tests/load/locustfile.py
Comment on lines +9 to +12
headers = {
"x-api-key": API_KEY,
"Content-Type": "application/json",
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Convert mutable class attribute to property or instance attribute.

Ruff correctly flags that defining headers as a mutable dict at class level means all instances share the same dict object. If any task modifies self.headers, it affects all concurrent users.

🛡️ Recommended fix using on_start
-    headers = {
-        "x-api-key": API_KEY,
-        "Content-Type": "application/json",
-    }
+
+    def on_start(self):
+        """Initialize headers per user instance."""
+        self.headers = {
+            "x-api-key": API_KEY,
+            "Content-Type": "application/json",
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
headers = {
"x-api-key": API_KEY,
"Content-Type": "application/json",
}
def on_start(self):
"""Initialize headers per user instance."""
self.headers = {
"x-api-key": API_KEY,
"Content-Type": "application/json",
}
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 9-12: Mutable default value for class attribute

(RUF012)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` around lines 9 - 12, The class-level mutable dict
headers should be converted to an instance attribute or property to avoid
sharing it across Locust users; update the Locust user class (where headers is
declared) to initialize self.headers in an on_start method (or implement a
`@property` that returns a fresh dict) using API_KEY and "Content-Type":
"application/json", and update any references from headers to self.headers so
each user gets its own dict instance and concurrent modifications won't affect
other users.

Source: Linters/SAST tools

Comment thread tests/load/locustfile.py
Comment on lines +22 to +36
@task(2)
def get_stats(self):
self.client.get(
"/v1/stats",
headers=self.headers,
name="GET /v1/stats"
)

@task(2)
def list_events(self):
self.client.get(
"/v1/events",
headers=self.headers,
name="GET /v1/events"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add response validation and error handling to authenticated endpoints.

The get_stats and list_events tasks don't validate response status codes or catch exceptions. For load testing, tracking success/failure rates is essential to meet the acceptance criteria (error rate < 0.1%).

✅ Proposed enhancement with response validation
     `@task`(2)
     def get_stats(self):
-        self.client.get(
+        with self.client.get(
             "/v1/stats",
             headers=self.headers,
-            name="GET /v1/stats"
-        )
+            name="GET /v1/stats",
+            catch_response=True
+        ) as response:
+            if response.status_code != 200:
+                response.failure(f"Got status code {response.status_code}")
+            elif "total_events" not in response.json():
+                response.failure("Missing expected field: total_events")
+            else:
+                response.success()
 
     `@task`(2)
     def list_events(self):
-        self.client.get(
+        with self.client.get(
             "/v1/events",
             headers=self.headers,
-            name="GET /v1/events"
-        )
+            name="GET /v1/events",
+            catch_response=True
+        ) as response:
+            if response.status_code != 200:
+                response.failure(f"Got status code {response.status_code}")
+            elif "events" not in response.json():
+                response.failure("Missing expected field: events")
+            else:
+                response.success()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/load/locustfile.py` around lines 22 - 36, Wrap the GET calls in
get_stats and list_events with client.get(..., catch_response=True) and validate
the response inside that context: check resp.status_code (expect 200) and call
resp.success() on valid responses or resp.failure(...) with an informative
message on non-200 or unexpected body; also catch exceptions around the request
and call resp.failure(...) (or mark failure) so Locust records errors for those
authenticated endpoints (functions get_stats and list_events, and the
self.client.get calls using self.headers).

Comment thread tests/load/README.md
Comment on lines +20 to +28
## Running Tests

```bash
locust -f tests/load/locustfile.py
```

Then open:

http://localhost:8089

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Document required host configuration and API key setup.

The running instructions omit critical setup details: users must specify the target host URL via --host and ensure a valid API key is configured. Without these, the load tests will fail.

📝 Suggested documentation addition
 ## Running Tests
 
+### Prerequisites
+
+1. Ensure the TENET AI ingest service is running
+2. Configure a valid API key with `ingest` and `read` permissions (see Authentication section below)
+
+### Execute
+
 ```bash
-locust -f tests/load/locustfile.py
+locust -f tests/load/locustfile.py --host=http://localhost:8000

Then open:

http://localhost:8089
+
+### Authentication
+
+Update the API_KEY constant in locustfile.py with a valid test API key that has:
+- ingest permission for POST /v1/events/llm
+- read permission for GET /v1/stats and GET /v1/events

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @tests/load/README.md around lines 20 - 28, Update the tests/load/README.md
to include required host and auth setup: instruct users to pass --host (example:
--host=http://localhost:8000) when running locust with tests/load/locustfile.py
and add an "Authentication" section telling them to set the API_KEY constant in
locustfile.py to a valid test API key with the needed permissions (ingest for
POST /v1/events/llm and read for GET /v1/stats and GET /v1/events); reference
locustfile.py and the API_KEY constant so it's clear where to configure these
values.


</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- cr-comment:v1:fc7fe9e7eef0cf670bb72c46 -->

<!-- This is an auto-generated comment by CodeRabbit -->

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/load/locustfile.py">

<violation number="1" location="tests/load/locustfile.py:3">
P1: Hardcoding `test-api-key` here makes the load test brittle and likely unauthorized under the project’s expected API-key configuration, so the script may only measure 401/403 responses instead of actual ingest performance.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread tests/load/locustfile.py
@@ -0,0 +1,56 @@
from locust import HttpUser, task, between

API_KEY = "test-api-key"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Hardcoding test-api-key here makes the load test brittle and likely unauthorized under the project’s expected API-key configuration, so the script may only measure 401/403 responses instead of actual ingest performance.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/load/locustfile.py, line 3:

<comment>Hardcoding `test-api-key` here makes the load test brittle and likely unauthorized under the project’s expected API-key configuration, so the script may only measure 401/403 responses instead of actual ingest performance.</comment>

<file context>
@@ -0,0 +1,56 @@
+from locust import HttpUser, task, between
+
+API_KEY = "test-api-key"
+
+
</file context>

@S3DFX-CYBER S3DFX-CYBER requested a review from Preetham404 June 12, 2026 15:26
@aanyacloud

Copy link
Copy Markdown
Author

Implemented Issue #131.

CI/test and CodeQL checks are passing.
The TENET Security Review workflow appears to be failing because the TENET_AI_KEY repository secret is not configured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conduct Load and Stress Testing on TENET AI API

2 participants