rubric-load-tester 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. rubric_load_tester-0.2.0/LICENSE +21 -0
  2. rubric_load_tester-0.2.0/PKG-INFO +292 -0
  3. rubric_load_tester-0.2.0/README.md +244 -0
  4. rubric_load_tester-0.2.0/pyproject.toml +44 -0
  5. rubric_load_tester-0.2.0/rubric/__init__.py +0 -0
  6. rubric_load_tester-0.2.0/rubric/auth/__init__.py +8 -0
  7. rubric_load_tester-0.2.0/rubric/auth/interviewer.py +852 -0
  8. rubric_load_tester-0.2.0/rubric/auth/resolver.py +96 -0
  9. rubric_load_tester-0.2.0/rubric/auth/store.py +113 -0
  10. rubric_load_tester-0.2.0/rubric/cli/__init__.py +1 -0
  11. rubric_load_tester-0.2.0/rubric/cli/commands/forecast.py +228 -0
  12. rubric_load_tester-0.2.0/rubric/cli/commands/inspect.py +76 -0
  13. rubric_load_tester-0.2.0/rubric/cli/commands/run.py +529 -0
  14. rubric_load_tester-0.2.0/rubric/cli/commands/settings.py +44 -0
  15. rubric_load_tester-0.2.0/rubric/cli/commands/trace.py +186 -0
  16. rubric_load_tester-0.2.0/rubric/cli/helpers.py +518 -0
  17. rubric_load_tester-0.2.0/rubric/cli/main.py +127 -0
  18. rubric_load_tester-0.2.0/rubric/cli/tracer.py +104 -0
  19. rubric_load_tester-0.2.0/rubric/config/__init__.py +5 -0
  20. rubric_load_tester-0.2.0/rubric/config/loader.py +103 -0
  21. rubric_load_tester-0.2.0/rubric/core/__init__.py +1 -0
  22. rubric_load_tester-0.2.0/rubric/core/assertions.py +152 -0
  23. rubric_load_tester-0.2.0/rubric/core/llm.py +376 -0
  24. rubric_load_tester-0.2.0/rubric/core/schema.py +967 -0
  25. rubric_load_tester-0.2.0/rubric/core/synthesizer.py +490 -0
  26. rubric_load_tester-0.2.0/rubric/engine/__init__.py +1 -0
  27. rubric_load_tester-0.2.0/rubric/engine/aggregator.py +208 -0
  28. rubric_load_tester-0.2.0/rubric/engine/auth_pool.py +106 -0
  29. rubric_load_tester-0.2.0/rubric/engine/journey.py +228 -0
  30. rubric_load_tester-0.2.0/rubric/engine/rate_limit.py +56 -0
  31. rubric_load_tester-0.2.0/rubric/engine/registry.py +124 -0
  32. rubric_load_tester-0.2.0/rubric/engine/runner.py +5 -0
  33. rubric_load_tester-0.2.0/rubric/engine/scheduler.py +1064 -0
  34. rubric_load_tester-0.2.0/rubric/engine/types.py +54 -0
  35. rubric_load_tester-0.2.0/rubric/engine/utils.py +191 -0
  36. rubric_load_tester-0.2.0/rubric/forecast/__init__.py +0 -0
  37. rubric_load_tester-0.2.0/rubric/forecast/analyzer.py +350 -0
  38. rubric_load_tester-0.2.0/rubric/forecast/reporter.py +1354 -0
  39. rubric_load_tester-0.2.0/rubric/report/__init__.py +1 -0
  40. rubric_load_tester-0.2.0/rubric/report/diagnostics.py +97 -0
  41. rubric_load_tester-0.2.0/rubric/report/export.py +664 -0
  42. rubric_load_tester-0.2.0/rubric/report/junit.py +100 -0
  43. rubric_load_tester-0.2.0/rubric/report/metrics.py +522 -0
  44. rubric_load_tester-0.2.0/rubric/report/reporter.py +8 -0
  45. rubric_load_tester-0.2.0/rubric/report/terminal.py +295 -0
  46. rubric_load_tester-0.2.0/rubric/sample_api.py +323 -0
  47. rubric_load_tester-0.2.0/rubric/settings.py +199 -0
  48. rubric_load_tester-0.2.0/rubric_load_tester.egg-info/PKG-INFO +292 -0
  49. rubric_load_tester-0.2.0/rubric_load_tester.egg-info/SOURCES.txt +67 -0
  50. rubric_load_tester-0.2.0/rubric_load_tester.egg-info/dependency_links.txt +1 -0
  51. rubric_load_tester-0.2.0/rubric_load_tester.egg-info/entry_points.txt +2 -0
  52. rubric_load_tester-0.2.0/rubric_load_tester.egg-info/requires.txt +9 -0
  53. rubric_load_tester-0.2.0/rubric_load_tester.egg-info/top_level.txt +1 -0
  54. rubric_load_tester-0.2.0/setup.cfg +4 -0
  55. rubric_load_tester-0.2.0/tests/test_aggregator.py +146 -0
  56. rubric_load_tester-0.2.0/tests/test_assertions_and_junit.py +128 -0
  57. rubric_load_tester-0.2.0/tests/test_auth_resolver.py +84 -0
  58. rubric_load_tester-0.2.0/tests/test_config_loader.py +77 -0
  59. rubric_load_tester-0.2.0/tests/test_error_attribution.py +74 -0
  60. rubric_load_tester-0.2.0/tests/test_journey.py +42 -0
  61. rubric_load_tester-0.2.0/tests/test_oob_classification.py +64 -0
  62. rubric_load_tester-0.2.0/tests/test_registry.py +31 -0
  63. rubric_load_tester-0.2.0/tests/test_reporter.py +60 -0
  64. rubric_load_tester-0.2.0/tests/test_scheduler_phases.py +41 -0
  65. rubric_load_tester-0.2.0/tests/test_schema.py +76 -0
  66. rubric_load_tester-0.2.0/tests/test_schema_constraints.py +246 -0
  67. rubric_load_tester-0.2.0/tests/test_settings.py +13 -0
  68. rubric_load_tester-0.2.0/tests/test_synthesizer.py +40 -0
  69. rubric_load_tester-0.2.0/tests/test_synthesizer_constraints.py +210 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: rubric-load-tester
