Story¶
As an operator, I want to deploy a rover at a starting position and heading, so that it is ready to receive and execute commands.
Architecture Reference: 05-building-blocks.md — Rover, Heading domain entities; 01-introduction.md — FR-2
As an operator, I want to deploy a rover at a starting position and heading, so that it is ready to receive and execute commands.
Architecture Reference: 05-building-blocks.md — Rover, Heading domain entities; 01-introduction.md — FR-2
Scenario ID: ROVER-STORY-001-S1
GIVEN
Starting position 1 2 N is provided
WHEN
A Rover is constructed
THEN
rover.x == 1, rover.y == 2, rover.heading == Heading.N
Scenario ID: ROVER-STORY-001-S2
GIVEN
Any valid heading (N, E, S, W) is provided
WHEN
A Rover is constructed
THEN
The heading is stored correctly and readable
Scenario ID: ROVER-STORY-001-S3
GIVEN
A Rover(1, 2, Heading.N) has been created
WHEN
Its state is inspected
THEN
x, y, and heading are all accessible
Story ID: ROVER-BE-001.1
As a developer I want a mutable Rover entity and an immutable Heading enum so that rover state accumulates correctly as commands are applied.
Architecture Reference: 05-building-blocks.md — Rover, Heading; 04-solution-strategy.md — Command pattern
Scenarios:
Scenario ID: ROVER-BE-001.1-S1
GIVEN
Heading.N exists
WHEN
turn_left() is called
THEN
Returns Heading.W and the original Heading.N is unchanged
Scenario ID: ROVER-BE-001.1-S2
GIVEN
Starting heading Heading.N
WHEN
turn_left() is called four times in sequence
THEN
The final heading is Heading.N
mars_rover/domain/heading.py¶from enum import Enum
class Heading(Enum):
N = "N"
E = "E"
S = "S"
W = "W"
def turn_left(self) -> "Heading":
order = [Heading.N, Heading.W, Heading.S, Heading.E]
return order[(order.index(self) + 1) % 4]
def turn_right(self) -> "Heading":
order = [Heading.N, Heading.E, Heading.S, Heading.W]
return order[(order.index(self) + 1) % 4]
def delta(self) -> tuple[int, int]:
"""Returns (dx, dy) for one forward step in this heading."""
return {
Heading.N: (0, 1),
Heading.E: (1, 0),
Heading.S: (0, -1),
Heading.W: (-1, 0),
}[self]
mars_rover/domain/rover.py¶from dataclasses import dataclass
from mars_rover.domain.heading import Heading
@dataclass
class Rover:
"""Mutable entity. Accumulates state as commands are applied."""
x: int
y: int
heading: Heading
def execute(self, command) -> None:
command(self)
Design notes:
Rover is intentionally mutable (models a physical vehicle moving through space — see 08-cross-cutting-concepts.md)
execute accepts any callable that takes a Rover — this is the Command pattern (ADR / 04-solution-strategy.md)
Heading is an immutable enum; rotation methods return new values, never mutate
tests/domain/test_rover.py¶from mars_rover.domain.heading import Heading
from mars_rover.domain.rover import Rover
def test_rover_initial_state():
rover = Rover(1, 2, Heading.N)
assert rover.x == 1
assert rover.y == 2
assert rover.heading == Heading.N
def test_rover_all_headings():
for heading in Heading:
rover = Rover(0, 0, heading)
assert rover.heading == heading
tests/domain/test_heading.py¶from mars_rover.domain.heading import Heading
def test_turn_left_from_north():
assert Heading.N.turn_left() == Heading.W
def test_turn_left_full_circle():
h = Heading.N
for _ in range(4):
h = h.turn_left()
assert h == Heading.N
def test_turn_right_from_north():
assert Heading.N.turn_right() == Heading.E
def test_turn_right_full_circle():
h = Heading.N
for _ in range(4):
h = h.turn_right()
assert h == Heading.N
def test_delta_north():
assert Heading.N.delta() == (0, 1)
def test_delta_east():
assert Heading.E.delta() == (1, 0)
def test_delta_south():
assert Heading.S.delta() == (0, -1)
def test_delta_west():
assert Heading.W.delta() == (-1, 0)
Story ID: ROVER-FE-001.1
As an operator I want to specify rover deployment via plain-text stdin so that I can position a rover without a GUI.
Architecture Reference: 03-context.md — Operator → System interface
Scenarios:
Scenario ID: ROVER-FE-001.1-S1
GIVEN
The plateau line 5 5 has been read
WHEN
The next line is 1 2 N
THEN
The system creates Rover(1, 2, Heading.N) ready for commands
Rover deployment is expressed via the stdin text format 1 2 N. Parsing is covered in CLI-STORY-001. Display of the deployed rover’s final state is covered in CLI-STORY-002.
Story ID: ROVER-INFRA-001.1
As a developer I want the rover deployment functionality to be containerized and testable so that the application can be built, tested, and deployed consistently across environments.
Architecture Reference: 07-deployment.md — deployment topology; 02-constraints.md — TC-1
Scenario ID: ROVER-INFRA-001.1-S1
GIVEN
The Dockerfile exists in the project root
The mars_rover/domain/ directory contains rover.py and heading.py
WHEN
docker build -t mars-rover . is executed
THEN
The build completes successfully with exit code 0
The container includes the rover domain code at /app/mars_rover/domain/
No build errors or warnings are reported
Scenario ID: ROVER-INFRA-001.1-S2
GIVEN
The Docker image has been built successfully
Test files exist in tests/domain/test_rover.py and tests/domain/test_heading.py
WHEN
docker run --rm mars-rover pytest tests/domain/ -v is executed
THEN
All rover and heading tests pass
Test output shows successful validation of rover initialization and heading rotation
Container exits with code 0
Scenario ID: ROVER-INFRA-001.1-S3
GIVEN
The requirements.txt file lists all necessary dependencies
The Dockerfile includes dependency installation steps
WHEN
The Docker build process installs dependencies
THEN
All required packages (pytest, black, isort, ruff) are available in the container
No dependency conflicts or missing packages are reported
The Python environment is properly configured with PYTHONPATH=/app
Scenario ID: ROVER-INFRA-001.1-S4
GIVEN
The project follows Python package structure with __init__.py files
Test files are organized in the tests/ directory
WHEN
pytest is run inside the container without specific file paths
THEN
pytest automatically discovers all test files in tests/domain/
All rover-related tests are executed
Test coverage includes rover initialization and heading functionality
Heading enum implemented in mars_rover/domain/heading.py
Rover dataclass implemented in mars_rover/domain/rover.py
All heading rotation and delta tests pass
All rover initialisation tests pass
Dockerfile builds successfully without errors
Test suite runs inside Docker container and passes
Dependencies are correctly installed in container
pytest discovers and runs all domain tests
ruff, black, and isort pass with no warnings
No imports from adapters/ or application/ inside domain/