From ee9d172aa51939bb4a39a7a4d1b7b6aecd911c33 Mon Sep 17 00:00:00 2001 From: FORGE Date: Tue, 7 Apr 2026 00:29:09 +0000 Subject: [PATCH 1/2] feat: Create index.html with Hello World and white background Run: c6e315ca-4cd5-4306-9426-cf1901601501 Task: 587e6c9a-5f19-42d3-bacc-677d7ff297f2 Agent: builder --- ARCHITECTURE.md | 43 +++++++++++ Dockerfile | 9 +++ RUNNING.md | 45 +++++++++++ docker-compose.yml | 6 ++ index.html | 22 ++++++ server.py | 36 +++++++++ tests/__init__.py | 0 tests/test_server.py | 180 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 341 insertions(+) create mode 100644 ARCHITECTURE.md create mode 100644 Dockerfile create mode 100644 RUNNING.md create mode 100644 docker-compose.yml create mode 100644 index.html create mode 100644 server.py create mode 100644 tests/__init__.py create mode 100644 tests/test_server.py diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..b9344ae --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,43 @@ +# Architecture + +## Overview + +This project is a single static HTML page served by Python's built-in +HTTP server. There are no frameworks, no databases, and no build steps. + +## File Structure + +| File | Purpose | +|-----------------------|----------------------------------------------------------------| +| `index.html` | Static HTML page displaying "Hello World" on a white background| +| `server.py` | Python HTTP server using `http.server` from the standard lib | +| `Dockerfile` | Container image definition (Python 3.12-slim base) | +| `docker-compose.yml` | One-command orchestration via Docker Compose | +| `RUNNING.md` | Instructions to build and run the application | +| `ARCHITECTURE.md` | This file – documents architecture and design decisions | +| `tests/test_server.py`| Automated tests verifying the server and HTML content | + +## Technology Choices + +* **Python 3 standard library** – `http.server.SimpleHTTPRequestHandler` + provides a zero-dependency static file server. +* **No framework** – the app serves a single static page; adding Flask, + FastAPI, or similar would be unnecessary overhead. +* **No database** – there is no dynamic data to persist. + +## How It Works + +1. `server.py` starts an HTTP server on port **8000**, bound to + `0.0.0.0`, serving files from the working directory. +2. When a browser requests `/`, the server returns `index.html`. +3. `index.html` uses minimal inline CSS to centre the text "Hello World" + both vertically and horizontally on a white (`#ffffff`) background. + +## Design Decisions + +* **Inline CSS** – a single ` + + +

Hello World

