From 0d3d603f640cea4868a0854664eec3b74d9413cc Mon Sep 17 00:00:00 2001 From: FORGE Date: Sun, 12 Apr 2026 00:57:15 +0000 Subject: [PATCH 1/3] feat: Create requirements.txt and app.py with GET /hello endpoint Run: 02986f67-8a37-4b6e-bf70-d6b119917c84 Task: 64ba9e45-f035-4eda-bce1-bda8016f763b Agent: builder --- Dockerfile | 12 +++++++++ RUNNING.md | 64 +++++++++++++++++++++++++++++++++++---------- app.py | 37 ++++++++++++++++++++++++++ conftest.py | 5 ++++ docker-compose.yml | 9 +++++++ requirements.txt | 9 ++++--- tests/test_hello.py | 62 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 docker-compose.yml create mode 100644 tests/test_hello.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ae713a9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/RUNNING.md b/RUNNING.md index 77896cf..3477e19 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,33 +1,69 @@ -# Running the Todo API +# Hello World API -## Prerequisites +## TEAM_BRIEF +stack: Python/FastAPI +test_runner: pytest tests/ -v +lint_tool: ruff check . +coverage_tool: pytest-cov +coverage_threshold: 70 +coverage_applies: true -- Python 3.10 or later +## Quick Start -## Install dependencies +### With Docker ```bash -pip install fastapi uvicorn pydantic +docker compose up --build ``` -For running the test suite you will also need: +Then open . + +### Without Docker ```bash -pip install httpx pytest +pip install -r requirements.txt +uvicorn app:app --host 0.0.0.0 --port 8000 ``` -## Start the server +## API Reference -```bash -uvicorn main:app --reload --host 0.0.0.0 --port 8000 +### `GET /` + +Health-check endpoint. + +**Response** `200 OK` + +```json +{"status": "ok"} ``` -The API will be available at . +### `GET /hello` + +Returns a greeting with the current UTC timestamp. + +**Response** `200 OK` -Interactive docs are served at . +```json +{ + "message": "hello world", + "timestamp": "2024-01-15T12:00:00.000000+00:00" +} +``` + +| Field | Type | Description | +|-------------|--------|--------------------------------------| +| `message` | string | Always `"hello world"` | +| `timestamp` | string | Current UTC time in ISO 8601 format | + +## Running Tests + +```bash +pip install -r requirements.txt +pytest tests/ -v --tb=short --cov=app --cov-report=term-missing +``` -## Run the tests +Or with Docker: ```bash -pytest tests/ +docker compose exec api pytest tests/ -v --tb=short --cov=app --cov-report=term-missing ``` diff --git a/app.py b/app.py new file mode 100644 index 0000000..a0073cb --- /dev/null +++ b/app.py @@ -0,0 +1,37 @@ +"""FastAPI application with a GET /hello endpoint. + +Returns a JSON payload containing a greeting message and the current +UTC timestamp in ISO 8601 format. +""" + +from __future__ import annotations + +import datetime + +from fastapi import FastAPI +from pydantic import BaseModel + + +class HelloResponse(BaseModel): + """Response model for the /hello endpoint.""" + + message: str + timestamp: str + + +app = FastAPI(title="Hello World API", version="1.0.0") + + +@app.get("/", tags=["root"]) +async def root() -> dict: + """Health-check endpoint returning a simple status object.""" + return {"status": "ok"} + + +@app.get("/hello", response_model=HelloResponse, tags=["hello"]) +async def hello() -> HelloResponse: + """Return a greeting with the current UTC timestamp.""" + return HelloResponse( + message="hello world", + timestamp=datetime.datetime.now(datetime.timezone.utc).isoformat(), + ) diff --git a/conftest.py b/conftest.py index 2cb6af4..f62d5ec 100644 --- a/conftest.py +++ b/conftest.py @@ -7,6 +7,11 @@ import sys from pathlib import Path +# Ensure the project root is on sys.path so that 'import app' works. +_PROJECT_ROOT = str(Path(__file__).resolve().parent) +if _PROJECT_ROOT not in sys.path: + sys.path.insert(0, _PROJECT_ROOT) + def pytest_addoption(parser): """Register --timeout so pytest doesn't choke when the plugin is absent.""" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..aaade9f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3.9" + +services: + api: + build: . + ports: + - "8000:8000" + environment: + PYTHONUNBUFFERED: "1" diff --git a/requirements.txt b/requirements.txt index 5a6ebf7..a0137c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ -fastapi>=0.100.0 -uvicorn>=0.23.0 +fastapi>=0.110.0,<1.0.0 +uvicorn[standard]>=0.29.0,<1.0.0 pydantic>=2.0.0 -pytest>=7.0.0 +httpx>=0.27.0,<1.0.0 +pytest>=8.0.0,<9.0.0 +pytest-cov>=5.0.0,<6.0.0 pytest-timeout>=2.1.0 -httpx>=0.24.0 diff --git a/tests/test_hello.py b/tests/test_hello.py new file mode 100644 index 0000000..3319d4f --- /dev/null +++ b/tests/test_hello.py @@ -0,0 +1,62 @@ +"""Tests for the GET /hello and GET / endpoints.""" + +from __future__ import annotations + +import datetime + +from fastapi.testclient import TestClient + +from app import app + +client = TestClient(app) + + +def test_hello_returns_200() -> None: + """GET /hello must return HTTP 200.""" + response = client.get("/hello") + assert response.status_code == 200 + + +def test_hello_response_has_message() -> None: + """The response body must contain message='hello world'.""" + response = client.get("/hello") + body = response.json() + assert body["message"] == "hello world" + + +def test_hello_response_has_iso_timestamp() -> None: + """The timestamp field must be a valid ISO 8601 string.""" + response = client.get("/hello") + body = response.json() + # Will raise ValueError if not a valid ISO 8601 string + parsed = datetime.datetime.fromisoformat(body["timestamp"]) + assert isinstance(parsed, datetime.datetime) + + +def test_hello_response_timestamp_is_utc() -> None: + """The parsed timestamp must carry UTC timezone info (offset == 0).""" + response = client.get("/hello") + body = response.json() + parsed = datetime.datetime.fromisoformat(body["timestamp"]) + assert parsed.tzinfo is not None + assert parsed.utcoffset() == datetime.timedelta(0) + + +def test_hello_response_schema_keys() -> None: + """The response JSON must contain exactly 'message' and 'timestamp'.""" + response = client.get("/hello") + body = response.json() + assert set(body.keys()) == {"message", "timestamp"} + + +def test_root_returns_200() -> None: + """GET / must return 200 with {'status': 'ok'}.""" + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + +def test_hello_method_not_allowed() -> None: + """POST /hello must return 405 Method Not Allowed.""" + response = client.post("/hello") + assert response.status_code == 405 From c6b2cb76afe343c30d08de84b14730eae40821b3 Mon Sep 17 00:00:00 2001 From: FORGE Date: Sun, 12 Apr 2026 00:58:14 +0000 Subject: [PATCH 2/3] feat: Create RUNNING.md with startup instructions Run: 02986f67-8a37-4b6e-bf70-d6b119917c84 Task: a7dead25-a356-4c63-9f2c-0858be7ad48c Agent: builder --- RUNNING.md | 49 ++++++++++++++++++++++++++++++++++----------- docker-compose.yml | 2 -- requirements.txt | 2 -- tests/test_hello.py | 20 +++++++++--------- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/RUNNING.md b/RUNNING.md index 3477e19..90da22f 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -8,23 +8,53 @@ coverage_tool: pytest-cov coverage_threshold: 70 coverage_applies: true -## Quick Start +## Prerequisites -### With Docker +- Python 3.11+ +- pip + +## Install Dependencies ```bash -docker compose up --build +pip install -r requirements.txt ``` -Then open . - -### Without Docker +## Start the Server ```bash -pip install -r requirements.txt uvicorn app:app --host 0.0.0.0 --port 8000 ``` +The server will be available at . + +## Sample Request + +```bash +curl http://localhost:8000/hello +``` + +**Expected JSON response:** + +```json +{ + "message": "hello world", + "timestamp": "2024-01-15T12:00:00.000000+00:00" +} +``` + +| Field | Type | Description | +|-------------|--------|--------------------------------------| +| `message` | string | Always `"hello world"` | +| `timestamp` | string | Current UTC time in ISO 8601 format | + +## Quick Start with Docker + +```bash +docker compose up --build +``` + +Then open . + ## API Reference ### `GET /` @@ -50,11 +80,6 @@ Returns a greeting with the current UTC timestamp. } ``` -| Field | Type | Description | -|-------------|--------|--------------------------------------| -| `message` | string | Always `"hello world"` | -| `timestamp` | string | Current UTC time in ISO 8601 format | - ## Running Tests ```bash diff --git a/docker-compose.yml b/docker-compose.yml index aaade9f..eee0816 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.9" - services: api: build: . diff --git a/requirements.txt b/requirements.txt index a0137c6..cf578d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ fastapi>=0.110.0,<1.0.0 uvicorn[standard]>=0.29.0,<1.0.0 -pydantic>=2.0.0 httpx>=0.27.0,<1.0.0 pytest>=8.0.0,<9.0.0 pytest-cov>=5.0.0,<6.0.0 -pytest-timeout>=2.1.0 diff --git a/tests/test_hello.py b/tests/test_hello.py index 3319d4f..11e0670 100644 --- a/tests/test_hello.py +++ b/tests/test_hello.py @@ -1,4 +1,4 @@ -"""Tests for the GET /hello and GET / endpoints.""" +"""Tests for the Hello World API endpoints.""" from __future__ import annotations @@ -12,29 +12,29 @@ def test_hello_returns_200() -> None: - """GET /hello must return HTTP 200.""" + """GET /hello should return HTTP 200.""" response = client.get("/hello") assert response.status_code == 200 def test_hello_response_has_message() -> None: - """The response body must contain message='hello world'.""" + """GET /hello response body should contain message 'hello world'.""" response = client.get("/hello") body = response.json() assert body["message"] == "hello world" def test_hello_response_has_iso_timestamp() -> None: - """The timestamp field must be a valid ISO 8601 string.""" + """GET /hello response timestamp should be valid ISO 8601.""" response = client.get("/hello") body = response.json() - # Will raise ValueError if not a valid ISO 8601 string + # Will raise ValueError if the timestamp is not valid ISO format parsed = datetime.datetime.fromisoformat(body["timestamp"]) - assert isinstance(parsed, datetime.datetime) + assert parsed is not None def test_hello_response_timestamp_is_utc() -> None: - """The parsed timestamp must carry UTC timezone info (offset == 0).""" + """GET /hello response timestamp should be UTC (offset +00:00).""" response = client.get("/hello") body = response.json() parsed = datetime.datetime.fromisoformat(body["timestamp"]) @@ -43,20 +43,20 @@ def test_hello_response_timestamp_is_utc() -> None: def test_hello_response_schema_keys() -> None: - """The response JSON must contain exactly 'message' and 'timestamp'.""" + """GET /hello response should contain exactly 'message' and 'timestamp' keys.""" response = client.get("/hello") body = response.json() assert set(body.keys()) == {"message", "timestamp"} def test_root_returns_200() -> None: - """GET / must return 200 with {'status': 'ok'}.""" + """GET / should return HTTP 200 with status ok.""" response = client.get("/") assert response.status_code == 200 assert response.json() == {"status": "ok"} def test_hello_method_not_allowed() -> None: - """POST /hello must return 405 Method Not Allowed.""" + """POST /hello should return HTTP 405 Method Not Allowed.""" response = client.post("/hello") assert response.status_code == 405 From 37b0a51d2e285acda653fa5432050b88a2606282 Mon Sep 17 00:00:00 2001 From: FORGE Date: Sun, 12 Apr 2026 00:58:57 +0000 Subject: [PATCH 3/3] feat: Write complete test suite for the Hello World API Run: 02986f67-8a37-4b6e-bf70-d6b119917c84 Task: b809dd80-30ed-46c1-8366-0a3e958c9df3 Agent: builder --- QA.md | 20 ++++++++++++++ RUNNING.md | 67 +++++++-------------------------------------- tests/test_hello.py | 39 +++++++++++++++++++------- 3 files changed, 59 insertions(+), 67 deletions(-) create mode 100644 QA.md diff --git a/QA.md b/QA.md new file mode 100644 index 0000000..2ef83bc --- /dev/null +++ b/QA.md @@ -0,0 +1,20 @@ +app_type: web-api +coverage_applies: true +coverage_source: app +coverage_threshold: 70 +coverage_tool: pytest-cov +install_steps: +- pip install -r /tmp/forge-repos/hello-world-micro-api-02986f67/requirements.txt +- pip install ruff +lint_tool: ruff check . +notes: Verify that GET /hello returns 200 with 'message' equal to 'hello world' and + a valid ISO 8601 'timestamp', and that a non-existent route returns 404. +stack: Python/FastAPI +test_files: +- tests/test_hello.py +- tests/test_main.py +- tests/test_models.py +- tests/test_storage.py +- tests/test_todos.py +test_runner: pytest tests/ -v --tb=short +workspace: /tmp/forge-repos/hello-world-micro-api-02986f67 diff --git a/RUNNING.md b/RUNNING.md index 90da22f..3c71f21 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -8,77 +8,36 @@ coverage_tool: pytest-cov coverage_threshold: 70 coverage_applies: true -## Prerequisites - -- Python 3.11+ -- pip - -## Install Dependencies +## Quick Start ```bash pip install -r requirements.txt -``` - -## Start the Server - -```bash uvicorn app:app --host 0.0.0.0 --port 8000 ``` -The server will be available at . +Then open http://localhost:8000/hello -## Sample Request +## API Reference -```bash -curl http://localhost:8000/hello -``` +### GET /hello -**Expected JSON response:** +Returns a JSON object: ```json { "message": "hello world", - "timestamp": "2024-01-15T12:00:00.000000+00:00" + "timestamp": "2024-01-01T00:00:00+00:00" } ``` | Field | Type | Description | |-------------|--------|--------------------------------------| -| `message` | string | Always `"hello world"` | -| `timestamp` | string | Current UTC time in ISO 8601 format | - -## Quick Start with Docker - -```bash -docker compose up --build -``` - -Then open . - -## API Reference - -### `GET /` +| message | string | Always `"hello world"` | +| timestamp | string | Current UTC time in ISO 8601 format | -Health-check endpoint. +### GET / -**Response** `200 OK` - -```json -{"status": "ok"} -``` - -### `GET /hello` - -Returns a greeting with the current UTC timestamp. - -**Response** `200 OK` - -```json -{ - "message": "hello world", - "timestamp": "2024-01-15T12:00:00.000000+00:00" -} -``` +Health-check endpoint returning `{"status": "ok"}`. ## Running Tests @@ -86,9 +45,3 @@ Returns a greeting with the current UTC timestamp. pip install -r requirements.txt pytest tests/ -v --tb=short --cov=app --cov-report=term-missing ``` - -Or with Docker: - -```bash -docker compose exec api pytest tests/ -v --tb=short --cov=app --cov-report=term-missing -``` diff --git a/tests/test_hello.py b/tests/test_hello.py index 11e0670..a583160 100644 --- a/tests/test_hello.py +++ b/tests/test_hello.py @@ -1,4 +1,15 @@ -"""Tests for the Hello World API endpoints.""" +"""Test suite for the Hello World API. + +Covers: +- GET /hello returns 200 +- Response JSON contains 'message' key with value 'hello world' +- Response JSON contains 'timestamp' key that is a valid ISO 8601 string +- Timestamp is UTC-aware +- Response schema has exactly the expected keys +- GET / health-check returns 200 +- POST /hello returns 405 (Method Not Allowed) +- GET on a non-existent route returns 404 +""" from __future__ import annotations @@ -12,29 +23,31 @@ def test_hello_returns_200() -> None: - """GET /hello should return HTTP 200.""" + """GET /hello must return HTTP 200.""" response = client.get("/hello") assert response.status_code == 200 def test_hello_response_has_message() -> None: - """GET /hello response body should contain message 'hello world'.""" + """Response body must contain 'message' equal to 'hello world'.""" response = client.get("/hello") body = response.json() + assert "message" in body assert body["message"] == "hello world" def test_hello_response_has_iso_timestamp() -> None: - """GET /hello response timestamp should be valid ISO 8601.""" + """Response body must contain 'timestamp' that is a valid ISO 8601 string.""" response = client.get("/hello") body = response.json() - # Will raise ValueError if the timestamp is not valid ISO format + assert "timestamp" in body + # datetime.fromisoformat will raise ValueError for non-ISO strings parsed = datetime.datetime.fromisoformat(body["timestamp"]) - assert parsed is not None + assert isinstance(parsed, datetime.datetime) def test_hello_response_timestamp_is_utc() -> None: - """GET /hello response timestamp should be UTC (offset +00:00).""" + """Parsed timestamp must be timezone-aware with UTC offset of zero.""" response = client.get("/hello") body = response.json() parsed = datetime.datetime.fromisoformat(body["timestamp"]) @@ -43,20 +56,26 @@ def test_hello_response_timestamp_is_utc() -> None: def test_hello_response_schema_keys() -> None: - """GET /hello response should contain exactly 'message' and 'timestamp' keys.""" + """Response JSON must contain exactly 'message' and 'timestamp' keys.""" response = client.get("/hello") body = response.json() assert set(body.keys()) == {"message", "timestamp"} def test_root_returns_200() -> None: - """GET / should return HTTP 200 with status ok.""" + """GET / health-check must return 200 with {'status': 'ok'}.""" response = client.get("/") assert response.status_code == 200 assert response.json() == {"status": "ok"} def test_hello_method_not_allowed() -> None: - """POST /hello should return HTTP 405 Method Not Allowed.""" + """POST /hello must return 405 Method Not Allowed.""" response = client.post("/hello") assert response.status_code == 405 + + +def test_nonexistent_route_returns_404() -> None: + """GET on a route that does not exist must return 404.""" + response = client.get("/nonexistent-route") + assert response.status_code == 404