From e7508031a910fc1960dec0ee6889502e31cdd4b0 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 20:54:16 -0500 Subject: [PATCH 01/12] docs: add resource management API feature spec --- .idea/.gitignore | 0 .../inspectionProfiles/profiles_settings.xml | 6 +++ .idea/modules.xml | 8 +++ .idea/spec-driven-development.iml | 8 +++ .idea/vcs.xml | 6 +++ .idea/workspace.xml | 52 +++++++++++++++++++ SPECS/feature-template.md | 14 ----- SPECS/resource-managment-api.md | 33 ++++++++++++ 8 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/spec-driven-development.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml delete mode 100644 SPECS/feature-template.md create mode 100644 SPECS/resource-managment-api.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..4bc28042 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/spec-driven-development.iml b/.idea/spec-driven-development.iml new file mode 100644 index 00000000..d0876a78 --- /dev/null +++ b/.idea/spec-driven-development.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..73caaefe --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,52 @@ + + + + + + + + + + { + "associatedIndex": 8 +} + + + + + + + + + + + + + + 1770687309735 + + + + \ No newline at end of file diff --git a/SPECS/feature-template.md b/SPECS/feature-template.md deleted file mode 100644 index 7dbc70a5..00000000 --- a/SPECS/feature-template.md +++ /dev/null @@ -1,14 +0,0 @@ -# Feature Spec: - -## Goal -- - -## Scope -- In: -- Out: - -## Requirements -- - -## Acceptance Criteria -- [ ] \ No newline at end of file diff --git a/SPECS/resource-managment-api.md b/SPECS/resource-managment-api.md new file mode 100644 index 00000000..2606755b --- /dev/null +++ b/SPECS/resource-managment-api.md @@ -0,0 +1,33 @@ +# Feature Spec: Resource Management API + +## Goal +Provide a simple backend API to create, retrieve, and filter resources in a spec-driven manner, +serving as a test target for API and contract testing. + +## Scope +### In: +- Create a resource via API +- Retrieve all resources +- Filter resources by type +- Validate request and response schemas + +### Out: +- Authentication / authorization +- External persistence (DB, cloud services) +- Frontend UI + +## Requirements +- The API must support creating a resource with required fields. +- Each resource must be assigned a unique ID. +- Resources must be retrievable via a GET endpoint. +- Filtering by resource type must be supported. +- Invalid requests must return clear error responses. +- API responses must conform to a stable schema. + +## Acceptance Criteria +- [ ] POST /resources creates a resource with valid input +- [ ] GET /resources returns all created resources +- [ ] GET /resources?type= filters resources correctly +- [ ] Missing required fields return a 400 error +- [ ] Invalid resource type returns a validation error +- [ ] Response schema remains consistent across requests From 508194261b2b8f5a44b12c2a726e6e78f97b5be5 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 21:08:48 -0500 Subject: [PATCH 02/12] Min FastAPi application --- .gitignore | 3 +++ app/main.py | 8 ++++++++ requirements.txt | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 app/main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index e69de29b..5cd30241 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +.venv/ +.pytest_cache/ diff --git a/app/main.py b/app/main.py new file mode 100644 index 00000000..38a1881d --- /dev/null +++ b/app/main.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(title="Resource Management API") + + +@app.get("/health") +def health_check(): + return {"status": "ok"} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c39f3d71 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn +pytest From d48b0a6e27c680a4f35f5bfe373d2c5cb7d1d344 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 21:53:09 -0500 Subject: [PATCH 03/12] add resources creation and retrieval endpoints, left heath end point from scafold --- app/main.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index 38a1881d..d542a89d 100644 --- a/app/main.py +++ b/app/main.py @@ -1,8 +1,49 @@ -from fastapi import FastAPI +from __future__ import annotations + +from typing import Optional +from uuid import uuid4 + +from fastapi import FastAPI, Query +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from pydantic import BaseModel app = FastAPI(title="Resource Management API") +# In-memory storage: id -> resource +resources: dict[str, "Resource"] = {} + + +class ResourceIn(BaseModel): + name: str + type: str + + +class Resource(ResourceIn): + id: str + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request, exc): + return JSONResponse(status_code=400, content={"detail": "Invalid input"}) + + +@app.post("/resources", response_model=Resource) +def create_resource(payload: ResourceIn) -> Resource: + resource = Resource(id=str(uuid4()), **payload.dict()) + resources[resource.id] = resource + return resource + + +@app.get("/resources", response_model=list[Resource]) +def list_resources(type: Optional[str] = Query(default=None)) -> list[Resource]: + items = list(resources.values()) + if type is not None: + items = [r for r in items if r.type == type] + return items @app.get("/health") -def health_check(): +def health(): return {"status": "ok"} + + From 43ddec3a4508c536a1d42e55008c688d2be5de20 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 22:14:47 -0500 Subject: [PATCH 04/12] add pytest setup and initial API tests --- app/__init__.py | 0 pytest.ini | 3 +++ requirements.txt | 1 + tests/conftest.py | 6 ++++++ tests/test_resources_api.py | 20 ++++++++++++++++++++ 5 files changed, 30 insertions(+) create mode 100644 app/__init__.py create mode 100644 pytest.ini create mode 100644 tests/conftest.py create mode 100644 tests/test_resources_api.py diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..27eec68e --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tests +python_files = test_*.py diff --git a/requirements.txt b/requirements.txt index c39f3d71..2522ad0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ fastapi uvicorn pytest +httpx diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..9e20faf2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,6 @@ +import sys +from pathlib import Path + +# Add project root to PYTHONPATH +ROOT_DIR = Path(__file__).resolve().parents[1] +sys.path.append(str(ROOT_DIR)) diff --git a/tests/test_resources_api.py b/tests/test_resources_api.py new file mode 100644 index 00000000..27815c34 --- /dev/null +++ b/tests/test_resources_api.py @@ -0,0 +1,20 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +def test_create_resource_success(): + response = client.post( + "/resources", + json={"name": "test-resource", "type": "demo"}, + ) + + assert response.status_code == 200 + body = response.json() + assert "id" in body + assert body["name"] == "test-resource" + assert body["type"] == "demo" + +def test_sanity(): + assert True From d7053a65def3db9d594f8cd94a04380a9161e5ca Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 22:28:20 -0500 Subject: [PATCH 05/12] add spec-driven API tests for resource management app --- tests/test_resources_api.py | 72 ++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/tests/test_resources_api.py b/tests/test_resources_api.py index 27815c34..1a1b7f51 100644 --- a/tests/test_resources_api.py +++ b/tests/test_resources_api.py @@ -1,20 +1,82 @@ from fastapi.testclient import TestClient -from app.main import app + +from app.main import app, resources client = TestClient(app) +def setup_function(): + # Ensure deterministic tests by clearing in-memory storage + resources.clear() + + def test_create_resource_success(): response = client.post( "/resources", - json={"name": "test-resource", "type": "demo"}, + json={"name": "resource-1", "type": "demo"}, ) assert response.status_code == 200 body = response.json() + assert "id" in body - assert body["name"] == "test-resource" + assert body["name"] == "resource-1" assert body["type"] == "demo" -def test_sanity(): - assert True + +def test_list_resources_returns_created_items(): + client.post("/resources", json={"name": "r1", "type": "demo"}) + client.post("/resources", json={"name": "r2", "type": "test"}) + + response = client.get("/resources") + + assert response.status_code == 200 + body = response.json() + + assert len(body) == 2 + names = [item["name"] for item in body] + assert "r1" in names + assert "r2" in names + + +def test_filter_resources_by_type(): + client.post("/resources", json={"name": "r1", "type": "demo"}) + client.post("/resources", json={"name": "r2", "type": "test"}) + + response = client.get("/resources", params={"type": "demo"}) + + assert response.status_code == 200 + body = response.json() + + assert len(body) == 1 + assert body[0]["name"] == "r1" + assert body[0]["type"] == "demo" + + +def test_create_resource_missing_required_fields_returns_400(): + response = client.post("/resources", json={}) + + assert response.status_code == 400 + assert response.json() == {"detail": "Invalid input"} + + +def test_create_resource_invalid_payload_returns_400(): + response = client.post( + "/resources", + json={"name": 123, "type": ["not", "a", "string"]}, + ) + + assert response.status_code == 400 + assert response.json() == {"detail": "Invalid input"} + + +def test_response_schema_contains_expected_fields(): + response = client.post( + "/resources", + json={"name": "schema-test", "type": "demo"}, + ) + + body = response.json() + + assert set(body.keys()) == {"id", "name", "type"} + From b85d583bfd8516053e5f856097e6ed7727bc0949 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 22:36:00 -0500 Subject: [PATCH 06/12] add edge case for empty resources list --- tests/test_resources_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_resources_api.py b/tests/test_resources_api.py index 1a1b7f51..b67fff3d 100644 --- a/tests/test_resources_api.py +++ b/tests/test_resources_api.py @@ -80,3 +80,10 @@ def test_response_schema_contains_expected_fields(): assert set(body.keys()) == {"id", "name", "type"} +def test_list_resources_returns_empty_list_when_no_resources_exist(): + response = client.get("/resources") + + assert response.status_code == 200 + assert response.json() == [] + + From 36be9f643b3a761dd70a5216564495884ebd64c4 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 22:37:54 -0500 Subject: [PATCH 07/12] update TODO after implementing resource API feature --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index b5d82042..764a54e5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ # TODO ## Refactor Proposals -- +- None at this time ## New Feature Proposals -- \ No newline at end of file +- None at this time From 15b164b252f7dfe0f4e1443b7463140131cc2e24 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 23:10:56 -0500 Subject: [PATCH 08/12] add complete local setup,run,test instructions and code generation usage info --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index 494f1c75..3930fb94 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,50 @@ Your solution should include at least one real workflow, for example: - When you are complete, put up a Pull Request against this repository with your changes. - A short summary of your approach and tools used in your PR submission - Any additional information or approach that helped you. + +++++++++++++++++++++++++++++++++++++++++++++++ + +## Local Setup and Usage + +### Clone the Repository +```bash +git clone https://github.com/automationExamples/spec-driven-development.git +cd spec-driven-development +``` + +### Create and Activate Virtual Environment (Python) +```bash +python3 -m venv .venv +source .venv/bin/activate +``` +### Install Dependencies +```bash +pip install -r requirements.txt +``` +### Run the Application +Start the FastAPI server locally: +```bash +uvicorn app.main:app --reload +``` +Verify the API is running by opening: +```bash +http://127.0.0.1:8000/docs +``` + +### You can also manually verify the API: +```bash +curl -X POST http://127.0.0.1:8000/resources \ + -H "Content-Type: application/json" \ + -d '{"name":"example","type":"demo"}' +``` +### Run Tests +Execute the full test suite: +```bash +pytest +``` + +## Code Generation Usage +Modern code generation tools (ChatGPT / Codex-style models) were used to +accelerate scaffolding of the API and test cases. All generated code was +reviewed and refined manually to ensure correctness, determinism, and +coverage of edge cases. From 7e418bc66f5acab0b07b24d1a1c565915ee44574 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 23:19:31 -0500 Subject: [PATCH 09/12] add informative loggin to API tests and update README.md --- README.md | 7 ++++++- tests/test_resources_api.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3930fb94..a190d4d0 100644 --- a/README.md +++ b/README.md @@ -77,11 +77,16 @@ curl -X POST http://127.0.0.1:8000/resources \ -H "Content-Type: application/json" \ -d '{"name":"example","type":"demo"}' ``` -### Run Tests +### Run Tests (without readable logs) Execute the full test suite: ```bash pytest ``` +### Run Tests (with readable logs) +Execute the full test suite: +```bash +pytest --log-cli-level=INFO +``` ## Code Generation Usage Modern code generation tools (ChatGPT / Codex-style models) were used to diff --git a/tests/test_resources_api.py b/tests/test_resources_api.py index b67fff3d..58dc98b9 100644 --- a/tests/test_resources_api.py +++ b/tests/test_resources_api.py @@ -1,3 +1,7 @@ +import logging + +logger = logging.getLogger(__name__) + from fastapi.testclient import TestClient from app.main import app, resources @@ -11,11 +15,15 @@ def setup_function(): def test_create_resource_success(): + logger.info("Creating resource with valid payload") + response = client.post( "/resources", json={"name": "resource-1", "type": "demo"}, ) + logger.info("Create response status=%s body=%s", response.status_code, response.json()) + assert response.status_code == 200 body = response.json() @@ -25,10 +33,15 @@ def test_create_resource_success(): def test_list_resources_returns_created_items(): + logger.info("Creating multiple resources for list test") + client.post("/resources", json={"name": "r1", "type": "demo"}) client.post("/resources", json={"name": "r2", "type": "test"}) + logger.info("Fetching all resources") + response = client.get("/resources") + logger.info("List response status=%s body=%s", response.status_code, response.json()) assert response.status_code == 200 body = response.json() @@ -40,10 +53,15 @@ def test_list_resources_returns_created_items(): def test_filter_resources_by_type(): + logger.info("Creating resources with different types") + client.post("/resources", json={"name": "r1", "type": "demo"}) client.post("/resources", json={"name": "r2", "type": "test"}) + logger.info("Filtering resources by type=demo") + response = client.get("/resources", params={"type": "demo"}) + logger.info("Filter response status=%s body=%s", response.status_code, response.json()) assert response.status_code == 200 body = response.json() @@ -54,36 +72,48 @@ def test_filter_resources_by_type(): def test_create_resource_missing_required_fields_returns_400(): + logger.info("Creating resource with missing required fields") + response = client.post("/resources", json={}) + logger.info("Validation response status=%s body=%s", response.status_code, response.json()) assert response.status_code == 400 assert response.json() == {"detail": "Invalid input"} def test_create_resource_invalid_payload_returns_400(): + logger.info("Creating resource with invalid payload types") + response = client.post( "/resources", json={"name": 123, "type": ["not", "a", "string"]}, ) + logger.info("Invalid payload response status=%s body=%s", response.status_code, response.json()) + assert response.status_code == 400 assert response.json() == {"detail": "Invalid input"} def test_response_schema_contains_expected_fields(): + logger.info("Validating response schema for created resource") + response = client.post( "/resources", json={"name": "schema-test", "type": "demo"}, ) body = response.json() + logger.info("Schema validation response body=%s", body) assert set(body.keys()) == {"id", "name", "type"} + def test_list_resources_returns_empty_list_when_no_resources_exist(): + logger.info("Listing resources when no resources exist") + response = client.get("/resources") + logger.info("Empty list response status=%s body=%s", response.status_code, response.json()) assert response.status_code == 200 assert response.json() == [] - - From 42fcf7259e43e46a0a891e1cad878f2ff886ca09 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 23:30:39 -0500 Subject: [PATCH 10/12] Update Local setup and Usage section --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a190d4d0..1d42a4c0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ Your solution should include at least one real workflow, for example: git clone https://github.com/automationExamples/spec-driven-development.git cd spec-driven-development ``` +If you are reviewing this work via a pull request, check out the feature branch: +```bash +git checkout resource-management-api +``` ### Create and Activate Virtual Environment (Python) ```bash From daf2607f113c40b3acedb9328ca46b3fa0e0ad91 Mon Sep 17 00:00:00 2001 From: JEEVA Date: Mon, 9 Feb 2026 23:33:25 -0500 Subject: [PATCH 11/12] ignore IDE-specific files --- .gitignore | 3 ++ .idea/.gitignore | 0 .../inspectionProfiles/profiles_settings.xml | 6 --- .idea/modules.xml | 8 --- .idea/spec-driven-development.iml | 8 --- .idea/vcs.xml | 6 --- .idea/workspace.xml | 52 ------------------- 7 files changed, 3 insertions(+), 80 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/spec-driven-development.iml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.gitignore b/.gitignore index 5cd30241..1c97ed29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ __pycache__/ .venv/ .pytest_cache/ + +# IDE files +.idea/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4bc28042..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/spec-driven-development.iml b/.idea/spec-driven-development.iml deleted file mode 100644 index d0876a78..00000000 --- a/.idea/spec-driven-development.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 73caaefe..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - { - "associatedIndex": 8 -} - - - - - - - - - - - - - - 1770687309735 - - - - \ No newline at end of file From 85045c33cbc7ca766211b5aca0996629739303ac Mon Sep 17 00:00:00 2001 From: JEEVA Date: Tue, 10 Feb 2026 00:08:16 -0500 Subject: [PATCH 12/12] Add Local Setup and Usages changes to READ.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1d42a4c0..6c217209 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,9 @@ Your solution should include at least one real workflow, for example: ### Clone the Repository ```bash -git clone https://github.com/automationExamples/spec-driven-development.git -cd spec-driven-development +https://github.com/jeevan-puli/spec-driven-development/tree/resource-management-api ``` -If you are reviewing this work via a pull request, check out the feature branch: +check out the feature branch: ```bash git checkout resource-management-api ```