+ + diff --git a/server.py b/server.py new file mode 100644 index 0000000..3cb7de0 --- /dev/null +++ b/server.py @@ -0,0 +1,36 @@ +"""Lightweight Python HTTP server that serves static files from the current directory. + +Uses only the Python standard library. Designed to be run directly or +imported and started programmatically via :func:`run_server`. +""" + +from __future__ import annotations + +import functools +import http.server +import socketserver +from typing import Optional + + +def run_server(port: int = 8000, directory: str = ".") -> None: + """Start an HTTP server serving *directory* on *port*. + + The server binds to ``0.0.0.0`` so it is reachable from outside a + Docker container. + + Args: + port: TCP port to listen on. Defaults to ``8000``. + directory: Filesystem directory to serve. Defaults to ``"."``. + """ + handler = functools.partial( + http.server.SimpleHTTPRequestHandler, + directory=directory, + ) + + with socketserver.TCPServer(("0.0.0.0", port), handler) as httpd: + print(f"Serving on http://0.0.0.0:{port}") + httpd.serve_forever() + + +if __name__ == "__main__": + run_server() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_server.py b/tests/test_server.py new file mode 100644 index 0000000..de66286 --- /dev/null +++ b/tests/test_server.py @@ -0,0 +1,180 @@ +"""Automated tests for the Hello World static server. + +Tests verify that: +- The server starts and responds on the configured port. +- The response contains the expected HTML content. +- The background colour is set to #ffffff. +- The page title is 'Hello World'. +- The visible text 'Hello World' is present. +""" + +from __future__ import annotations + +import os +import socket +import threading +import time +import unittest +import urllib.request +from functools import partial +from http.server import HTTPServer, SimpleHTTPRequestHandler +from typing import Optional + + +def _find_free_port() -> int: + """Return an available TCP port on localhost.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + return s.getsockname()[1] + + +class TestHelloWorldServer(unittest.TestCase): + """Integration tests that spin up the static file server and issue HTTP requests.""" + + server: Optional[HTTPServer] = None + thread: Optional[threading.Thread] = None + port: int = 0 + + @classmethod + def setUpClass(cls) -> None: + """Start an HTTP server in a background thread before any tests run.""" + cls.port = _find_free_port() + + # Serve from the project root where index.html lives + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + handler = partial(SimpleHTTPRequestHandler, directory=project_root) + + cls.server = HTTPServer(("127.0.0.1", cls.port), handler) + cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True) + cls.thread.start() + + # Give the server a moment to start accepting connections + time.sleep(0.3) + + @classmethod + def tearDownClass(cls) -> None: + """Shut down the HTTP server after all tests complete.""" + if cls.server is not None: + cls.server.shutdown() + + def _get(self, path: str = "/") -> str: + """Perform an HTTP GET and return the decoded response body. + + Args: + path: URL path to request. Defaults to ``"/"``. + + Returns: + The response body as a UTF-8 string. + """ + url = f"http://127.0.0.1:{self.port}{path}" + with urllib.request.urlopen(url, timeout=5) as resp: + return resp.read().decode("utf-8") + + # ------------------------------------------------------------------ + # Tests + # ------------------------------------------------------------------ + + def test_server_responds_with_200(self) -> None: + """The server should respond with HTTP 200 for the root path.""" + url = f"http://127.0.0.1:{self.port}/" + with urllib.request.urlopen(url, timeout=5) as resp: + self.assertEqual(resp.status, 200) + + def test_response_contains_hello_world_heading(self) -> None: + """The page body must contain an

