{
  "name": "codegen-rest-api",
  "description": "Build a REST API server from a specification",
  "dataset": "terminal-bench-local",
  "difficulty": "hard",
  "instruction": "Build a simple REST API for a todo list application using Python's built-in http.server module (no Flask, no FastAPI, no external dependencies). Create a file called server.py that implements:\n\n1. GET /todos — return all todos as JSON array\n2. POST /todos — create a todo (JSON body with 'title' field, auto-assign 'id' starting from 1, default 'completed' to false)\n3. PUT /todos/<id> — update a todo (JSON body, can update 'title' and/or 'completed')\n4. DELETE /todos/<id> — delete a todo\n5. GET /todos/<id> — get a single todo\n\nReturn appropriate status codes: 200 for success, 201 for creation, 404 for not found, 400 for bad requests.\nAll responses should be JSON with Content-Type: application/json.\nStore todos in memory (no database needed).",
  "setup_files": {
    "test_server.py": "import json\nimport subprocess\nimport sys\nimport time\nimport urllib.request\nimport urllib.error\nimport signal\nimport os\n\npassed = 0\nfailed = 0\n\ndef check(desc, got, expected):\n    global passed, failed\n    if got == expected:\n        passed += 1\n    else:\n        print(f\"FAIL {desc}: got {got}, expected {expected}\")\n        failed += 1\n\ndef req(method, path, body=None):\n    url = f\"http://127.0.0.1:18923{path}\"\n    data = json.dumps(body).encode() if body else None\n    r = urllib.request.Request(url, data=data, method=method)\n    r.add_header('Content-Type', 'application/json')\n    try:\n        resp = urllib.request.urlopen(r, timeout=5)\n        return resp.status, json.loads(resp.read().decode())\n    except urllib.error.HTTPError as e:\n        body_text = e.read().decode()\n        try:\n            return e.code, json.loads(body_text)\n        except:\n            return e.code, body_text\n\n# Start server\nproc = subprocess.Popen([sys.executable, 'server.py', '18923'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\ntime.sleep(1.5)\n\ntry:\n    # Test empty list\n    status, data = req('GET', '/todos')\n    check('empty list status', status, 200)\n    check('empty list data', data, [])\n\n    # Create todo\n    status, data = req('POST', '/todos', {'title': 'Buy milk'})\n    check('create status', status, 201)\n    check('create id', data.get('id'), 1)\n    check('create title', data.get('title'), 'Buy milk')\n    check('create completed', data.get('completed'), False)\n\n    # Create second todo\n    status, data = req('POST', '/todos', {'title': 'Walk dog'})\n    check('create2 status', status, 201)\n    check('create2 id', data.get('id'), 2)\n\n    # Get all\n    status, data = req('GET', '/todos')\n    check('list count', len(data), 2)\n\n    # Get single\n    status, data = req('GET', '/todos/1')\n    check('get single status', status, 200)\n    check('get single title', data.get('title'), 'Buy milk')\n\n    # Update\n    status, data = req('PUT', '/todos/1', {'completed': True})\n    check('update status', status, 200)\n    check('update completed', data.get('completed'), True)\n    check('update title preserved', data.get('title'), 'Buy milk')\n\n    # Delete\n    status, _ = req('DELETE', '/todos/2')\n    check('delete status', status, 200)\n\n    # Verify deleted\n    status, _ = req('GET', '/todos/2')\n    check('get deleted', status, 404)\n\n    # List after delete\n    status, data = req('GET', '/todos')\n    check('list after delete', len(data), 1)\n\n    # Not found\n    status, _ = req('GET', '/todos/999')\n    check('not found', status, 404)\n\nfinally:\n    proc.terminate()\n    proc.wait(timeout=5)\n\nprint(f\"{passed}/{passed+failed} tests passed\")\nif failed > 0:\n    sys.exit(1)\n"
  },
  "verify": "cd $BENCH_WORK_DIR && python3 test_server.py",
  "timeout": 360000,
  "tags": ["code-generation", "python", "api", "from-spec"]
}
