From c12d28f48ee810b8bf2ffaa4a23ad0d762cd60a6 Mon Sep 17 00:00:00 2001 From: FORGE Date: Sat, 11 Apr 2026 01:01:53 +0000 Subject: [PATCH 1/2] feat: Create FastAPI App and Requirements Run: 1ca57c11-43d8-483a-9d51-4a709270786b Task: 46556d80-5feb-4f5f-9f37-2422430479b4 Agent: builder --- Dockerfile | 18 +++++++++++++++ RUNNING.md | 53 +++++++++++++++++++++++++++++++++++---------- app/__init__.py | 0 app/main.py | 24 ++++++++++++++++++++ docker-compose.yml | 8 +++++++ tests/test_hello.py | 39 +++++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 Dockerfile create mode 100644 app/__init__.py create mode 100644 app/main.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..9bb0df0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11-slim + +WORKDIR /code + +COPY requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY app/ /code/app/ +COPY main.py /code/main.py +COPY routes.py /code/routes.py +COPY models.py /code/models.py +COPY storage.py /code/storage.py +COPY conftest.py /code/conftest.py + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/RUNNING.md b/RUNNING.md index 77896cf..57f74f0 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,33 +1,64 @@ -# Running the Todo API +# Running the Application + +## TEAM_BRIEF +stack: Python/FastAPI +test_runner: pytest tests/ +lint_tool: ruff check . +coverage_tool: pytest-cov +coverage_threshold: 70 +coverage_applies: true ## Prerequisites -- Python 3.10 or later +- Python 3.11+ +- Docker and Docker Compose (for containerised execution) + +## Local Development -## Install dependencies +### 1. Install dependencies ```bash -pip install fastapi uvicorn pydantic +pip install -r requirements.txt ``` -For running the test suite you will also need: +### 2. Run the application ```bash -pip install httpx pytest +uvicorn app.main:app --host 0.0.0.0 --port 8000 ``` -## Start the server +### 3. Access the API + +Open your browser or use curl: ```bash -uvicorn main:app --reload --host 0.0.0.0 --port 8000 +curl http://localhost:8000/hello ``` -The API will be available at . +Expected response: -Interactive docs are served at . +```json +{"message": "Hello, World!"} +``` -## Run the tests +### 4. Run tests ```bash pytest tests/ ``` + +## Docker + +### Build and run with Docker Compose + +```bash +docker compose up --build +``` + +The application will be accessible at `http://localhost:8000/hello`. + +### Stop the service + +```bash +docker compose down +``` diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..cd05692 --- /dev/null +++ b/app/main.py @@ -0,0 +1,24 @@ +"""FastAPI application entry point. + +Creates the FastAPI app instance and defines the GET /hello endpoint. +""" + +from __future__ import annotations + +from fastapi import FastAPI + +app = FastAPI( + title="Hello World API", + description="A minimal FastAPI application with a /hello endpoint.", + version="1.0.0", +) + + +@app.get("/hello", tags=["hello"]) +async def hello() -> dict: + """Return a JSON greeting message. + + Returns: + A dictionary with a single 'message' key. + """ + return {"message": "Hello, World!"} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c221ee1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.9" + +services: + app: + build: . + ports: + - "8000:8000" + restart: unless-stopped diff --git a/tests/test_hello.py b/tests/test_hello.py new file mode 100644 index 0000000..44665ef --- /dev/null +++ b/tests/test_hello.py @@ -0,0 +1,39 @@ +"""Tests for the GET /hello endpoint.""" + +from __future__ import annotations + +from fastapi.testclient import TestClient + +from app.main import app + +client = TestClient(app) + + +def test_hello_endpoint_returns_200() -> None: + """GET /hello should return HTTP 200.""" + response = client.get("/hello") + assert response.status_code == 200 + + +def test_hello_endpoint_returns_correct_json() -> None: + """GET /hello should return {'message': 'Hello, World!'}.""" + response = client.get("/hello") + assert response.json() == {"message": "Hello, World!"} + + +def test_hello_endpoint_content_type() -> None: + """GET /hello should return application/json content type.""" + response = client.get("/hello") + assert "application/json" in response.headers["content-type"] + + +def test_hello_endpoint_post_not_allowed() -> None: + """POST /hello should return HTTP 405 Method Not Allowed.""" + response = client.post("/hello") + assert response.status_code == 405 + + +def test_nonexistent_endpoint_returns_404() -> None: + """GET /nonexistent should return HTTP 404.""" + response = client.get("/nonexistent") + assert response.status_code == 404 From 77c4cc2548421ad889e0a4fe8cf734c777d05030 Mon Sep 17 00:00:00 2001 From: FORGE Date: Sat, 11 Apr 2026 01:02:37 +0000 Subject: [PATCH 2/2] feat: Write Complete Test Suite Run: 1ca57c11-43d8-483a-9d51-4a709270786b Task: b578bf2b-8e0c-41d3-ad10-e1ddca2c7374 Agent: builder --- Dockerfile | 12 ++--- QA.md | 22 ++++++++++ RUNNING.md | 54 ++++++++++++++++------- docker-compose.yml | 2 +- requirements.txt | 10 ++--- tests/test_hello.py | 105 ++++++++++++++++++++++++++++++++++---------- 6 files changed, 152 insertions(+), 53 deletions(-) create mode 100644 QA.md diff --git a/Dockerfile b/Dockerfile index 9bb0df0..d73e71a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,10 @@ FROM python:3.11-slim WORKDIR /code -COPY requirements.txt /code/requirements.txt +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -COPY app/ /code/app/ -COPY main.py /code/main.py -COPY routes.py /code/routes.py -COPY models.py /code/models.py -COPY storage.py /code/storage.py -COPY conftest.py /code/conftest.py +COPY app/ ./app/ EXPOSE 8000 diff --git a/QA.md b/QA.md new file mode 100644 index 0000000..84d97ba --- /dev/null +++ b/QA.md @@ -0,0 +1,22 @@ +app_type: web-api +coverage_applies: true +coverage_source: app +coverage_threshold: 70 +coverage_tool: pytest-cov +install_steps: +- pip install --upgrade pip +- pip install -r /tmp/forge-repos/hello-world-fastapi-1ca57c11/requirements.txt +lint_tool: ruff check . +notes: Verify that all tests in tests/ pass, code coverage for app/ is at least 70%, + and ruff reports no lint errors. +stack: Python/FastAPI +test_files: +- tests/__init__.py +- tests/conftest.py +- tests/test_hello.py +- tests/test_main.py +- tests/test_models.py +- tests/test_storage.py +- tests/test_todos.py +test_runner: pytest tests/ +workspace: /tmp/forge-repos/hello-world-fastapi-1ca57c11 diff --git a/RUNNING.md b/RUNNING.md index 57f74f0..b5fdd9c 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,4 +1,4 @@ -# Running the Application +# Hello World FastAPI Application ## TEAM_BRIEF stack: Python/FastAPI @@ -10,7 +10,7 @@ coverage_applies: true ## Prerequisites -- Python 3.11+ +- Python 3.11+ (for local development) - Docker and Docker Compose (for containerised execution) ## Local Development @@ -27,24 +27,18 @@ pip install -r requirements.txt uvicorn app.main:app --host 0.0.0.0 --port 8000 ``` -### 3. Access the API +The API will be available at: **http://localhost:8000/hello** -Open your browser or use curl: +### 3. Run the test suite ```bash -curl http://localhost:8000/hello -``` - -Expected response: - -```json -{"message": "Hello, World!"} +pytest tests/ ``` -### 4. Run tests +To run with verbose output: ```bash -pytest tests/ +pytest tests/ -v ``` ## Docker @@ -55,10 +49,40 @@ pytest tests/ docker compose up --build ``` -The application will be accessible at `http://localhost:8000/hello`. +The application will be accessible at: **http://localhost:8000/hello** -### Stop the service +To stop the application: ```bash docker compose down ``` + +### Build and run with Docker only + +```bash +docker build -t hello-api . +docker run -p 8000:8000 hello-api +``` + +## API Endpoints + +| Method | Path | Description | Response | +|--------|----------|--------------------------------------|-----------------------------------| +| GET | `/hello` | Returns a JSON greeting message | `{"message": "Hello, World!"}` | + +## Project Structure + +``` +. +├── app/ +│ ├── __init__.py +│ └── main.py # FastAPI application with /hello endpoint +├── tests/ +│ ├── __init__.py +│ └── test_hello.py # Comprehensive test suite +├── conftest.py # Root pytest configuration +├── requirements.txt # Python dependencies +├── Dockerfile # Container image definition +├── docker-compose.yml # Docker Compose orchestration +└── RUNNING.md # This file +``` diff --git a/docker-compose.yml b/docker-compose.yml index c221ee1..e6831fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.9" services: - app: + web: build: . ports: - "8000:8000" diff --git a/requirements.txt b/requirements.txt index 5a6ebf7..9490417 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ -fastapi>=0.100.0 -uvicorn>=0.23.0 -pydantic>=2.0.0 -pytest>=7.0.0 -pytest-timeout>=2.1.0 -httpx>=0.24.0 +fastapi>=0.110.0 +uvicorn>=0.29.0 +pytest>=8.0.0 +httpx>=0.27.0 diff --git a/tests/test_hello.py b/tests/test_hello.py index 44665ef..15d46a5 100644 --- a/tests/test_hello.py +++ b/tests/test_hello.py @@ -1,4 +1,9 @@ -"""Tests for the GET /hello endpoint.""" +"""Comprehensive test suite for the /hello endpoint. + +Verifies correct behaviour of the FastAPI application's GET /hello +endpoint, including status codes, response payloads, headers, and +error handling for unsupported methods and non-existent routes. +""" from __future__ import annotations @@ -6,34 +11,90 @@ from app.main import app -client = TestClient(app) +client: TestClient = TestClient(app) + + +class TestHelloEndpoint: + """Tests for the GET /hello endpoint.""" + + def test_hello_returns_200(self) -> None: + """GET /hello should return HTTP 200 status code.""" + response = client.get("/hello") + assert response.status_code == 200 + + def test_hello_returns_correct_json(self) -> None: + """GET /hello should return {'message': 'Hello, World!'}.""" + response = client.get("/hello") + assert response.json() == {"message": "Hello, World!"} + + def test_hello_content_type_is_json(self) -> None: + """GET /hello should return a JSON content-type header.""" + response = client.get("/hello") + assert "application/json" in response.headers["content-type"] + + def test_hello_message_key_present(self) -> None: + """GET /hello response must contain the 'message' key.""" + response = client.get("/hello") + data = response.json() + assert "message" in data + + def test_hello_message_value(self) -> None: + """GET /hello 'message' value must be exactly 'Hello, World!'.""" + response = client.get("/hello") + data = response.json() + assert data["message"] == "Hello, World!" + + def test_hello_response_has_single_key(self) -> None: + """GET /hello response should contain exactly one key.""" + response = client.get("/hello") + data = response.json() + assert len(data) == 1 + + +class TestHelloMethodNotAllowed: + """Tests verifying that unsupported HTTP methods return 405.""" + + def test_post_hello_returns_405(self) -> None: + """POST /hello should return HTTP 405 Method Not Allowed.""" + response = client.post("/hello") + assert response.status_code == 405 + def test_put_hello_returns_405(self) -> None: + """PUT /hello should return HTTP 405 Method Not Allowed.""" + response = client.put("/hello") + assert response.status_code == 405 -def test_hello_endpoint_returns_200() -> None: - """GET /hello should return HTTP 200.""" - response = client.get("/hello") - assert response.status_code == 200 + def test_delete_hello_returns_405(self) -> None: + """DELETE /hello should return HTTP 405 Method Not Allowed.""" + response = client.delete("/hello") + assert response.status_code == 405 + def test_patch_hello_returns_405(self) -> None: + """PATCH /hello should return HTTP 405 Method Not Allowed.""" + response = client.patch("/hello") + assert response.status_code == 405 -def test_hello_endpoint_returns_correct_json() -> None: - """GET /hello should return {'message': 'Hello, World!'}.""" - response = client.get("/hello") - assert response.json() == {"message": "Hello, World!"} +class TestNonExistentRoutes: + """Tests verifying that requests to unknown paths return 404.""" -def test_hello_endpoint_content_type() -> None: - """GET /hello should return application/json content type.""" - response = client.get("/hello") - assert "application/json" in response.headers["content-type"] + def test_unknown_path_returns_404(self) -> None: + """GET /nonexistent should return HTTP 404 Not Found.""" + response = client.get("/nonexistent") + assert response.status_code == 404 + def test_root_path_returns_404(self) -> None: + """GET / should return HTTP 404 when no root route is defined.""" + response = client.get("/") + assert response.status_code == 404 -def test_hello_endpoint_post_not_allowed() -> None: - """POST /hello should return HTTP 405 Method Not Allowed.""" - response = client.post("/hello") - assert response.status_code == 405 +class TestHelloIdempotency: + """Tests verifying that repeated calls return consistent results.""" -def test_nonexistent_endpoint_returns_404() -> None: - """GET /nonexistent should return HTTP 404.""" - response = client.get("/nonexistent") - assert response.status_code == 404 + def test_multiple_calls_return_same_result(self) -> None: + """Consecutive GET /hello calls should return identical responses.""" + response1 = client.get("/hello") + response2 = client.get("/hello") + assert response1.json() == response2.json() + assert response1.status_code == response2.status_code