ROVER-STORY-001 — Deploy a Rover

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.mdRover, Heading domain entities; 01-introduction.md — FR-2


Scenarios

SCENARIO 1: Rover is created with valid position and heading

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 2: All cardinal headings are accepted

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 3: Rover state is readable after creation

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


Backend Sub-Story

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.mdRover, Heading; 04-solution-strategy.md — Command pattern

Scenarios:

SCENARIO 1: Heading rotation returns new value without mutation

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 2: Full left-rotation circle returns to origin heading

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


Domain model — 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]

Domain model — 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

Unit tests — 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

Unit tests — 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)

Frontend Sub-Story

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 1: Rover deployment line follows plateau line

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.


Infrastructure Sub-Story

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 1: Dockerfile builds successfully with rover domain code

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 2: Test suite runs inside Docker container and validates rover functionality

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 3: Dependencies are installed correctly in container

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 4: Project structure supports pytest discovery

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


Definition of Done

  • 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/