with 'Hello World'.""" + body = self._get("/") + self.assertIn("

Hello World

", body) + + def test_page_title_is_hello_world(self) -> None: + """The element must be 'Hello World'.""" + body = self._get("/") + self.assertIn("<title>Hello World", body) + + def test_background_color_is_white(self) -> None: + """The CSS must set background-color to #ffffff.""" + body = self._get("/") + self.assertIn("background-color: #ffffff", body) + + def test_html5_doctype(self) -> None: + """The page must start with an HTML5 doctype declaration.""" + body = self._get("/") + self.assertTrue(body.strip().startswith("")) + + def test_flexbox_centering(self) -> None: + """The CSS must use flexbox for centering content.""" + body = self._get("/") + self.assertIn("display: flex", body) + self.assertIn("justify-content: center", body) + self.assertIn("align-items: center", body) + + def test_viewport_meta_tag(self) -> None: + """The page should include a viewport meta tag for responsiveness.""" + body = self._get("/") + self.assertIn('name="viewport"', body) + + def test_content_type_is_html(self) -> None: + """The Content-Type header should indicate HTML.""" + url = f"http://127.0.0.1:{self.port}/" + with urllib.request.urlopen(url, timeout=5) as resp: + content_type = resp.headers.get("Content-Type", "") + self.assertIn("text/html", content_type) + + +class TestIndexHtmlFile(unittest.TestCase): + """Unit tests that verify index.html exists and has correct structure.""" + + def setUp(self) -> None: + """Load index.html content from the project root.""" + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + self.index_path = os.path.join(project_root, "index.html") + + def test_index_html_exists(self) -> None: + """index.html must exist in the project root.""" + self.assertTrue( + os.path.isfile(self.index_path), + f"index.html not found at {self.index_path}", + ) + + def test_index_html_is_not_empty(self) -> None: + """index.html must not be an empty file.""" + size = os.path.getsize(self.index_path) + self.assertGreater(size, 0, "index.html is empty") + + def test_index_html_contains_lang_attribute(self) -> None: + """The tag should have a lang attribute.""" + with open(self.index_path, encoding="utf-8") as f: + content = f.read() + self.assertIn('lang="en"', content) + + +class TestServerModule(unittest.TestCase): + """Unit tests for the server.py module.""" + + def test_server_module_importable(self) -> None: + """server.py must be importable without starting a server.""" + import importlib + import sys + + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + if project_root not in sys.path: + sys.path.insert(0, project_root) + + # Importing should not block because serve_forever is behind __main__ guard + module = importlib.import_module("server") + self.assertTrue(hasattr(module, "run_server")) + + def test_run_server_is_callable(self) -> None: + """run_server must be a callable function.""" + import importlib + import sys + + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + if project_root not in sys.path: + sys.path.insert(0, project_root) + + module = importlib.import_module("server") + self.assertTrue(callable(module.run_server)) + + +if __name__ == "__main__": + unittest.main() From 9327c66d25851faa00d6dc2e517388a91cfa123c Mon Sep 17 00:00:00 2001 From: FORGE Date: Tue, 7 Apr 2026 00:30:11 +0000 Subject: [PATCH 2/2] feat: Create Python server and RUNNING.md Run: c6e315ca-4cd5-4306-9426-cf1901601501 Task: dbd4c4e3-5a99-4328-9a42-0402e98dbc1a Agent: builder --- ARCHITECTURE.md | 63 +++++---- Dockerfile | 2 +- RUNNING.md | 29 +++- server.py | 3 + tests/test_server.py | 312 ++++++++++++++++++++++++------------------- 5 files changed, 236 insertions(+), 173 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b9344ae..b0c5b01 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,42 +2,51 @@ ## Overview -This project is a single static HTML page served by Python's built-in -HTTP server. There are no frameworks, no databases, and no build steps. +This is a minimal "Hello World" web application consisting of a single +static HTML page served by Python's built-in HTTP server. No external +frameworks, databases, or build tools are required. ## File Structure -| File | Purpose | -|-----------------------|----------------------------------------------------------------| -| `index.html` | Static HTML page displaying "Hello World" on a white background| -| `server.py` | Python HTTP server using `http.server` from the standard lib | -| `Dockerfile` | Container image definition (Python 3.12-slim base) | -| `docker-compose.yml` | One-command orchestration via Docker Compose | -| `RUNNING.md` | Instructions to build and run the application | -| `ARCHITECTURE.md` | This file – documents architecture and design decisions | -| `tests/test_server.py`| Automated tests verifying the server and HTML content | +``` +. +├── index.html # Static HTML page displaying "Hello World" +├── server.py # Python HTTP server (stdlib only) +├── Dockerfile # Container image definition +├── docker-compose.yml # One-command container orchestration +├── RUNNING.md # Instructions for running the application +├── ARCHITECTURE.md # This file – architecture documentation +└── tests/ + └── test_server.py # Automated tests for the server +``` ## Technology Choices -* **Python 3 standard library** – `http.server.SimpleHTTPRequestHandler` - provides a zero-dependency static file server. -* **No framework** – the app serves a single static page; adding Flask, - FastAPI, or similar would be unnecessary overhead. -* **No database** – there is no dynamic data to persist. +| Component | Choice | Rationale | +| -------------- | --------------------------------------- | ------------------------------------------------- | +| Web server | `http.server` (Python standard library) | Zero dependencies; ships with every Python 3 install | +| Containerisation | Docker + Docker Compose | Reproducible environment; single-command startup | +| Frontend | Plain HTML + inline CSS | No build step needed for a single static page | +| Testing | `unittest` (Python standard library) | No extra test runner required | ## How It Works -1. `server.py` starts an HTTP server on port **8000**, bound to - `0.0.0.0`, serving files from the working directory. -2. When a browser requests `/`, the server returns `index.html`. -3. `index.html` uses minimal inline CSS to centre the text "Hello World" - both vertically and horizontally on a white (`#ffffff`) background. +1. `server.py` starts a `TCPServer` on `0.0.0.0:8000` using + `SimpleHTTPRequestHandler` pointed at the current working directory. +2. When a browser requests `/`, the handler automatically serves + `index.html` as the directory index. +3. `index.html` renders a centred "Hello World" heading on a white + background. ## Design Decisions -* **Inline CSS** – a single `