|
| 1 | +# Production Testing Observations |
| 2 | + |
| 3 | +**Date**: December 27, 2025 |
| 4 | +**Production URL**: http://157.245.108.179:8080 |
| 5 | +**Tester**: Claude (following Getting Started guide) |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Executive Summary |
| 10 | + |
| 11 | +**Production is NOT functional for the core use case.** |
| 12 | + |
| 13 | +The primary workflow (register → compile → execute) is completely blocked because `POST /functions` returns 500. Read-only operations work, but all write operations and error handling have issues. |
| 14 | + |
| 15 | +### Priority Bugs |
| 16 | + |
| 17 | +| Priority | Bug | Impact | |
| 18 | +|----------|-----|--------| |
| 19 | +| **P0** | POST /functions returns 500 | Core functionality broken | |
| 20 | +| **P1** | Framework exceptions return 500 | Poor DX, hard to debug | |
| 21 | +| **P2** | Invalid UUID returns 500 | Should be 400 | |
| 22 | +| **P3** | Invalid JSON/empty body returns 500 | Should be 400 | |
| 23 | + |
| 24 | +### Immediate Actions Required |
| 25 | + |
| 26 | +1. **SSH into production** and check: |
| 27 | + - API logs: `podman logs projectnil-api` |
| 28 | + - PGMQ queues exist: `SELECT pgmq.list_queues();` |
| 29 | + - Migrations ran: `podman logs projectnil-migrations` |
| 30 | + |
| 31 | +2. **Fix GlobalExceptionHandler** to properly handle: |
| 32 | + - `NoResourceFoundException` → 404 |
| 33 | + - `HttpRequestMethodNotSupportedException` → 405 |
| 34 | + - `MethodArgumentTypeMismatchException` → 400 |
| 35 | + - `HttpMessageNotReadableException` → 400 |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Test Summary |
| 40 | + |
| 41 | +| Test | Expected | Actual | Status | |
| 42 | +|------|----------|--------|--------| |
| 43 | +| Health check | 200 UP | 200 UP | PASS | |
| 44 | +| List functions | 200 [] | 200 [] | PASS | |
| 45 | +| Get non-existent function (valid UUID) | 404 | 404 | PASS | |
| 46 | +| Get non-existent execution (valid UUID) | 404 | 404 | PASS | |
| 47 | +| Unsupported language validation | 415 | 415 | PASS | |
| 48 | +| **Register function** | **201** | **500** | **FAIL** | |
| 49 | +| Get function (invalid UUID) | 400 | 500 | FAIL | |
| 50 | +| Non-existent endpoint | 404 | 500 | FAIL | |
| 51 | +| Wrong HTTP method | 405 | 500 | FAIL | |
| 52 | +| Empty request body | 400 | 500 | FAIL | |
| 53 | +| Invalid JSON | 400 | 500 | FAIL | |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## Bug #1: POST /functions returns 500 Internal Server Error |
| 58 | + |
| 59 | +### Steps to Reproduce |
| 60 | + |
| 61 | +```bash |
| 62 | +export API_URL="http://157.245.108.179:8080" |
| 63 | + |
| 64 | +curl -X POST $API_URL/functions \ |
| 65 | + -H "Content-Type: application/json" \ |
| 66 | + -d '{ |
| 67 | + "name": "add", |
| 68 | + "description": "Adds two numbers", |
| 69 | + "language": "assemblyscript", |
| 70 | + "source": "export function handle(input: string): string { return input; }" |
| 71 | + }' |
| 72 | +``` |
| 73 | + |
| 74 | +### Expected Response |
| 75 | + |
| 76 | +```json |
| 77 | +{ |
| 78 | + "id": "...", |
| 79 | + "name": "add", |
| 80 | + "status": "PENDING", |
| 81 | + "createdAt": "..." |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +### Actual Response |
| 86 | + |
| 87 | +```json |
| 88 | +{ |
| 89 | + "timestamp": "2025-12-27T07:40:02.610285242", |
| 90 | + "message": "Internal server error", |
| 91 | + "error": "Internal Server Error", |
| 92 | + "status": 500 |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +### Analysis |
| 97 | + |
| 98 | +1. **Validation works**: Unsupported language returns proper 415 error |
| 99 | +2. **Read operations work**: GET /functions, GET /functions/{id} work correctly |
| 100 | +3. **Failure point**: Error occurs after validation, likely during: |
| 101 | + - Database save (Function entity) |
| 102 | + - PGMQ queue publish (CompilationJob) |
| 103 | + |
| 104 | +### Likely Root Causes |
| 105 | + |
| 106 | +1. **PGMQ queue not initialized**: The `compilation_jobs` queue may not exist in production |
| 107 | +2. **Database migration incomplete**: PGMQ extension or queues not created |
| 108 | +3. **PGMQ connection issue**: API can't connect to queue |
| 109 | + |
| 110 | +### Recommended Investigation |
| 111 | + |
| 112 | +1. SSH into production and check: |
| 113 | + ```bash |
| 114 | + podman exec projectnil-db psql -U projectnil -d projectnil -c "\dx" |
| 115 | + # Should show pgmq extension |
| 116 | + |
| 117 | + podman exec projectnil-db psql -U projectnil -d projectnil -c "SELECT pgmq.list_queues();" |
| 118 | + # Should show compilation_jobs, compilation_results |
| 119 | + ``` |
| 120 | + |
| 121 | +2. Check API logs: |
| 122 | + ```bash |
| 123 | + podman logs projectnil-api --tail 50 |
| 124 | + ``` |
| 125 | + |
| 126 | +3. Check if migrations ran: |
| 127 | + ```bash |
| 128 | + podman logs projectnil-migrations |
| 129 | + ``` |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Observations: What Works |
| 134 | + |
| 135 | +### 1. Health Check |
| 136 | +```bash |
| 137 | +curl http://157.245.108.179:8080/health |
| 138 | +# {"status":"UP"} |
| 139 | +``` |
| 140 | + |
| 141 | +### 2. List Functions (empty) |
| 142 | +```bash |
| 143 | +curl http://157.245.108.179:8080/functions |
| 144 | +# [] |
| 145 | +``` |
| 146 | + |
| 147 | +### 3. 404 for Non-existent Resources |
| 148 | +```bash |
| 149 | +curl http://157.245.108.179:8080/functions/00000000-0000-0000-0000-000000000000 |
| 150 | +# {"timestamp":"...","message":"Function not found: ...","error":"Not Found","status":404} |
| 151 | +``` |
| 152 | + |
| 153 | +### 4. Language Validation |
| 154 | +```bash |
| 155 | +curl -X POST http://157.245.108.179:8080/functions \ |
| 156 | + -H "Content-Type: application/json" \ |
| 157 | + -d '{"name":"x","language":"rust","source":"x"}' |
| 158 | +# {"timestamp":"...","message":"Unsupported language: rust. Supported: [assemblyscript]","error":"Unsupported Media Type","status":415} |
| 159 | +``` |
| 160 | + |
| 161 | +--- |
| 162 | + |
| 163 | +## Bug #2: Missing/Invalid Request Body Returns 500 Instead of 400 |
| 164 | + |
| 165 | +### Observations |
| 166 | + |
| 167 | +| Request Issue | Expected | Actual | |
| 168 | +|---------------|----------|--------| |
| 169 | +| Missing Content-Type header | 415 or 400 | 500 | |
| 170 | +| Empty request body | 400 | 500 | |
| 171 | +| Invalid JSON `{invalid}` | 400 | 500 | |
| 172 | +| Missing required fields | 400 | 415 (language validation first) | |
| 173 | + |
| 174 | +### Examples |
| 175 | + |
| 176 | +```bash |
| 177 | +# Empty body - should be 400 |
| 178 | +curl -X POST http://157.245.108.179:8080/functions \ |
| 179 | + -H "Content-Type: application/json" -d '' |
| 180 | +# Returns 500 |
| 181 | + |
| 182 | +# Invalid JSON - should be 400 |
| 183 | +curl -X POST http://157.245.108.179:8080/functions \ |
| 184 | + -H "Content-Type: application/json" -d '{invalid}' |
| 185 | +# Returns 500 |
| 186 | +``` |
| 187 | + |
| 188 | +### Impact |
| 189 | + |
| 190 | +- Poor developer experience - unhelpful error messages |
| 191 | +- Security concern - 500 errors may leak stack traces in logs |
| 192 | + |
| 193 | +### Recommendation |
| 194 | + |
| 195 | +Add proper request validation and JSON parsing error handling in `GlobalExceptionHandler`: |
| 196 | +- `HttpMessageNotReadableException` → 400 |
| 197 | +- `MissingServletRequestParameterException` → 400 |
| 198 | +- `MethodArgumentTypeMismatchException` → 400 |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +## Bug #3: Invalid UUID Path Parameter Returns 500 |
| 203 | + |
| 204 | +### Steps to Reproduce |
| 205 | + |
| 206 | +```bash |
| 207 | +curl -X POST http://157.245.108.179:8080/functions/not-a-uuid/execute \ |
| 208 | + -H "Content-Type: application/json" \ |
| 209 | + -d '{"input": {}}' |
| 210 | +``` |
| 211 | + |
| 212 | +### Expected |
| 213 | + |
| 214 | +```json |
| 215 | +{ |
| 216 | + "status": 400, |
| 217 | + "error": "Bad Request", |
| 218 | + "message": "Invalid UUID format: not-a-uuid" |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +### Actual |
| 223 | + |
| 224 | +```json |
| 225 | +{ |
| 226 | + "timestamp": "...", |
| 227 | + "message": "Internal server error", |
| 228 | + "error": "Internal Server Error", |
| 229 | + "status": 500 |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +### Affected Endpoints |
| 234 | + |
| 235 | +- `GET /functions/{id}` with invalid UUID |
| 236 | +- `PUT /functions/{id}` with invalid UUID |
| 237 | +- `DELETE /functions/{id}` with invalid UUID |
| 238 | +- `POST /functions/{id}/execute` with invalid UUID |
| 239 | +- `GET /functions/{id}/executions` with invalid UUID |
| 240 | +- `GET /executions/{id}` with invalid UUID |
| 241 | + |
| 242 | +### Recommendation |
| 243 | + |
| 244 | +Add `MethodArgumentTypeMismatchException` handler to `GlobalExceptionHandler`: |
| 245 | + |
| 246 | +```java |
| 247 | +@ExceptionHandler(MethodArgumentTypeMismatchException.class) |
| 248 | +public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex) { |
| 249 | + String message = String.format("Invalid %s: %s", ex.getName(), ex.getValue()); |
| 250 | + return ResponseEntity.badRequest().body(new ErrorResponse(400, "Bad Request", message)); |
| 251 | +} |
| 252 | +``` |
| 253 | + |
| 254 | +--- |
| 255 | + |
| 256 | +## Bug #4: 404 and 405 Errors Return 500 |
| 257 | + |
| 258 | +### Observations |
| 259 | + |
| 260 | +```bash |
| 261 | +# Non-existent endpoint - should be 404 |
| 262 | +curl http://157.245.108.179:8080/nonexistent |
| 263 | +# Returns 500 |
| 264 | + |
| 265 | +# Wrong HTTP method - should be 405 |
| 266 | +curl -X PATCH http://157.245.108.179:8080/functions |
| 267 | +# Returns 500 |
| 268 | +``` |
| 269 | + |
| 270 | +### Impact |
| 271 | + |
| 272 | +- All Spring MVC framework exceptions are being caught by generic handler |
| 273 | +- Makes debugging very difficult |
| 274 | +- Poor API experience |
| 275 | + |
| 276 | +### Root Cause Theory |
| 277 | + |
| 278 | +The `GlobalExceptionHandler` likely has a catch-all `@ExceptionHandler(Exception.class)` that's swallowing specific exceptions like: |
| 279 | +- `NoResourceFoundException` (Spring 6.x) → should be 404 |
| 280 | +- `HttpRequestMethodNotSupportedException` → should be 405 |
| 281 | +- `MethodArgumentTypeMismatchException` → should be 400 |
| 282 | + |
| 283 | +### Recommendation |
| 284 | + |
| 285 | +Add specific exception handlers BEFORE the generic catch-all: |
| 286 | + |
| 287 | +```java |
| 288 | +@ExceptionHandler(NoResourceFoundException.class) |
| 289 | +public ResponseEntity<ErrorResponse> handleNotFound(NoResourceFoundException ex) { |
| 290 | + return ResponseEntity.status(404).body(...); |
| 291 | +} |
| 292 | + |
| 293 | +@ExceptionHandler(HttpRequestMethodNotSupportedException.class) |
| 294 | +public ResponseEntity<ErrorResponse> handleMethodNotAllowed(...) { |
| 295 | + return ResponseEntity.status(405).body(...); |
| 296 | +} |
| 297 | +``` |
| 298 | + |
| 299 | +--- |
| 300 | + |
| 301 | +## Observation: Input Validation Order |
| 302 | + |
| 303 | +The execute endpoint validates function existence before input format: |
| 304 | + |
| 305 | +```bash |
| 306 | +# With invalid input but non-existent function |
| 307 | +curl -X POST $API_URL/functions/00000000.../execute -d '{"input": "string"}' |
| 308 | +# Returns 404 (function not found), not 400 (invalid input) |
| 309 | +``` |
| 310 | + |
| 311 | +This is acceptable behavior (fail-fast on existence) but worth noting: |
| 312 | +- Input validation happens inside `ExecutionService.execute()` after function lookup |
| 313 | +- A truly invalid request with bad input AND non-existent function returns 404 |
| 314 | + |
| 315 | +--- |
| 316 | + |
| 317 | +## Unable to Test (Blocked by Bug #1) |
| 318 | + |
| 319 | +Since we can't create functions, the following cannot be tested: |
| 320 | + |
| 321 | +- [ ] GET /functions/{id} (with valid function) |
| 322 | +- [ ] PUT /functions/{id} |
| 323 | +- [ ] DELETE /functions/{id} |
| 324 | +- [ ] POST /functions/{id}/execute |
| 325 | +- [ ] GET /functions/{id}/executions |
| 326 | +- [ ] GET /executions/{id} |
| 327 | +- [ ] Compilation flow (PENDING → COMPILING → READY) |
| 328 | +- [ ] Execution flow |
| 329 | + |
| 330 | +--- |
| 331 | + |
| 332 | +## Documentation Issues Found |
| 333 | + |
| 334 | +### Issue #1: Getting Started guide assumes local setup works |
| 335 | + |
| 336 | +The guide jumps straight to `curl http://localhost:8080/functions` without verifying the full stack is operational. Consider adding a "verify your setup" section. |
| 337 | + |
| 338 | +### Issue #2: No production troubleshooting section |
| 339 | + |
| 340 | +The guide has troubleshooting for local development but not for production issues. |
| 341 | + |
| 342 | +### Issue #3: Error messages don't help diagnose |
| 343 | + |
| 344 | +The 500 error says "Internal server error" with no actionable details. Consider: |
| 345 | +- Adding correlation IDs to error responses |
| 346 | +- Logging more context in production logs |
| 347 | + |
| 348 | +--- |
| 349 | + |
| 350 | +## Next Steps |
| 351 | + |
| 352 | +1. **Immediate**: Investigate and fix Bug #1 (500 on POST /functions) |
| 353 | +2. **After fix**: Complete the testing of all endpoints |
| 354 | +3. **Documentation**: Add production troubleshooting guide |
| 355 | +4. **Observability**: Add correlation IDs and structured logging |
0 commit comments