Milestone 3: CLI program #15

Merged
Tirsvad merged 1 commits from milestone/03-cli into main 2026-10-04 08:14:12 +02:00
3 changed files with 135 additions and 40 deletions
+2 -2
View File
@@ -28,8 +28,8 @@ Work on ONE milestone at a time. Each milestone is one branch (`milestone/NN-nam
## Milestone 3 - CLI program (`milestone/03-cli`)
- [ ] `main.py`: prompt name and numeric bid (re-prompt on invalid), bids dict kept outside the loop, "other bidders? yes/no", clear screen with newlines between bidders, announce the winner
- [ ] `tests/test_main.py` with monkeypatched `input`
- [x] `main.py`: prompt name and numeric bid (re-prompt on invalid), bids dict kept outside the loop, "other bidders? yes/no", clear screen with newlines between bidders, announce the winner
- [x] `tests/test_main.py` with monkeypatched `input`
- Verify: `pytest`; scripted run `python -m secret_auction.main` with two bidders
## Milestone 4 - Documentation (`milestone/04-docs`)
+60
View File
@@ -0,0 +1,60 @@
"""! @file main.py
@brief Command line interface for the secret auction program.
"""
import math
from secret_auction import constants
from secret_auction.auction import add_bid, find_winner
def clear_screen() -> None:
"""! @brief Hide previous entries by printing blank lines."""
print("\n" * constants.CLEAR_SCREEN_LINES)
def ask_bid() -> float:
"""! @brief Ask for a bid until a valid, non-negative number is entered.
@return The bid amount.
"""
while True:
try:
amount = float(input(constants.PROMPT_BID))
except ValueError:
print(constants.MSG_INVALID_BID)
continue
if math.isfinite(amount) and amount >= 0:
return amount
print(constants.MSG_INVALID_BID)
def ask_more_bidders() -> bool:
"""! @brief Ask whether there are more bidders until yes/no is answered.
@return True if more bidders exist, False otherwise.
"""
while True:
answer = input(constants.PROMPT_MORE_BIDDERS).strip().lower()
if answer in constants.YES_ANSWERS:
return True
if answer in constants.NO_ANSWERS:
return False
print(constants.MSG_INVALID_ANSWER)
def main() -> None:
"""! @brief Run the blind auction: collect bids, then announce the winner."""
print(constants.MSG_WELCOME)
bids: dict[str, float] = {} # kept outside the loop so bids are not lost
more_bidders = True
while more_bidders:
name = input(constants.PROMPT_NAME).strip()
add_bid(bids, name, ask_bid())
more_bidders = ask_more_bidders()
if more_bidders:
clear_screen()
winner, amount = find_winner(bids)
print(constants.MSG_WINNER.format(name=winner, amount=amount))
if __name__ == "__main__":
main()
+35
View File
@@ -0,0 +1,35 @@
from secret_auction import constants
from secret_auction.main import ask_bid, ask_more_bidders, main
def feed(monkeypatch, answers):
it = iter(answers)
monkeypatch.setattr("builtins.input", lambda _prompt="": next(it))
def test_ask_bid_reprompts_on_invalid(monkeypatch, capsys):
feed(monkeypatch, ["abc", "-5", "nan", "12.5"])
assert ask_bid() == 12.5
assert capsys.readouterr().out.count(constants.MSG_INVALID_BID) == 3
def test_ask_more_bidders(monkeypatch, capsys):
feed(monkeypatch, ["maybe", "Y"])
assert ask_more_bidders() is True
assert constants.MSG_INVALID_ANSWER in capsys.readouterr().out
feed(monkeypatch, ["no"])
assert ask_more_bidders() is False
def test_main_announces_highest_bidder_and_clears_screen(monkeypatch, capsys):
feed(monkeypatch, ["Ann", "10", "yes", "Bob", "25.5", "yes", "Cy", "3", "no"])
main()
out = capsys.readouterr().out
assert "The winner is Bob with a bid of $25.50." in out
assert out.count("\n" * constants.CLEAR_SCREEN_LINES) == 2
def test_main_single_bidder(monkeypatch, capsys):
feed(monkeypatch, ["Ann", "7", "n"])
main()
assert "The winner is Ann with a bid of $7.00." in capsys.readouterr().out