Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- scenario_config.json +76 -35
- server/devops_sandbox_environment.py +43 -31
- simulated_app/.env +7 -0
- simulated_app/config.json +6 -1
- simulated_app/middleware/logger.js +18 -0
- simulated_app/middleware/rateLimit.js +39 -0
- simulated_app/routes/data.js +12 -14
- simulated_app/routes/status.js +20 -0
- simulated_app/routes/users.js +10 -8
- simulated_app/server.js +16 -4
scenario_config.json
CHANGED
|
@@ -7,24 +7,45 @@
|
|
| 7 |
"temperature": 0.2,
|
| 8 |
"max_tokens": 256,
|
| 9 |
"max_steps_per_task": 8,
|
| 10 |
-
"system_prompt": "You are an expert DevOps engineer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"tasks": [
|
| 12 |
{
|
| 13 |
"task_name": "easy",
|
| 14 |
-
"
|
| 15 |
-
"
|
| 16 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"verifiers": [
|
| 18 |
{
|
| 19 |
"name": "Config Port Fixed",
|
| 20 |
-
"description": "Ensures config.json has port set to 3000",
|
| 21 |
"verification_type": "file_content",
|
| 22 |
"file_path": "/app/config.json",
|
| 23 |
"expected_content": "3000"
|
| 24 |
},
|
| 25 |
{
|
| 26 |
-
"name": "Health Endpoint
|
| 27 |
-
"description": "GET /health returns HTTP 200",
|
| 28 |
"verification_type": "http_check",
|
| 29 |
"endpoint": "/health",
|
| 30 |
"expected_status": 200
|
|
@@ -33,33 +54,36 @@
|
|
| 33 |
},
|
| 34 |
{
|
| 35 |
"task_name": "medium",
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
],
|
| 45 |
"verifiers": [
|
| 46 |
{
|
| 47 |
"name": "Config Port Fixed",
|
| 48 |
-
"description": "Ensures config.json has port set to 3000",
|
| 49 |
"verification_type": "file_content",
|
| 50 |
"file_path": "/app/config.json",
|
| 51 |
"expected_content": "3000"
|
| 52 |
},
|
| 53 |
{
|
| 54 |
"name": "Syntax Error Fixed",
|
| 55 |
-
"description": "routes/users.js has closing parenthesis on router.get",
|
| 56 |
"verification_type": "file_content",
|
| 57 |
"file_path": "/app/routes/users.js",
|
| 58 |
"expected_content": "});"
|
| 59 |
},
|
| 60 |
{
|
| 61 |
-
"name": "Users Endpoint
|
| 62 |
-
"description": "GET /api/users returns HTTP 200 with users array",
|
| 63 |
"verification_type": "http_check",
|
| 64 |
"endpoint": "/api/users",
|
| 65 |
"expected_status": 200,
|
|
@@ -69,49 +93,67 @@
|
|
| 69 |
},
|
| 70 |
{
|
| 71 |
"task_name": "hard",
|
| 72 |
-
"
|
| 73 |
-
"
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
],
|
| 78 |
-
"
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
],
|
| 83 |
"verifiers": [
|
| 84 |
{
|
| 85 |
"name": "Config Port Fixed",
|
| 86 |
-
"description": "Ensures config.json has port set to 3000",
|
| 87 |
"verification_type": "file_content",
|
| 88 |
"file_path": "/app/config.json",
|
| 89 |
"expected_content": "3000"
|
| 90 |
},
|
| 91 |
{
|
| 92 |
"name": "Syntax Error Fixed",
|
| 93 |
-
"description": "routes/users.js has closing parenthesis",
|
| 94 |
"verification_type": "file_content",
|
| 95 |
"file_path": "/app/routes/users.js",
|
| 96 |
"expected_content": "});"
|
| 97 |
},
|
| 98 |
{
|
| 99 |
"name": "Await Added",
|
| 100 |
-
"description": "routes/data.js uses await before fetchDataFromDB()",
|
| 101 |
"verification_type": "file_content",
|
| 102 |
"file_path": "/app/routes/data.js",
|
| 103 |
-
"expected_content": "await
|
| 104 |
},
|
| 105 |
{
|
| 106 |
"name": "Health Endpoint",
|
| 107 |
-
"description": "GET /health returns HTTP 200",
|
| 108 |
"verification_type": "http_check",
|
| 109 |
"endpoint": "/health",
|
| 110 |
"expected_status": 200
|
| 111 |
},
|
| 112 |
{
|
| 113 |
"name": "Users Endpoint",
|
| 114 |
-
"description": "GET /api/users returns valid JSON with users",
|
| 115 |
"verification_type": "http_check",
|
| 116 |
"endpoint": "/api/users",
|
| 117 |
"expected_status": 200,
|
|
@@ -119,7 +161,6 @@
|
|
| 119 |
},
|
| 120 |
{
|
| 121 |
"name": "Data Endpoint",
|
| 122 |
-
"description": "GET /api/data returns valid JSON with records",
|
| 123 |
"verification_type": "http_check",
|
| 124 |
"endpoint": "/api/data",
|
| 125 |
"expected_status": 200,
|
|
|
|
| 7 |
"temperature": 0.2,
|
| 8 |
"max_tokens": 256,
|
| 9 |
"max_steps_per_task": 8,
|
| 10 |
+
"system_prompt": "You are an expert DevOps engineer responding to a production incident. A Node.js Express backend is broken and you must diagnose and fix it using bash commands. The app directory is /app. It contains config files, route handlers, middleware, .env, and old logs. NOT everything is broken — focus on actual root causes, not red herrings. Respond ONLY with a JSON object: {\"command\": \"<bash command>\"}. Use sed, echo, cat, grep — never interactive editors.",
|
| 11 |
+
"app_structure": {
|
| 12 |
+
"files": [
|
| 13 |
+
"/app/config.json",
|
| 14 |
+
"/app/server.js",
|
| 15 |
+
"/app/package.json",
|
| 16 |
+
"/app/.env",
|
| 17 |
+
"/app/routes/users.js",
|
| 18 |
+
"/app/routes/data.js",
|
| 19 |
+
"/app/routes/status.js",
|
| 20 |
+
"/app/middleware/logger.js",
|
| 21 |
+
"/app/middleware/rateLimit.js",
|
| 22 |
+
"/app/logs/error.log"
|
| 23 |
+
],
|
| 24 |
+
"bug_files": ["config.json", "routes/users.js", "routes/data.js"],
|
| 25 |
+
"red_herrings": [".env", "middleware/logger.js", "middleware/rateLimit.js", "routes/status.js", "logs/error.log"]
|
| 26 |
+
},
|
| 27 |
"tasks": [
|
| 28 |
{
|
| 29 |
"task_name": "easy",
|
| 30 |
+
"severity": "LOW",
|
| 31 |
+
"bugs_count": 1,
|
| 32 |
+
"description": "The app fails to bind to the expected port. Fix it so the app starts on port 3000 and GET /health returns 200.",
|
| 33 |
+
"bugs": [
|
| 34 |
+
{
|
| 35 |
+
"file": "config.json",
|
| 36 |
+
"type": "misconfiguration",
|
| 37 |
+
"description": "Port is set to 9999 instead of 3000"
|
| 38 |
+
}
|
| 39 |
+
],
|
| 40 |
"verifiers": [
|
| 41 |
{
|
| 42 |
"name": "Config Port Fixed",
|
|
|
|
| 43 |
"verification_type": "file_content",
|
| 44 |
"file_path": "/app/config.json",
|
| 45 |
"expected_content": "3000"
|
| 46 |
},
|
| 47 |
{
|
| 48 |
+
"name": "Health Endpoint",
|
|
|
|
| 49 |
"verification_type": "http_check",
|
| 50 |
"endpoint": "/health",
|
| 51 |
"expected_status": 200
|
|
|
|
| 54 |
},
|
| 55 |
{
|
| 56 |
"task_name": "medium",
|
| 57 |
+
"severity": "MEDIUM",
|
| 58 |
+
"bugs_count": 2,
|
| 59 |
+
"description": "App crashes on startup. Fix the crash AND a config issue so all endpoints up to /api/users work.",
|
| 60 |
+
"bugs": [
|
| 61 |
+
{
|
| 62 |
+
"file": "config.json",
|
| 63 |
+
"type": "misconfiguration",
|
| 64 |
+
"description": "Port is set to 9999 instead of 3000"
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"file": "routes/users.js",
|
| 68 |
+
"type": "syntax_error",
|
| 69 |
+
"description": "Missing closing parenthesis on router.get() call causes SyntaxError on import"
|
| 70 |
+
}
|
| 71 |
],
|
| 72 |
"verifiers": [
|
| 73 |
{
|
| 74 |
"name": "Config Port Fixed",
|
|
|
|
| 75 |
"verification_type": "file_content",
|
| 76 |
"file_path": "/app/config.json",
|
| 77 |
"expected_content": "3000"
|
| 78 |
},
|
| 79 |
{
|
| 80 |
"name": "Syntax Error Fixed",
|
|
|
|
| 81 |
"verification_type": "file_content",
|
| 82 |
"file_path": "/app/routes/users.js",
|
| 83 |
"expected_content": "});"
|
| 84 |
},
|
| 85 |
{
|
| 86 |
+
"name": "Users Endpoint",
|
|
|
|
| 87 |
"verification_type": "http_check",
|
| 88 |
"endpoint": "/api/users",
|
| 89 |
"expected_status": 200,
|
|
|
|
| 93 |
},
|
| 94 |
{
|
| 95 |
"task_name": "hard",
|
| 96 |
+
"severity": "HIGH",
|
| 97 |
+
"bugs_count": 3,
|
| 98 |
+
"description": "Multiple issues: app crashes, endpoints return errors. Misleading logs present. Fix ALL root causes.",
|
| 99 |
+
"bugs": [
|
| 100 |
+
{
|
| 101 |
+
"file": "config.json",
|
| 102 |
+
"type": "misconfiguration",
|
| 103 |
+
"description": "Port is set to 9999 instead of 3000"
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"file": "routes/users.js",
|
| 107 |
+
"type": "syntax_error",
|
| 108 |
+
"description": "Missing closing parenthesis on router.get() call"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"file": "routes/data.js",
|
| 112 |
+
"type": "async_bug",
|
| 113 |
+
"description": "Missing 'await' before fetchRecordsFromDB() — returns Promise instead of data"
|
| 114 |
+
}
|
| 115 |
],
|
| 116 |
+
"red_herrings": [
|
| 117 |
+
{
|
| 118 |
+
"file": "logs/error.log",
|
| 119 |
+
"description": "Old error log with misleading entries about port fallback and database connections"
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"file": ".env",
|
| 123 |
+
"description": "Environment file with DB_HOST and CORS settings — not actually used by the app"
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"file": "middleware/rateLimit.js",
|
| 127 |
+
"description": "Working rate limiter — might look suspicious but has generous limits"
|
| 128 |
+
}
|
| 129 |
],
|
| 130 |
"verifiers": [
|
| 131 |
{
|
| 132 |
"name": "Config Port Fixed",
|
|
|
|
| 133 |
"verification_type": "file_content",
|
| 134 |
"file_path": "/app/config.json",
|
| 135 |
"expected_content": "3000"
|
| 136 |
},
|
| 137 |
{
|
| 138 |
"name": "Syntax Error Fixed",
|
|
|
|
| 139 |
"verification_type": "file_content",
|
| 140 |
"file_path": "/app/routes/users.js",
|
| 141 |
"expected_content": "});"
|
| 142 |
},
|
| 143 |
{
|
| 144 |
"name": "Await Added",
|
|
|
|
| 145 |
"verification_type": "file_content",
|
| 146 |
"file_path": "/app/routes/data.js",
|
| 147 |
+
"expected_content": "await fetchRecordsFromDB"
|
| 148 |
},
|
| 149 |
{
|
| 150 |
"name": "Health Endpoint",
|
|
|
|
| 151 |
"verification_type": "http_check",
|
| 152 |
"endpoint": "/health",
|
| 153 |
"expected_status": 200
|
| 154 |
},
|
| 155 |
{
|
| 156 |
"name": "Users Endpoint",
|
|
|
|
| 157 |
"verification_type": "http_check",
|
| 158 |
"endpoint": "/api/users",
|
| 159 |
"expected_status": 200,
|
|
|
|
| 161 |
},
|
| 162 |
{
|
| 163 |
"name": "Data Endpoint",
|
|
|
|
| 164 |
"verification_type": "http_check",
|
| 165 |
"endpoint": "/api/data",
|
| 166 |
"expected_status": 200,
|
server/devops_sandbox_environment.py
CHANGED
|
@@ -59,6 +59,13 @@ BUG_FILES = {
|
|
| 59 |
"routes/data.js": "await",
|
| 60 |
}
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
class DevOpsSandbox(Environment):
|
| 64 |
"""
|
|
@@ -132,9 +139,9 @@ class DevOpsSandbox(Environment):
|
|
| 132 |
self._snapshot_file_hashes()
|
| 133 |
self._inject_grader_script()
|
| 134 |
|
| 135 |
-
# Gather initial observation
|
| 136 |
init_stdout = self._exec_cmd(
|
| 137 |
-
f"
|
| 138 |
)
|
| 139 |
|
| 140 |
task_prompt = self._build_task_prompt(init_stdout)
|
|
@@ -240,50 +247,55 @@ class DevOpsSandbox(Environment):
|
|
| 240 |
def _build_task_prompt(self, init_stdout: str) -> str:
|
| 241 |
"""Build the task prompt based on the current difficulty level."""
|
| 242 |
base = (
|
| 243 |
-
"===
|
| 244 |
-
f"
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
| 246 |
)
|
| 247 |
|
| 248 |
if self._current_task == "easy":
|
| 249 |
mission = (
|
| 250 |
-
"
|
| 251 |
-
"
|
| 252 |
-
"
|
| 253 |
-
"
|
| 254 |
-
"HINTS:\n"
|
| 255 |
-
" - Check config.json for wrong settings\n"
|
| 256 |
)
|
| 257 |
elif self._current_task == "medium":
|
| 258 |
mission = (
|
| 259 |
-
"
|
| 260 |
-
"
|
| 261 |
-
"
|
| 262 |
-
"
|
| 263 |
-
"
|
| 264 |
-
"
|
| 265 |
-
" -
|
| 266 |
-
" -
|
|
|
|
|
|
|
| 267 |
)
|
| 268 |
else:
|
| 269 |
mission = (
|
| 270 |
-
"
|
| 271 |
-
"
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
"
|
| 275 |
-
"
|
| 276 |
-
"
|
| 277 |
-
" -
|
| 278 |
-
" -
|
| 279 |
-
" -
|
|
|
|
|
|
|
| 280 |
)
|
| 281 |
|
| 282 |
return (
|
| 283 |
base + mission +
|
| 284 |
"\nUse bash commands to explore, edit files, and test.\n"
|
| 285 |
-
"When you think you've fixed everything, run: npm start\n\n"
|
| 286 |
-
f"--- INITIAL
|
| 287 |
)
|
| 288 |
|
| 289 |
def _bugs_for_task(self) -> int:
|
|
|
|
| 59 |
"routes/data.js": "await",
|
| 60 |
}
|
| 61 |
|
| 62 |
+
# All interesting files in the app (bugs + red herrings)
|
| 63 |
+
ALL_TRACKED_FILES = {
|
| 64 |
+
"config.json", "server.js", "routes/users.js", "routes/data.js",
|
| 65 |
+
"routes/status.js", "middleware/logger.js", "middleware/rateLimit.js",
|
| 66 |
+
".env", "logs/error.log",
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
|
| 70 |
class DevOpsSandbox(Environment):
|
| 71 |
"""
|
|
|
|
| 139 |
self._snapshot_file_hashes()
|
| 140 |
self._inject_grader_script()
|
| 141 |
|
| 142 |
+
# Gather initial observation — show full file tree
|
| 143 |
init_stdout = self._exec_cmd(
|
| 144 |
+
f"find {self._app_dir} -type f | head -20 && echo '---' && cat {os.path.join(self._app_dir, 'package.json')}"
|
| 145 |
)
|
| 146 |
|
| 147 |
task_prompt = self._build_task_prompt(init_stdout)
|
|
|
|
| 247 |
def _build_task_prompt(self, init_stdout: str) -> str:
|
| 248 |
"""Build the task prompt based on the current difficulty level."""
|
| 249 |
base = (
|
| 250 |
+
"=== DEVOPS INCIDENT RESPONSE ===\n"
|
| 251 |
+
f"ALERT: Production Node.js service in {self._app_dir} is DOWN.\n"
|
| 252 |
+
"You are the on-call engineer. Diagnose and fix the issue(s).\n\n"
|
| 253 |
+
"The app is an Express.js backend with multiple routes, middleware,\n"
|
| 254 |
+
"config files, and logs. Not everything you see is broken — some files\n"
|
| 255 |
+
"are red herrings. Focus on what's actually causing failures.\n\n"
|
| 256 |
)
|
| 257 |
|
| 258 |
if self._current_task == "easy":
|
| 259 |
mission = (
|
| 260 |
+
"SEVERITY: LOW (1 known issue)\n"
|
| 261 |
+
"SYMPTOM: App fails to bind to the expected port.\n"
|
| 262 |
+
"EXPECTED: App should listen on port 3000, GET /health returns 200.\n\n"
|
| 263 |
+
"Start by checking configuration and trying to start the app.\n"
|
|
|
|
|
|
|
| 264 |
)
|
| 265 |
elif self._current_task == "medium":
|
| 266 |
mission = (
|
| 267 |
+
"SEVERITY: MEDIUM (2 known issues)\n"
|
| 268 |
+
"SYMPTOMS:\n"
|
| 269 |
+
" - App crashes immediately on startup\n"
|
| 270 |
+
" - Even after fixing the crash, some routes may not work\n"
|
| 271 |
+
"EXPECTED:\n"
|
| 272 |
+
" - App listens on port 3000\n"
|
| 273 |
+
" - GET /health returns 200\n"
|
| 274 |
+
" - GET /api/users returns 200 with valid JSON\n\n"
|
| 275 |
+
"Check startup logs carefully. The crash message will point you\n"
|
| 276 |
+
"to the first bug, but there may be a config issue too.\n"
|
| 277 |
)
|
| 278 |
else:
|
| 279 |
mission = (
|
| 280 |
+
"SEVERITY: HIGH (3 known issues)\n"
|
| 281 |
+
"SYMPTOMS:\n"
|
| 282 |
+
" - App crashes on startup with an error\n"
|
| 283 |
+
" - Multiple endpoints return errors or bad data\n"
|
| 284 |
+
" - There are misleading old logs in logs/error.log\n"
|
| 285 |
+
"EXPECTED:\n"
|
| 286 |
+
" - App listens on port 3000\n"
|
| 287 |
+
" - GET /health returns 200\n"
|
| 288 |
+
" - GET /api/users returns 200 with JSON containing 'users' array\n"
|
| 289 |
+
" - GET /api/data returns 200 with JSON containing 'records' array\n\n"
|
| 290 |
+
"WARNING: The app has middleware, config files, .env, and old logs.\n"
|
| 291 |
+
"Not everything is broken — isolate the actual root causes.\n"
|
| 292 |
)
|
| 293 |
|
| 294 |
return (
|
| 295 |
base + mission +
|
| 296 |
"\nUse bash commands to explore, edit files, and test.\n"
|
| 297 |
+
"When you think you've fixed everything, run: cd /app && npm start\n\n"
|
| 298 |
+
f"--- INITIAL STATE ---\n{init_stdout}\n"
|
| 299 |
)
|
| 300 |
|
| 301 |
def _bugs_for_task(self) -> int:
|
simulated_app/.env
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
NODE_ENV=production
|
| 2 |
+
LOG_LEVEL=info
|
| 3 |
+
DB_HOST=localhost
|
| 4 |
+
DB_PORT=5432
|
| 5 |
+
DB_NAME=sandbox_db
|
| 6 |
+
SESSION_SECRET=s3cr3t_k3y_d0_n0t_sh4r3
|
| 7 |
+
CORS_ORIGIN=http://localhost:3000
|
simulated_app/config.json
CHANGED
|
@@ -1,5 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"port": 9999,
|
| 3 |
"appName": "DevOps Sandbox App",
|
| 4 |
-
"version": "1.0.0"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"port": 9999,
|
| 3 |
"appName": "DevOps Sandbox App",
|
| 4 |
+
"version": "1.0.0",
|
| 5 |
+
"logLevel": "info",
|
| 6 |
+
"rateLimit": {
|
| 7 |
+
"windowMs": 60000,
|
| 8 |
+
"maxRequests": 100
|
| 9 |
+
}
|
| 10 |
}
|
simulated_app/middleware/logger.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Request logging middleware.
|
| 3 |
+
* Logs method, URL, status code, and response time for every request.
|
| 4 |
+
*/
|
| 5 |
+
function requestLogger(req, res, next) {
|
| 6 |
+
const start = Date.now();
|
| 7 |
+
const originalEnd = res.end;
|
| 8 |
+
|
| 9 |
+
res.end = function (...args) {
|
| 10 |
+
const duration = Date.now() - start;
|
| 11 |
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
|
| 12 |
+
originalEnd.apply(res, args);
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
next();
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
module.exports = { requestLogger };
|
simulated_app/middleware/rateLimit.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Simple in-memory rate limiter middleware.
|
| 3 |
+
* Tracks request counts per IP within a sliding window.
|
| 4 |
+
*/
|
| 5 |
+
const requestCounts = new Map();
|
| 6 |
+
|
| 7 |
+
function rateLimiter(req, res, next) {
|
| 8 |
+
const ip = req.ip || req.connection.remoteAddress || 'unknown';
|
| 9 |
+
const now = Date.now();
|
| 10 |
+
const windowMs = 60000; // 1 minute window
|
| 11 |
+
const maxRequests = 100;
|
| 12 |
+
|
| 13 |
+
if (!requestCounts.has(ip)) {
|
| 14 |
+
requestCounts.set(ip, { count: 1, windowStart: now });
|
| 15 |
+
return next();
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
const record = requestCounts.get(ip);
|
| 19 |
+
|
| 20 |
+
if (now - record.windowStart > windowMs) {
|
| 21 |
+
// Reset window
|
| 22 |
+
record.count = 1;
|
| 23 |
+
record.windowStart = now;
|
| 24 |
+
return next();
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
record.count++;
|
| 28 |
+
|
| 29 |
+
if (record.count > maxRequests) {
|
| 30 |
+
return res.status(429).json({
|
| 31 |
+
error: 'Too many requests',
|
| 32 |
+
retryAfter: Math.ceil((record.windowStart + windowMs - now) / 1000)
|
| 33 |
+
});
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
next();
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
module.exports = { rateLimiter };
|
simulated_app/routes/data.js
CHANGED
|
@@ -1,36 +1,34 @@
|
|
| 1 |
const express = require('express');
|
| 2 |
const router = express.Router();
|
| 3 |
|
| 4 |
-
// Simulates
|
| 5 |
-
function
|
| 6 |
return new Promise((resolve) => {
|
| 7 |
setTimeout(() => {
|
| 8 |
resolve({
|
| 9 |
records: [
|
| 10 |
-
{ id: 1,
|
| 11 |
-
{ id: 2,
|
| 12 |
-
{ id: 3, value: '
|
|
|
|
| 13 |
],
|
| 14 |
-
|
|
|
|
| 15 |
});
|
| 16 |
}, 100);
|
| 17 |
});
|
| 18 |
}
|
| 19 |
|
| 20 |
-
// BUG 3 (Hard): The handler is marked async but does NOT await the Promise.
|
| 21 |
-
// This means `result` will be a pending Promise object, not the resolved data.
|
| 22 |
-
// Express will try to serialize the Promise, resulting in an empty/broken response
|
| 23 |
-
// or a 500 error when the client expects valid JSON.
|
| 24 |
-
|
| 25 |
router.get('/', async (req, res) => {
|
| 26 |
try {
|
| 27 |
-
const result =
|
| 28 |
if (!result || !result.records) {
|
| 29 |
-
return res.status(500).json({ error: '
|
| 30 |
}
|
| 31 |
res.json(result);
|
| 32 |
} catch (err) {
|
| 33 |
-
|
|
|
|
| 34 |
}
|
| 35 |
});
|
| 36 |
|
|
|
|
| 1 |
const express = require('express');
|
| 2 |
const router = express.Router();
|
| 3 |
|
| 4 |
+
// Simulates an async database query
|
| 5 |
+
function fetchRecordsFromDB() {
|
| 6 |
return new Promise((resolve) => {
|
| 7 |
setTimeout(() => {
|
| 8 |
resolve({
|
| 9 |
records: [
|
| 10 |
+
{ id: 1, sensor: 'temperature', value: 42.5, unit: 'C', timestamp: new Date().toISOString() },
|
| 11 |
+
{ id: 2, sensor: 'humidity', value: 67.3, unit: '%', timestamp: new Date().toISOString() },
|
| 12 |
+
{ id: 3, sensor: 'pressure', value: 1013.25, unit: 'hPa', timestamp: new Date().toISOString() },
|
| 13 |
+
{ id: 4, sensor: 'wind_speed', value: 12.8, unit: 'km/h', timestamp: new Date().toISOString() }
|
| 14 |
],
|
| 15 |
+
total: 4,
|
| 16 |
+
page: 1
|
| 17 |
});
|
| 18 |
}, 100);
|
| 19 |
});
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
router.get('/', async (req, res) => {
|
| 23 |
try {
|
| 24 |
+
const result = fetchRecordsFromDB();
|
| 25 |
if (!result || !result.records) {
|
| 26 |
+
return res.status(500).json({ error: 'Database query returned empty result' });
|
| 27 |
}
|
| 28 |
res.json(result);
|
| 29 |
} catch (err) {
|
| 30 |
+
console.error(`[DATA] Error fetching records: ${err.message}`);
|
| 31 |
+
res.status(500).json({ error: 'Failed to fetch sensor data' });
|
| 32 |
}
|
| 33 |
});
|
| 34 |
|
simulated_app/routes/status.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const router = express.Router();
|
| 3 |
+
const os = require('os');
|
| 4 |
+
|
| 5 |
+
router.get('/', (req, res) => {
|
| 6 |
+
res.json({
|
| 7 |
+
status: 'operational',
|
| 8 |
+
hostname: os.hostname(),
|
| 9 |
+
platform: os.platform(),
|
| 10 |
+
memory: {
|
| 11 |
+
total: Math.round(os.totalmem() / 1024 / 1024),
|
| 12 |
+
free: Math.round(os.freemem() / 1024 / 1024),
|
| 13 |
+
unit: 'MB'
|
| 14 |
+
},
|
| 15 |
+
uptime: Math.round(os.uptime()),
|
| 16 |
+
nodeVersion: process.version
|
| 17 |
+
});
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
module.exports = router;
|
simulated_app/routes/users.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
| 1 |
const express = require('express');
|
| 2 |
const router = express.Router();
|
| 3 |
|
| 4 |
-
// BUG 2 (Medium): There is a syntax error below.
|
| 5 |
-
// The closing parenthesis for router.get() is missing,
|
| 6 |
-
// which will cause Node.js to crash on startup with a SyntaxError.
|
| 7 |
-
|
| 8 |
const users = [
|
| 9 |
-
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
| 10 |
-
{ id: 2, name: 'Bob', email: 'bob@example.com' },
|
| 11 |
-
{ id: 3, name: 'Charlie', email: 'charlie@example.com' }
|
|
|
|
| 12 |
];
|
| 13 |
|
| 14 |
router.get('/', (req, res) => {
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
};
|
| 17 |
|
| 18 |
module.exports = router;
|
|
|
|
| 1 |
const express = require('express');
|
| 2 |
const router = express.Router();
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
const users = [
|
| 5 |
+
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
|
| 6 |
+
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
|
| 7 |
+
{ id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'user' },
|
| 8 |
+
{ id: 4, name: 'Diana', email: 'diana@example.com', role: 'moderator' }
|
| 9 |
];
|
| 10 |
|
| 11 |
router.get('/', (req, res) => {
|
| 12 |
+
const role = req.query.role;
|
| 13 |
+
if (role) {
|
| 14 |
+
const filtered = users.filter(u => u.role === role);
|
| 15 |
+
return res.json({ users: filtered, count: filtered.length });
|
| 16 |
+
}
|
| 17 |
+
res.json({ users: users, count: users.length });
|
| 18 |
};
|
| 19 |
|
| 20 |
module.exports = router;
|
simulated_app/server.js
CHANGED
|
@@ -1,22 +1,34 @@
|
|
| 1 |
const express = require('express');
|
| 2 |
const config = require('./config.json');
|
|
|
|
|
|
|
| 3 |
|
| 4 |
const app = express();
|
| 5 |
app.use(express.json());
|
|
|
|
|
|
|
| 6 |
|
| 7 |
-
// Health check
|
| 8 |
app.get('/health', (req, res) => {
|
| 9 |
-
res.json({ status: 'ok', uptime: process.uptime() });
|
| 10 |
});
|
| 11 |
|
| 12 |
-
// Mount
|
| 13 |
const usersRouter = require('./routes/users');
|
| 14 |
const dataRouter = require('./routes/data');
|
|
|
|
| 15 |
|
| 16 |
app.use('/api/users', usersRouter);
|
| 17 |
app.use('/api/data', dataRouter);
|
|
|
|
| 18 |
|
| 19 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
const PORT = config.port;
|
| 21 |
app.listen(PORT, '0.0.0.0', () => {
|
| 22 |
console.log(`Server running on port ${PORT}`);
|
|
|
|
| 1 |
const express = require('express');
|
| 2 |
const config = require('./config.json');
|
| 3 |
+
const { requestLogger } = require('./middleware/logger');
|
| 4 |
+
const { rateLimiter } = require('./middleware/rateLimit');
|
| 5 |
|
| 6 |
const app = express();
|
| 7 |
app.use(express.json());
|
| 8 |
+
app.use(requestLogger);
|
| 9 |
+
app.use(rateLimiter);
|
| 10 |
|
| 11 |
+
// Health check
|
| 12 |
app.get('/health', (req, res) => {
|
| 13 |
+
res.json({ status: 'ok', uptime: process.uptime(), version: config.version });
|
| 14 |
});
|
| 15 |
|
| 16 |
+
// Mount routes
|
| 17 |
const usersRouter = require('./routes/users');
|
| 18 |
const dataRouter = require('./routes/data');
|
| 19 |
+
const statusRouter = require('./routes/status');
|
| 20 |
|
| 21 |
app.use('/api/users', usersRouter);
|
| 22 |
app.use('/api/data', dataRouter);
|
| 23 |
+
app.use('/api/status', statusRouter);
|
| 24 |
|
| 25 |
+
// Error handling middleware
|
| 26 |
+
app.use((err, req, res, next) => {
|
| 27 |
+
console.error(`[ERROR] ${err.message}`);
|
| 28 |
+
res.status(500).json({ error: 'Internal server error' });
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
// Start server
|
| 32 |
const PORT = config.port;
|
| 33 |
app.listen(PORT, '0.0.0.0', () => {
|
| 34 |
console.log(`Server running on port ${PORT}`);
|