Story
As a developer,
I want to add a new command type without modifying existing domain classes,
so that the system is open for extension and closed for modification.
Architecture Reference: 05-building-blocks.md — Command protocol; 04-solution-strategy.md — Command pattern; 10-quality-requirements.md — QS-6; 08-cross-cutting-concepts.md — open/closed principle
Backend Sub-Story
Story ID: NAV-BE-004.1
As a developer I want the Command pattern to enable adding new commands with zero changes to existing domain logic so that the open/closed principle is enforced by the architecture.
Architecture Reference: 05-building-blocks.md — Command protocol; 04-solution-strategy.md — Command pattern; 10-quality-requirements.md — QS-6
Scenarios:
SCENARIO 1: UTurn implements the Command protocol
Scenario ID: NAV-BE-004.1-S1
GIVEN
WHEN
THEN
SCENARIO 2: UTurn uses existing Heading rotation methods
Scenario ID: NAV-BE-004.1-S2
GIVEN
WHEN
THEN
New command — mars_rover/domain/commands.py (append only)
class UTurn:
"""Rotate the rover 180° in place. U = two consecutive right turns."""
def __call__(self, rover: "Rover") -> None:
rover.heading = rover.heading.turn_right().turn_right()
Command map update — mars_rover/application/mission_controller.py
The only change outside commands.py is registering the new letter in the command map inside MissionController:
# In MissionController.__init__ or wherever the map lives:
command_map = {
"L": TurnLeft(),
"R": TurnRight(),
"M": MoveForward(self._plateau),
"U": UTurn(), # ← only addition required
}
Unit tests — tests/domain/test_extensible_commands.py
from mars_rover.domain.commands import UTurn
from mars_rover.domain.heading import Heading
from mars_rover.domain.rover import Rover
def test_uturn_from_north():
rover = Rover(0, 0, Heading.N)
UTurn()(rover)
assert rover.heading == Heading.S
def test_uturn_from_east():
rover = Rover(0, 0, Heading.E)
UTurn()(rover)
assert rover.heading == Heading.W
def test_uturn_from_south():
rover = Rover(0, 0, Heading.S)
UTurn()(rover)
assert rover.heading == Heading.N
def test_uturn_from_west():
rover = Rover(0, 0, Heading.W)
UTurn()(rover)
assert rover.heading == Heading.E
def test_uturn_does_not_move():
rover = Rover(3, 4, Heading.N)
UTurn()(rover)
assert rover.x == 3 and rover.y == 4
def test_uturn_via_rover_execute():
rover = Rover(0, 0, Heading.N)
rover.execute(UTurn())
assert rover.heading == Heading.S
Proof of open/closed compliance
Files touched to add UTurn:
File |
Change |
mars_rover/domain/commands.py
|
Add UTurn class (new code only) |
mars_rover/application/mission_controller.py
|
Add "U": UTurn() to command map |
tests/domain/test_extensible_commands.py
|
New test file |
Files not touched:
mars_rover/domain/rover.py ✅
mars_rover/domain/plateau.py ✅
mars_rover/domain/heading.py ✅
mars_rover/adapters/input_parser.py ✅
mars_rover/adapters/output_formatter.py ✅
Frontend Sub-Story
Story ID: NAV-FE-004.1
As an operator I want to include U in command strings once the mapping is registered so that I can use the new command without changing my input format.
Architecture Reference: 03-context.md — Operator → System interface
Scenarios:
Infrastructure Sub-Story
Story ID: NAV-INFRA-004.1
As a developer I want extensible command functionality to be containerized and testable so that new commands can be added and validated consistently across environments.
Architecture Reference: 07-deployment.md — deployment topology; 10-quality-requirements.md — QS-6; 04-solution-strategy.md — Command pattern
SCENARIO 1: Container processes new UTurn command correctly
Scenario ID: NAV-INFRA-004.1-S1
GIVEN
WHEN
THEN
UTurn command is executed correctly (180-degree rotation)
Final rover position reflects the UTurn operation
Container completes successfully with correct output
No errors are logged for the UTurn command
SCENARIO 2: Container validates command strings and rejects unknown commands
Scenario ID: NAV-INFRA-004.1-S2
GIVEN
WHEN
THEN
Validation error is logged to stderr with unknown command details
Container exits with non-zero exit code
No processing of invalid commands occurs
Error message indicates which command character is invalid
SCENARIO 3: Dockerfile builds with extensible command dependencies
Scenario ID: NAV-INFRA-004.1-S3
GIVEN
WHEN
THEN
The build includes all command classes and the command registry
UTurn and other extensible commands are available in the container
The container can execute any registered command
Build completes without errors
SCENARIO 4: Test suite validates extensible commands inside container
Scenario ID: NAV-INFRA-004.1-S4
GIVEN
WHEN
THEN
All extensible command tests run inside the container
Tests validate UTurn functionality and command validation
pytest discovers and executes all command-related tests
Container exits with code 0 on test success