3
+ Version: 0.2.0
4
+ Summary: Zero-config, LLM-powered API load tester.
5
+ Author: Rubric Team
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/adetu-siyan/rubric_lt
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Developers
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3.11
33
+ Classifier: Topic :: Software Development :: Testing
34
+ Classifier: Topic :: Software Development :: Testing :: Traffic Generation
35
+ Requires-Python: >=3.11
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: click
39
+ Requires-Dist: rich
40
+ Requires-Dist: aiohttp
41
+ Requires-Dist: numpy
42
+ Requires-Dist: groq
43
+ Requires-Dist: python-dotenv
44
+ Requires-Dist: pyyaml
45
+ Requires-Dist: pydantic
46
+ Requires-Dist: prompt_toolkit
47
+ Dynamic: license-file
48
+
49
+ # Rubric
50
+
51
+ Rubric points at your API, reads its OpenAPI schema, and hits it with realistic load — no manual test scripts to write. It uses an LLM to generate the actual request payloads (valid data, edge cases, and garbage inputs), fires them concurrently, and gives you latency percentiles plus AI notes on what broke and why.
52
+
53
+ ---
54
+
55
+ ## How it works
56
+
57
+ 1. **Schema ingestion** — pulls `/openapi.json`, sorts routes into buckets (does it need a body? does it depend on an ID from an earlier request?)
58
+ 2. **Payload synthesis** — the LLM writes ~50 payloads per endpoint across three tiers, generated in parallel so this doesn't become the slow part
59
+ 3. **Load engine** — async workers fire the requests and stream live numbers to your terminal as it runs
60
+ 4. **Report** — latency percentiles, pass/fail per endpoint, and a short AI writeup of likely root causes for anything that failed
61
+
62
+ ### Route buckets
63
+
64
+ | Bucket | Methods | What it means |
65
+ |--------|---------|----------------|
66
+ | A | GET, DELETE | No body — just path/query params |
67
+ | B | POST, PUT, PATCH | Has a request body |
68
+ | C | Any | Depends on a resource ID from an earlier POST in the run |
69
+
70
+ ### Payload tiers
71
+
72
+ | Tier | Count | What it's testing |
73
+ |------|-------|--------------------|
74
+ | `happy_path` | 17 | Normal, valid data — should just return 2xx |
75
+ | `edge_cases` | 17 | Boundaries: empty strings, huge ints, unicode, very long strings |
76
+ | `malformed` | 16 | Wrong types, missing fields, broken structure |
77
+
78
+ ---
79
+
80
+ ## Getting set up
81
+
82
+ ```bash
83
+ cd rubric
84
+ pip install -r requirements.txt
85
+ # or install as a package so you get the `rubric` command:
86
+ pip install -e .
87
+ ```
88
+
89
+ Then copy the env file and fill in your provider:
90
+
91
+ ```bash
92
+ cp .env.example .env
93
+ ```
94
+
95
+ ```env
96
+ LLM_PROVIDER=groq # or openai
97
+
98
+ # Groq — fast, has a free tier, this is what most people use
99
+ GROQ_API_KEY=your_groq_key_here
100
+ GROQ_MODEL=meta-llama/llama-4-scout-17b-16e-instruct
101
+
102
+ # OpenAI, if you'd rather use that
103
+ OPENAI_API_KEY=your_openai_key_here
104
+ OPENAI_MODEL=gpt-4o
105
+ OPENAI_BASE_URL=https://api.openai.com/v1
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Using it
111
+
112
+ The simplest case:
113
+
114
+ ```bash
115
+ rubric run http://localhost:8000
116
+ ```
117
+
118
+ Tuning the run:
119
+
120
+ ```bash
121
+ rubric run http://localhost:8000 \
122
+ --requests 500 \
123
+ --concurrency 50 \
124
+ --tier happy_path \
125
+ --p95-threshold 200 \
126
+ --error-rate 0.01
127
+ ```
128
+
129
+ Run every tier in one go:
130
+
131
+ ```bash
132
+ rubric run http://localhost:8000 --tier all --requests 1000
133
+ ```
134
+
135
+ Just want to see what Rubric found without actually load testing:
136
+
137
+ ```bash
138
+ rubric inspect http://localhost:8000
139
+ ```
140
+
141
+ Skip the AI diagnostics step if you just want raw numbers fast:
142
+
143
+ ```bash
144
+ rubric run http://localhost:8000 --no-diagnostics
145
+ ```
146
+
147
+ Switch providers per run instead of editing `.env`:
148
+
149
+ ```bash
150
+ rubric run http://localhost:8000 --provider groq
151
+ rubric run http://localhost:8000 --provider openai
152
+ ```
153
+
154
+ ### Testing against a self-signed cert (local/dev)
155
+
156
+ Rubric checks the target's TLS certificate before sending login credentials, the same way a browser would — if it can't verify the cert, it won't send anything. That's fine for a real API, but it'll block you if you're testing against `localhost` or a dev server on a self-signed cert. In that case, opt in explicitly:
157
+
158
+ ```bash
159
+ rubric run https://localhost:8443 --allow-self-signed
160
+ ```
161
+
162
+ Only do this against something you control. It turns off the check that stops your credentials and tokens from being sent to an unverified server — don't use it against a real target over a network you don't fully trust.
163
+
164
+ ---
165
+
166
+ ## Flags
167
+
168
+ | Flag | Default | What it does |
169
+ |------|---------|---------------|
170
+ | `--requests` | 100 | Total requests spread across endpoints |
171
+ | `--concurrency` | 20 | Max concurrent connections |
172
+ | `--tier` | `happy_path` | `happy_path`, `edge_cases`, `malformed`, or `all` |
173
+ | `--p95-threshold` | 200 | P95 latency (ms) — above this, endpoint is marked DEGRADED |
174
+ | `--error-rate` | 0.01 | 5xx rate above this marks the endpoint FAILED |
175
+ | `--no-diagnostics` | False | Skip the AI failure writeup |
176
+ | `--save` | True | Write the JSON report to disk |
177
+ | `--provider` | from `.env` | Override the LLM provider for this run |
178
+ | `--allow-self-signed` | False | Skip TLS verification for login/token requests — local/self-signed dev servers only |
179
+
180
+ ---
181
+
182
+ ## What you get back
183
+
184
+ While it's running, you'll see something like this update live:
185
+
186
+ ```
187
+ RUBRIC · 847 requests · 142.3 RPS · 5.9s
188
+ ┌────────────────────────┬──────┬──────┬──────┬──────┬──────┬───────┐
189
+ │ Endpoint │ RPS │ P95 │ 2xx │ 4xx │ 5xx │ Total │
190
+ ├────────────────────────┼──────┼──────┼──────┼──────┼──────┼───────┤
191
+ │ POST /users │ 142 │ 84ms│ 891 │ 23 │ 6 │ 920 │
192
+ │ GET /users/{user_id} │ 89 │ 31ms│ 412 │ 0 │ 1 │ 413 │
193
+ │ POST /products │ 201 │ 19ms│ 1204 │ 11 │ 0 │ 1215 │
194
+ └────────────────────────┴──────┴──────┴──────┴──────┴──────┴───────┘
195
+ ```
196
+
197
+ And a summary once it's done:
198
+
199
+ ```
200
+ ┌────────────────────────┬────────────┬─────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
201
+ │ Endpoint │ Status │ RPS │ P50 │ P95 │ P99 │ 2xx │ 4xx │ 5xx │ Err% │
202
+ ├────────────────────────┼────────────┼─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
203
+ │ POST /users │ PASSED │ 142 │ 34ms │ 84ms │201ms │ 891 │ 23 │ 6 │0.67% │
204
+ │ GET /users/{user_id} │ DEGRADED │ 89 │ 12ms │312ms │ 67ms │ 412 │ 0 │ 1 │0.24% │
205
+ │ POST /products │ FAILED │ 201 │ 18ms │ 44ms │ 98ms │ 980 │ 11 │ 24 │2.10% │
206
+ └────────────────────────┴────────────┴─────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
207
+
208
+ SUITE FAILED
209
+ Passed: 1 Degraded: 1 Failed: 1
210
+ Total: 2507 requests Duration: 17.6s RPS: 142.4
211
+ Thresholds — P95 < 200ms, error rate < 1%
212
+ ```
213
+
214
+ For anything that failed, you'll get a short explanation of the likely cause, not just a status code:
215
+
216
+ ```
217
+ 1. POST /users
218
+ Payload: {"name": "", "email": "x@y.com", "role": ""}
219
+ Cause: empty 'role' string hits a NOT NULL constraint at the DB layer.
220
+ Fix: add a validator to reject empty strings for that field.
221
+
222
+ 2. POST /products
223
+ Payload: {"price": -1, "name": "Widget", "category": "tools"}
224
+ Cause: negative price trips a CHECK constraint in Postgres.
225
+ Fix: add Field(gt=0) to the price field in the schema.
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Trying it against the sample API
231
+
232
+ There's a small FastAPI app included if you want to try Rubric without pointing it at a real project yet:
233
+
234
+ ```bash
235
+ pip install fastapi uvicorn
236
+ uvicorn sample_api:app --reload
237
+
238
+ # then, in another terminal:
239
+ rubric run http://localhost:8000
240
+ rubric inspect http://localhost:8000
241
+ ```
242
+
243
+ ---
244
+
245
+ ## The JSON report
246
+
247
+ Every run writes `rubric_report_<url>_<timestamp>.json` to disk:
248
+
249
+ ```json
250
+ {
251
+ "suite_result": "SUITE FAILED",
252
+ "thresholds": { "p95_ms": 200, "error_rate_pct": 1.0 },
253
+ "summary": {
254
+ "total_requests": 2507,
255
+ "duration_seconds": 17.6,
256
+ "overall_rps": 142.4,
257
+ "connection_drops": 0,
258
+ "passed": 1,
259
+ "degraded": 1,
260
+ "failed": 1
261
+ },
262
+ "endpoints": [ ... ],
263
+ "failure_diagnostics": [ ... ]
264
+ }
265
+ ```
266
+
267
+ `suite_result` is meant to be read by CI — Rubric exits `1` on a failed suite.
268
+
269
+ ---
270
+
271
+ ## Layout
272
+
273
+ ```
274
+ rubric/
275
+ ├── core/
276
+ │ ├── schema.py # OpenAPI ingestion & route categorization
277
+ │ ├── synthesizer.py # LLM payload synthesis
278
+ │ └── llm.py # Groq / OpenAI client wrapper
279
+ ├── engine/
280
+ │ └── runner.py # Async load engine & live streaming
281
+ ├── report/
282
+ │ └── reporter.py # Reports & AI diagnostics
283
+ ├── cli/
284
+ │ └── main.py # Click entry point
285
+ ├── sample_api.py # Demo FastAPI server
286
+ ├── requirements.txt
287
+ ├── setup.py
288
+ └── .env.example
289
+ ```
290
+
291
+ ---
292
+
@@ -0,0 +1,244 @@
1
+ # Rubric
2
+
3
+ Rubric points at your API, reads its OpenAPI schema, and hits it with realistic load — no manual test scripts to write. It uses an LLM to generate the actual request payloads (valid data, edge cases, and garbage inputs), fires them concurrently, and gives you latency percentiles plus AI notes on what broke and why.
4
+
5
+ ---
6
+
7
+ ## How it works
8
+
9
+ 1. **Schema ingestion** — pulls `/openapi.json`, sorts routes into buckets (does it need a body? does it depend on an ID from an earlier request?)
10
+ 2. **Payload synthesis** — the LLM writes ~50 payloads per endpoint across three tiers, generated in parallel so this doesn't become the slow part
11
+ 3. **Load engine** — async workers fire the requests and stream live numbers to your terminal as it runs
12
+ 4. **Report** — latency percentiles, pass/fail per endpoint, and a short AI writeup of likely root causes for anything that failed
13
+
14
+ ### Route buckets
15
+
16
+ | Bucket | Methods | What it means |
17
+ |--------|---------|----------------|
18
+ | A | GET, DELETE | No body — just path/query params |
19
+ | B | POST, PUT, PATCH | Has a request body |
20
+ | C | Any | Depends on a resource ID from an earlier POST in the run |
21
+
22
+ ### Payload tiers
23
+
24
+ | Tier | Count | What it's testing |
25
+ |------|-------|--------------------|
26
+ | `happy_path` | 17 | Normal, valid data — should just return 2xx |
27
+ | `edge_cases` | 17 | Boundaries: empty strings, huge ints, unicode, very long strings |
28
+ | `malformed` | 16 | Wrong types, missing fields, broken structure |
29
+
30
+ ---
31
+
32
+ ## Getting set up
33
+
34
+ ```bash
35
+ cd rubric
36
+ pip install -r requirements.txt
37
+ # or install as a package so you get the `rubric` command:
38
+ pip install -e .
39
+ ```
40
+
41
+ Then copy the env file and fill in your provider:
42
+
43
+ ```bash
44
+ cp .env.example .env
45
+ ```
46
+
47
+ ```env
48
+ LLM_PROVIDER=groq # or openai
49
+
50
+ # Groq — fast, has a free tier, this is what most people use
51
+ GROQ_API_KEY=your_groq_key_here
52
+ GROQ_MODEL=meta-llama/llama-4-scout-17b-16e-instruct
53
+
54
+ # OpenAI, if you'd rather use that
55
+ OPENAI_API_KEY=your_openai_key_here
56
+ OPENAI_MODEL=gpt-4o
57
+ OPENAI_BASE_URL=https://api.openai.com/v1
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Using it
63
+
64
+ The simplest case:
65
+
66
+ ```bash
67
+ rubric run http://localhost:8000
68
+ ```
69
+
70
+ Tuning the run:
71
+
72
+ ```bash
73
+ rubric run http://localhost:8000 \
74
+ --requests 500 \
75
+ --concurrency 50 \
76
+ --tier happy_path \
77
+ --p95-threshold 200 \
78
+ --error-rate 0.01
79
+ ```
80
+
81
+ Run every tier in one go:
82
+
83
+ ```bash
84
+ rubric run http://localhost:8000 --tier all --requests 1000
85
+ ```
86
+
87
+ Just want to see what Rubric found without actually load testing:
88
+
89
+ ```bash
90
+ rubric inspect http://localhost:8000
91
+ ```
92
+
93
+ Skip the AI diagnostics step if you just want raw numbers fast:
94
+
95
+ ```bash
96
+ rubric run http://localhost:8000 --no-diagnostics
97
+ ```
98
+
99
+ Switch providers per run instead of editing `.env`:
100
+
101
+ ```bash
102
+ rubric run http://localhost:8000 --provider groq
103
+ rubric run http://localhost:8000 --provider openai
104
+ ```
105
+
106
+ ### Testing against a self-signed cert (local/dev)
107
+
108
+ Rubric checks the target's TLS certificate before sending login credentials, the same way a browser would — if it can't verify the cert, it won't send anything. That's fine for a real API, but it'll block you if you're testing against `localhost` or a dev server on a self-signed cert. In that case, opt in explicitly:
109
+
110
+ ```bash
111
+ rubric run https://localhost:8443 --allow-self-signed
112
+ ```
113
+
114
+ Only do this against something you control. It turns off the check that stops your credentials and tokens from being sent to an unverified server — don't use it against a real target over a network you don't fully trust.
115
+
116
+ ---
117
+
118
+ ## Flags
119
+
120
+ | Flag | Default | What it does |
121
+ |------|---------|---------------|
122
+ | `--requests` | 100 | Total requests spread across endpoints |
123
+ | `--concurrency` | 20 | Max concurrent connections |
124
+ | `--tier` | `happy_path` | `happy_path`, `edge_cases`, `malformed`, or `all` |
125
+ | `--p95-threshold` | 200 | P95 latency (ms) — above this, endpoint is marked DEGRADED |
126
+ | `--error-rate` | 0.01 | 5xx rate above this marks the endpoint FAILED |
127
+ | `--no-diagnostics` | False | Skip the AI failure writeup |
128
+ | `--save` | True | Write the JSON report to disk |
129
+ | `--provider` | from `.env` | Override the LLM provider for this run |
130
+ | `--allow-self-signed` | False | Skip TLS verification for login/token requests — local/self-signed dev servers only |
131
+
132
+ ---
133
+
134
+ ## What you get back
135
+
136
+ While it's running, you'll see something like this update live:
137
+
138
+ ```
139
+ RUBRIC · 847 requests · 142.3 RPS · 5.9s
140
+ ┌────────────────────────┬──────┬──────┬──────┬──────┬──────┬───────┐
141
+ │ Endpoint │ RPS │ P95 │ 2xx │ 4xx │ 5xx │ Total │
142
+ ├────────────────────────┼──────┼──────┼──────┼──────┼──────┼───────┤
143
+ │ POST /users │ 142 │ 84ms│ 891 │ 23 │ 6 │ 920 │
144
+ │ GET /users/{user_id} │ 89 │ 31ms│ 412 │ 0 │ 1 │ 413 │
145
+ │ POST /products │ 201 │ 19ms│ 1204 │ 11 │ 0 │ 1215 │
146
+ └────────────────────────┴──────┴──────┴──────┴──────┴──────┴───────┘
147
+ ```
148
+
149
+ And a summary once it's done:
150
+
151
+ ```
152
+ ┌────────────────────────┬────────────┬─────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
153
+ │ Endpoint │ Status │ RPS │ P50 │ P95 │ P99 │ 2xx │ 4xx │ 5xx │ Err% │
154
+ ├────────────────────────┼────────────┼─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
155
+ │ POST /users │ PASSED │ 142 │ 34ms │ 84ms │201ms │ 891 │ 23 │ 6 │0.67% │
156
+ │ GET /users/{user_id} │ DEGRADED │ 89 │ 12ms │312ms │ 67ms │ 412 │ 0 │ 1 │0.24% │
157
+ │ POST /products │ FAILED │ 201 │ 18ms │ 44ms │ 98ms │ 980 │ 11 │ 24 │2.10% │
158
+ └────────────────────────┴────────────┴─────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
159
+
160
+ SUITE FAILED
161
+ Passed: 1 Degraded: 1 Failed: 1
162
+ Total: 2507 requests Duration: 17.6s RPS: 142.4
163
+ Thresholds — P95 < 200ms, error rate < 1%
164
+ ```
165
+
166
+ For anything that failed, you'll get a short explanation of the likely cause, not just a status code:
167
+
168
+ ```
169
+ 1. POST /users
170
+ Payload: {"name": "", "email": "x@y.com", "role": ""}
171
+ Cause: empty 'role' string hits a NOT NULL constraint at the DB layer.
172
+ Fix: add a validator to reject empty strings for that field.
173
+
174
+ 2. POST /products
175
+ Payload: {"price": -1, "name": "Widget", "category": "tools"}
176
+ Cause: negative price trips a CHECK constraint in Postgres.
177
+ Fix: add Field(gt=0) to the price field in the schema.
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Trying it against the sample API
183
+
184
+ There's a small FastAPI app included if you want to try Rubric without pointing it at a real project yet:
185
+
186
+ ```bash
187
+ pip install fastapi uvicorn
188
+ uvicorn sample_api:app --reload
189
+
190
+ # then, in another terminal:
191
+ rubric run http://localhost:8000
192
+ rubric inspect http://localhost:8000
193
+ ```
194
+
195
+ ---
196
+
197
+ ## The JSON report
198
+
199
+ Every run writes `rubric_report_<url>_<timestamp>.json` to disk:
200
+
201
+ ```json
202
+ {
203
+ "suite_result": "SUITE FAILED",
204
+ "thresholds": { "p95_ms": 200, "error_rate_pct": 1.0 },
205
+ "summary": {
206
+ "total_requests": 2507,
207
+ "duration_seconds": 17.6,
208
+ "overall_rps": 142.4,
209
+ "connection_drops": 0,
210
+ "passed": 1,
211
+ "degraded": 1,
212
+ "failed": 1
213
+ },
214
+ "endpoints": [ ... ],
215
+ "failure_diagnostics": [ ... ]
216
+ }
217
+ ```
218
+
219
+ `suite_result` is meant to be read by CI — Rubric exits `1` on a failed suite.
220
+
221
+ ---
222
+
223
+ ## Layout
224
+
225
+ ```
226
+ rubric/
227
+ ├── core/
228
+ │ ├── schema.py # OpenAPI ingestion & route categorization
229
+ │ ├── synthesizer.py # LLM payload synthesis
230
+ │ └── llm.py # Groq / OpenAI client wrapper
231
+ ├── engine/
232
+ │ └── runner.py # Async load engine & live streaming
233
+ ├── report/
234
+ │ └── reporter.py # Reports & AI diagnostics
235
+ ├── cli/
236
+ │ └── main.py # Click entry point
237
+ ├── sample_api.py # Demo FastAPI server
238
+ ├── requirements.txt
239
+ ├── setup.py
240
+ └── .env.example
241
+ ```
242
+
243
+ ---
244
+
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rubric-load-tester"
7
+ version = "0.2.0"
8
+ description = "Zero-config, LLM-powered API load tester."
9
+ authors = [
10
+ {name = "Rubric Team"}
11
+ ]
12
+ readme = "README.md"
13
+ license = {file = "LICENSE"}
14
+ requires-python = ">=3.11"
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Topic :: Software Development :: Testing",
21
+ "Topic :: Software Development :: Testing :: Traffic Generation",
22
+ ]
23
+ dependencies = [
24
+ "click",
25
+ "rich",
26
+ "aiohttp",
27
+ "numpy",
28
+ "groq",
29
+ "python-dotenv",
30
+ "pyyaml",
31
+ "pydantic",
32
+ "prompt_toolkit",
33
+ ]
34
+
35
+ [project.scripts]
36
+ rubric = "rubric.cli.main:main"
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/adetu-siyan/rubric_lt"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["."]
43
+ include = ["rubric*"]
44
+ exclude = ["rubric_traces*", "tests*"]
File without changes
@@ -0,0 +1,8 @@
1
+ from .store import load_auth, save_auth, delete_credentials, auth_file_exists
2
+ from .interviewer import run_auth_interview
3
+ from .resolver import resolve_headers
4
+
5
+ __all__ = [
6
+ "load_auth", "save_auth", "delete_credentials", "auth_file_exists",
7
+ "run_auth_interview", "resolve_headers",
8
+ ]