mirror of
https://github.com/emilybache/GildedRose-Refactoring-Kata.git
synced 2026-05-13 13:27:58 +00:00
feat: refactor GildedRose with Strategy pattern, ItemRecord, and days injection
This commit is contained in:
parent
4db0d990a7
commit
81332e2d28
@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol
|
||||
|
||||
@ -99,40 +101,57 @@ class ConjuredStrategy:
|
||||
item.quality = max(0, item.quality - 2)
|
||||
|
||||
|
||||
class GildedRose(object):
|
||||
_STRATEGY_MAP: dict[str, UpdateStrategy] = {
|
||||
"Aged Brie": AgedBrieStrategy(),
|
||||
"Backstage passes to a TAFKAL80ETC concert": BackstagePassStrategy(),
|
||||
"Sulfuras, Hand of Ragnaros": SulfurasStrategy(),
|
||||
"Conjured Mana Cake": ConjuredStrategy(),
|
||||
}
|
||||
|
||||
def __init__(self, items):
|
||||
|
||||
@dataclass
|
||||
class ItemRecord:
|
||||
"""
|
||||
Internal pairing of an Item with its UpdateStrategy.
|
||||
|
||||
Built once in GildedRose.__init__ so the name-to-strategy lookup
|
||||
happens at construction time, not on every update call. Exposed
|
||||
publicly so tests can inject strategies directly without going
|
||||
through the name-lookup dict.
|
||||
"""
|
||||
|
||||
item: Item
|
||||
strategy: UpdateStrategy
|
||||
|
||||
|
||||
class GildedRose:
|
||||
"""
|
||||
Inventory manager for the Gilded Rose inn.
|
||||
|
||||
Accepts a list of Item objects and updates their quality and sell_in
|
||||
values according to each item's type-specific strategy. The Item
|
||||
class and the items list are left untouched per the goblin's terms.
|
||||
|
||||
Usage:
|
||||
rose = GildedRose(items)
|
||||
rose.update_quality() # advance one day
|
||||
rose.update_quality(days=7) # advance a full week
|
||||
"""
|
||||
|
||||
def __init__(self, items: list[Item]) -> None:
|
||||
self.items = items
|
||||
self._records: list[ItemRecord] = [
|
||||
ItemRecord(
|
||||
item=i,
|
||||
strategy=_STRATEGY_MAP.get(i.name, NormalStrategy()),
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
|
||||
def update_quality(self):
|
||||
for item in self.items:
|
||||
if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert":
|
||||
if item.quality > 0:
|
||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
||||
item.quality = item.quality - 1
|
||||
else:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
if item.name == "Backstage passes to a TAFKAL80ETC concert":
|
||||
if item.sell_in < 11:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
if item.sell_in < 6:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
||||
item.sell_in = item.sell_in - 1
|
||||
if item.sell_in < 0:
|
||||
if item.name != "Aged Brie":
|
||||
if item.name != "Backstage passes to a TAFKAL80ETC concert":
|
||||
if item.quality > 0:
|
||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
||||
item.quality = item.quality - 1
|
||||
else:
|
||||
item.quality = item.quality - item.quality
|
||||
else:
|
||||
if item.quality < 50:
|
||||
item.quality = item.quality + 1
|
||||
def update_quality(self, days: int = 1) -> None:
|
||||
"""Advance quality and sell_in for all items by `days` days."""
|
||||
for record in self._records:
|
||||
record.strategy.update(record.item, days)
|
||||
|
||||
|
||||
class Item:
|
||||
|
||||
@ -10,6 +10,7 @@ if str(_EXERCISE_ROOT) not in sys.path:
|
||||
from gilded_rose import (
|
||||
Item, GildedRose, NormalStrategy, AgedBrieStrategy,
|
||||
BackstagePassStrategy, SulfurasStrategy, ConjuredStrategy,
|
||||
ItemRecord,
|
||||
)
|
||||
|
||||
|
||||
@ -207,5 +208,65 @@ class TestConjuredStrategy(unittest.TestCase):
|
||||
self.assertEqual(0, item.quality)
|
||||
|
||||
|
||||
class TestGildedRose(unittest.TestCase):
|
||||
"""
|
||||
Integration tests for GildedRose.
|
||||
|
||||
Verifies strategy dispatch by item name and that the days parameter
|
||||
is forwarded correctly to each strategy.
|
||||
"""
|
||||
|
||||
def test_dispatches_normal_strategy_for_unknown_item(self):
|
||||
items = [Item("unknown widget", sell_in=5, quality=10)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(9, items[0].quality)
|
||||
self.assertEqual(4, items[0].sell_in)
|
||||
|
||||
def test_dispatches_aged_brie_strategy(self):
|
||||
items = [Item("Aged Brie", sell_in=5, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(21, items[0].quality)
|
||||
|
||||
def test_dispatches_backstage_pass_strategy(self):
|
||||
items = [Item("Backstage passes to a TAFKAL80ETC concert", sell_in=5, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(23, items[0].quality)
|
||||
|
||||
def test_dispatches_sulfuras_strategy(self):
|
||||
items = [Item("Sulfuras, Hand of Ragnaros", sell_in=0, quality=80)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(80, items[0].quality)
|
||||
self.assertEqual(0, items[0].sell_in)
|
||||
|
||||
def test_dispatches_conjured_strategy(self):
|
||||
items = [Item("Conjured Mana Cake", sell_in=5, quality=20)]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(18, items[0].quality)
|
||||
|
||||
def test_multi_day_update_via_days_param(self):
|
||||
# sell_in=3, quality=10, days=3
|
||||
# Day1: q=9 si=2 | Day2: q=8 si=1 | Day3: q=7 si=0
|
||||
items = [Item("unknown widget", sell_in=3, quality=10)]
|
||||
GildedRose(items).update_quality(days=3)
|
||||
self.assertEqual(7, items[0].quality)
|
||||
self.assertEqual(0, items[0].sell_in)
|
||||
|
||||
def test_multiple_items_updated_independently(self):
|
||||
items = [
|
||||
Item("Aged Brie", sell_in=5, quality=20),
|
||||
Item("unknown widget", sell_in=5, quality=20),
|
||||
]
|
||||
GildedRose(items).update_quality()
|
||||
self.assertEqual(21, items[0].quality) # Aged Brie goes up
|
||||
self.assertEqual(19, items[1].quality) # normal goes down
|
||||
|
||||
def test_item_record_allows_direct_strategy_injection(self):
|
||||
# ItemRecord lets tests bypass name-lookup and inject a strategy directly
|
||||
item = Item("anything", sell_in=5, quality=20)
|
||||
record = ItemRecord(item=item, strategy=ConjuredStrategy())
|
||||
record.strategy.update(record.item, days=1)
|
||||
self.assertEqual(18, item.quality)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user