diff --git a/csharpcore/GildedRose/AbstractItemBuilder.cs b/csharpcore/GildedRose/AbstractItemBuilder.cs new file mode 100644 index 00000000..82d2fab2 --- /dev/null +++ b/csharpcore/GildedRose/AbstractItemBuilder.cs @@ -0,0 +1,13 @@ +namespace GildedRoseKata; + +public abstract class AbstractItemBuilder(string name, int sellIn, int quality) +{ + protected readonly string Name = name; + protected readonly int SellIn = sellIn; + protected readonly int Quality = quality; + + public virtual Item Build() + { + return new() { Name = Name, SellIn = SellIn, Quality = Quality }; + } +} \ No newline at end of file diff --git a/csharpcore/GildedRose/ItemBuilder.cs b/csharpcore/GildedRose/ItemBuilder.cs new file mode 100644 index 00000000..720058a7 --- /dev/null +++ b/csharpcore/GildedRose/ItemBuilder.cs @@ -0,0 +1,23 @@ +using System; + +namespace GildedRoseKata; + +public class ItemBuilder(string name, int sellIn, int quality) : AbstractItemBuilder(name, sellIn, quality) +{ + public override Item Build() + { + if (Quality < ItemQuality.MinQuality || Quality > ItemQuality.MaxQuality) + { + throw new ArgumentException("An item's quality must be >=0 and <=50"); + } + + if (ItemType.IsLegendaryItem(Name)) + { + throw new ArgumentException("Legendary Items cannot be constructed using this builder"); + } + + return base.Build(); + } +} + + diff --git a/csharpcore/GildedRose/LegendaryItemBuilder.cs b/csharpcore/GildedRose/LegendaryItemBuilder.cs new file mode 100644 index 00000000..456f3d51 --- /dev/null +++ b/csharpcore/GildedRose/LegendaryItemBuilder.cs @@ -0,0 +1,17 @@ +using System; + +namespace GildedRoseKata; + +public class LegendaryItemBuilder(string name, int sellIn) + : AbstractItemBuilder(name, sellIn, ItemQuality.LegendaryItemQuality) +{ + public override Item Build() + { + if (!ItemType.IsLegendaryItem(Name)) + { + throw new ArgumentException("Only Legendary items can be built using this builder"); + } + + return base.Build(); + } +} \ No newline at end of file diff --git a/csharpcore/GildedRose/RefactoringDiary.md b/csharpcore/GildedRose/RefactoringDiary.md index 12efe822..954d10f9 100644 --- a/csharpcore/GildedRose/RefactoringDiary.md +++ b/csharpcore/GildedRose/RefactoringDiary.md @@ -25,6 +25,8 @@ However, I've chosen to use the factory itself to manually manage it using a sem 11. Moved ItemType and ItemQuality to their own classes to allow re-use 12. Seperated the derived DailyUpdater classes into their own files.
Now, adding a new updater won't have to change the existing DailyUpdater file. +13. Added a new ItemBuilders hierarchy to enforce building valid items (quality limits and special rules for Legendary items). Also added unit tests and refactored the existing tests to use the new builders.
+note: if we could change Item, this could have been implemented in the Item's constructor itself and by using Quality TinyType. However, since we can't change Item, the builders gives us a nice decoupled way to validate the items are constructed consistently. diff --git a/csharpcore/GildedRoseTests/ItemBuilderTestFixture.cs b/csharpcore/GildedRoseTests/ItemBuilderTestFixture.cs new file mode 100644 index 00000000..67d85f56 --- /dev/null +++ b/csharpcore/GildedRoseTests/ItemBuilderTestFixture.cs @@ -0,0 +1,40 @@ +using System; +using FluentAssertions; +using GildedRoseKata; +using NUnit.Framework; + +namespace GildedRoseTests; + +public class ItemBuilderTestFixture +{ + [TestCase("Aged Brie", 5, 0)] + [TestCase("Elixir of the Mongoose", 0, 5)] + [TestCase("Backstage passes to a TAFKAL80ETC concert", -5, 39)] + public void WhenGivenValidParameters_ShouldBuildExpectedItemSuccessfully(string name, int sellIn, + int quality) + { + var item = new ItemBuilder(name, sellIn, quality).Build(); + item.Name.Should().Be(name); + item.SellIn.Should().Be(sellIn); + item.Quality.Should().Be(quality); + } + + + [TestCase("Aged Brie", 5, -1)] + [TestCase("Elixir of the Mongoose", 0, 51)] + public void WhenGivenInvalidQuality_ShouldThrow(string name, int sellIn, int quality) + { + var action = () => new ItemBuilder(name, sellIn, quality).Build(); + action.Should().Throw().WithMessage("An item's quality must be >=0 and <=50"); + } + + + [TestCase("Sulfuras, Hand of Ragnaros", 3, 3)] + [TestCase("something sulfuras", 3, 3)] + public void WhenGivenLegendaryItem_ShouldThrow(string name, int sellIn, int quality) + { + var action = () => new ItemBuilder(name, sellIn, quality).Build(); + action.Should().Throw().WithMessage("Legendary Items cannot be constructed using this builder"); + } + +} \ No newline at end of file diff --git a/csharpcore/GildedRoseTests/LegendaryItemBuilderTestFixture.cs b/csharpcore/GildedRoseTests/LegendaryItemBuilderTestFixture.cs new file mode 100644 index 00000000..12cfb23a --- /dev/null +++ b/csharpcore/GildedRoseTests/LegendaryItemBuilderTestFixture.cs @@ -0,0 +1,32 @@ +using System; +using FluentAssertions; +using GildedRoseKata; +using NUnit.Framework; + +namespace GildedRoseTests; + +public class LegendaryItemBuilderTestFixture +{ + + [TestCase("Aged Brie", 3)] + [TestCase("Elixir of the Mongoose", 0)] + [TestCase("Backstage passes to a TAFKAL80ETC concert", -3)] + public void WhenGivenOtherItemTypes_ShouldThrow(string name, int sellIn) + { + var action = () => new LegendaryItemBuilder(name, sellIn).Build(); + action.Should().Throw().WithMessage("Only Legendary items can be built using this builder"); + } + + + [TestCase("Sulfuras, Hand of Ragnaros", 3)] + [TestCase("Sulfuras, Hand of Ragnaros", 0)] + [TestCase("something sulfuras", -3)] + public void WhenGivenValidParameters_ShouldBuildExpectedItemSuccessfully(string name, int sellIn) + { + var item = new LegendaryItemBuilder(name, sellIn).Build(); + item.Name.Should().Be(name); + item.SellIn.Should().Be(sellIn); + item.Quality.Should().Be(ItemQuality.LegendaryItemQuality); + } + +} \ No newline at end of file diff --git a/csharpcore/GildedRoseTests/TextTestFixture.cs b/csharpcore/GildedRoseTests/TextTestFixture.cs index 89c48e79..c87b9608 100644 --- a/csharpcore/GildedRoseTests/TextTestFixture.cs +++ b/csharpcore/GildedRoseTests/TextTestFixture.cs @@ -11,31 +11,25 @@ public static class TextTestFixture Console.WriteLine("OMGHAI!"); var items = new List{ - new Item {Name = "+5 Dexterity Vest", SellIn = 10, Quality = 20}, - new Item {Name = "Aged Brie", SellIn = 2, Quality = 0}, - new Item {Name = "Elixir of the Mongoose", SellIn = 5, Quality = 7}, - new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = 0, Quality = 80}, - new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = -1, Quality = 80}, - new Item - { - Name = "Backstage passes to a TAFKAL80ETC concert", - SellIn = 15, - Quality = 20 - }, - new Item - { - Name = "Backstage passes to a TAFKAL80ETC concert", - SellIn = 10, - Quality = 49 - }, - new Item - { - Name = "Backstage passes to a TAFKAL80ETC concert", - SellIn = 5, - Quality = 49 - }, + new ItemBuilder("+5 Dexterity Vest", 10,20).Build(), + new ItemBuilder("Aged Brie", 2, 0).Build(), + new ItemBuilder("Elixir of the Mongoose", 5, 7).Build(), + new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 0).Build(), + new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", -1).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", + 15, + 20 + ).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", + 10, + 49 + ).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", + 5, + 49 + ).Build(), // this conjured item does not work properly yet - new Item {Name = "Conjured Mana Cake", SellIn = 3, Quality = 6} + new ItemBuilder("Conjured Mana Cake", 3, 6).Build() }; var app = new GildedRose(items); diff --git a/csharpcore/GildedRoseTests/UpdateQualityTestFixture.cs b/csharpcore/GildedRoseTests/UpdateQualityTestFixture.cs index e8123d4c..d39bc908 100644 --- a/csharpcore/GildedRoseTests/UpdateQualityTestFixture.cs +++ b/csharpcore/GildedRoseTests/UpdateQualityTestFixture.cs @@ -11,8 +11,8 @@ public class UpdateQualityTestFixture [Test] public void PlainItems_WhenNotExpired_Should_DecreaseSellInAndQualityByOne() { - var items = new List { new() { Name = "item 1", SellIn = 2, Quality = 3 } }; - var expectedItemsAfterTest = new List { new() { Name = "item 1", SellIn = 1, Quality = 2 } }; + var items = new List { new ItemBuilder("item 1", 2, 3).Build() }; + var expectedItemsAfterTest = new List { new ItemBuilder("item 1", 1, 2).Build() }; var app = new GildedRose(items); app.UpdateQuality(); @@ -24,9 +24,9 @@ public class UpdateQualityTestFixture [Test] public void PlainItems_WhenExpired_Should_DecreaseSellInByOneAndQualityByTwo() { - var items = new List { new() { Name = "item 1", SellIn = 0, Quality = 5 } }; - var expectedItemsAfterDay1 = new List { new() { Name = "item 1", SellIn = -1, Quality = 3 } }; - var expectedItemsAfterDay2 = new List { new() { Name = "item 1", SellIn = -2, Quality = 1 } }; + var items = new List { new ItemBuilder("item 1", 0, 5).Build() }; + var expectedItemsAfterDay1 = new List { new ItemBuilder("item 1", -1, 3).Build() }; + var expectedItemsAfterDay2 = new List { new ItemBuilder("item 1", -2, 1).Build() }; var app = new GildedRose(items); @@ -43,13 +43,13 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "item 1", SellIn = 0, Quality = 0 }, - new() { Name = "item 1", SellIn = 0, Quality = 1 } + new ItemBuilder("item 1", 0, 0).Build(), + new ItemBuilder("item 1", 0, 1).Build() }; var expectedItemsAfterTest = new List { - new() { Name = "item 1", SellIn = -1, Quality = 0 }, - new() { Name = "item 1", SellIn = -1, Quality = 0 } + new ItemBuilder("item 1", -1, 0).Build(), + new ItemBuilder("item 1", -1, 0).Build() }; @@ -63,8 +63,8 @@ public class UpdateQualityTestFixture [Test] public void BetterWithAgeItems_WhenNotExpired_Should_IncreaseQualityByOne() { - var items = new List { new() { Name = "Aged Brie", SellIn = 1, Quality = 0 } }; - var expectedItemsAfterTest = new List { new() { Name = "Aged Brie", SellIn = 0, Quality = 1 } }; + var items = new List { new ItemBuilder("Aged Brie", 1, 0).Build() }; + var expectedItemsAfterTest = new List { new ItemBuilder("Aged Brie", 0, 1).Build() }; var app = new GildedRose(items); app.UpdateQuality(); @@ -75,8 +75,8 @@ public class UpdateQualityTestFixture [Test] public void BetterWithAgeItems_WhenExpired_Should_IncreaseQualityByTwo() { - var items = new List { new() { Name = "aged Brie", SellIn = 0, Quality = 1 } }; - var expectedItemsAfterTest = new List { new() { Name = "aged Brie", SellIn = -1, Quality = 3 } }; + var items = new List { new ItemBuilder("aged Brie", 0, 1).Build() }; + var expectedItemsAfterTest = new List { new ItemBuilder("aged Brie", -1, 3).Build() }; var app = new GildedRose(items); app.UpdateQuality(); @@ -88,9 +88,9 @@ public class UpdateQualityTestFixture [Test] public void ItemsQuality_Should_NeverIncreaseAbove50() { - var items = new List { new() { Name = "Aged Brie", SellIn = 1, Quality = 49 } }; - var expectedItemsAfterDay1 = new List { new() { Name = "Aged Brie", SellIn = 0, Quality = 50 } }; - var expectedItemsAfterDay2 = new List { new() { Name = "Aged Brie", SellIn = -1, Quality = 50 } }; + var items = new List { new ItemBuilder("Aged Brie", 1, 49).Build() }; + var expectedItemsAfterDay1 = new List { new ItemBuilder("Aged Brie", 0, 50).Build() }; + var expectedItemsAfterDay2 = new List { new ItemBuilder("Aged Brie", -1, 50).Build() }; var app = new GildedRose(items); @@ -107,13 +107,13 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "Sulfuras, Hand of Ragnaros", SellIn = 3, Quality = 3 }, - new() { Name = "sulfuras", SellIn = 3, Quality = 31 } + new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 3).Build(), + new LegendaryItemBuilder("sulfuras", 3).Build() }; var expectedItemsAfterTest = new List { - new() { Name = "Sulfuras, Hand of Ragnaros", SellIn = 3, Quality = 3 }, - new() { Name = "sulfuras", SellIn = 3, Quality = 31 } + new LegendaryItemBuilder("Sulfuras, Hand of Ragnaros", 3).Build(), + new LegendaryItemBuilder("sulfuras", 3).Build() }; var app = new GildedRose(items); @@ -123,11 +123,10 @@ public class UpdateQualityTestFixture } [Test] - [Ignore("not implemented yet - Legendary Items should not take quality (constant at 80)")] public void LegendaryItems_Quality_IsConstant80() { - var items = new List { new() { Name = "something sulfuras something", SellIn = 3 } }; - var expectedItemsAfterTest = new List { new() { Name = "something sulfuras something", SellIn = 3, Quality = 80 } }; + var items = new List { new LegendaryItemBuilder("something sulfuras something", 3).Build() }; + var expectedItemsAfterTest = new List { new LegendaryItemBuilder("something sulfuras something", 3).Build() }; var app = new GildedRose(items); @@ -141,13 +140,13 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 11, Quality = 4 }, - new() { Name = "backstage passes to some other show", SellIn = 20, Quality = 40 } + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 11, 4).Build(), + new ItemBuilder("backstage passes to some other show", 20, 40).Build() }; var expectedItemsAfterTest = new List { - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 10, Quality = 5 }, - new() { Name = "backstage passes to some other show", SellIn = 19, Quality = 41 } + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 10, 5).Build(), + new ItemBuilder("backstage passes to some other show", 19, 41).Build() }; var app = new GildedRose(items); @@ -162,13 +161,13 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "backstage passes to a TAFKAL80ETC concert", SellIn = 10, Quality = 4 }, - new() { Name = "Backstage passes to some other show", SellIn = 6, Quality = 41 } + new ItemBuilder("backstage passes to a TAFKAL80ETC concert", 10, 4).Build(), + new ItemBuilder("Backstage passes to some other show", 6, 41).Build() }; var expectedItemsAfterTest = new List { - new() { Name = "backstage passes to a TAFKAL80ETC concert", SellIn = 9, Quality = 6 }, - new() { Name = "Backstage passes to some other show", SellIn = 5, Quality = 43 } + new ItemBuilder("backstage passes to a TAFKAL80ETC concert", 9, 6).Build(), + new ItemBuilder("Backstage passes to some other show", 5, 43).Build() }; var app = new GildedRose(items); @@ -182,13 +181,13 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "Backstage passes to some other show", SellIn = 5, Quality = 4 }, - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 1, Quality = 41 } + new ItemBuilder("Backstage passes to some other show", 5, 4).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 1, 41).Build() }; var expectedItemsAfterTest = new List { - new() { Name = "Backstage passes to some other show", SellIn = 4, Quality = 7 }, - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 0, Quality = 44 } + new ItemBuilder("Backstage passes to some other show", 4, 7).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 0, 44).Build() }; var app = new GildedRose(items); @@ -202,21 +201,21 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "Backstage passes", SellIn = 15, Quality = 49 }, - new() { Name = "Backstage passes to some other show", SellIn = 10, Quality = 49 }, - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 5, Quality = 48 } + new ItemBuilder("Backstage passes", 15, 49).Build(), + new ItemBuilder("Backstage passes to some other show", 10,49).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 5, 48).Build() }; var expectedItemsAfterDay1 = new List { - new() { Name = "Backstage passes", SellIn = 14, Quality = 50 }, - new() { Name = "Backstage passes to some other show", SellIn = 9, Quality = 50 }, - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 4, Quality = 50 } + new ItemBuilder("Backstage passes", 14, 50).Build(), + new ItemBuilder("Backstage passes to some other show", 9, 50).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 4, 50).Build() }; var expectedItemsAfterDay2 = new List { - new() { Name = "Backstage passes", SellIn = 13, Quality = 50 }, - new() { Name = "Backstage passes to some other show", SellIn = 8, Quality = 50 }, - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 3, Quality = 50 } + new ItemBuilder("Backstage passes", 13, 50).Build(), + new ItemBuilder("Backstage passes to some other show", 8, 50).Build(), + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 3, 50).Build() }; var app = new GildedRose(items); @@ -233,13 +232,13 @@ public class UpdateQualityTestFixture { var items = new List { - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 0, Quality = 4 }, - new() { Name = "Another type of backstage passes", SellIn = 0, Quality = 4 } + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", 0, 4).Build(), + new ItemBuilder("Another type of backstage passes", 0, 4).Build() }; var expectedItemsAfterTest = new List { - new() { Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = -1, Quality = 0 }, - new() { Name = "Another type of backstage passes", SellIn = -1, Quality = 0 } + new ItemBuilder("Backstage passes to a TAFKAL80ETC concert", -1, 0).Build(), + new ItemBuilder("Another type of backstage passes", -1, 0).Build() }; var app = new GildedRose(items);