|
| 1 | +🕵️♂️ The Case of the Vanishing Code |
| 2 | + |
| 3 | +Once upon a time in the bustling digital halls of the ITHS Java Lab, a brilliant team of developers built a sleek, |
| 4 | +elegant application. It was fast. It was flawless. It was... undocumented. |
| 5 | + |
| 6 | +But then—disaster struck. During a routine migration to a new version control system, someone (we won’t name names, but |
| 7 | +let’s just say their coffee-to-commit ratio was dangerously high ☕💻) accidentally triggered the mythical git nuke |
| 8 | +--force command. The source code vanished into the void. |
| 9 | + |
| 10 | +Gone. Poof. Like it never existed. |
| 11 | + |
| 12 | +All that remained were the sacred test files—meticulously crafted, oddly untouched by the chaos. These tests, like |
| 13 | +ancient scrolls, held the secrets of what the application was supposed to do. They whispered of methods, behaviors, and |
| 14 | +edge cases. They were our only clue. |
| 15 | + |
| 16 | +Now, the mission falls to you: the brave code archaeologists of Java25. Your task is to reconstruct the lost |
| 17 | +application, piece by piece, using only the tests as your guide. Think of it as reverse-engineering a fossilized |
| 18 | +dinosaur from its footprints... except the dinosaur is a Java program, and the footprints are JUnit assertions. |
| 19 | + |
| 20 | +Luckily, a few fragments of implementation survived—like half-buried ruins in the sand. Use them wisely. Interpret the |
| 21 | +tests. Rebuild the logic. And maybe, just maybe, restore the glory of the lost codebase. |
| 22 | + |
1 | 23 | # Warehouse Kata – Instructions |
2 | 24 |
|
3 | 25 | Welcome! In this exercise you will implement a small domain around a Warehouse with products. Your goal is to: |
4 | | -- Create and complete several classes/interfaces under src/main/java that are currently missing or partially implemented. |
| 26 | + |
| 27 | +- Create and complete several classes/interfaces under src/main/java that are currently missing or partially |
| 28 | + implemented. |
5 | 29 | - Pick correct data types for parameters and return values so the project compiles and tests can run. |
6 | 30 | - Make all tests in BasicTest green first. |
7 | 31 | - Commit after major steps. When all BasicTest tests are green, push your commits. |
8 | 32 | - Extra credit: implement the advanced features so that EdgeCaseTest is also green. |
9 | 33 |
|
10 | 34 | ## 1) Getting started |
| 35 | + |
11 | 36 | 1. Open this project in your IDE (IntelliJ IDEA recommended). |
12 | 37 | 2. Ensure you have JDK 25. |
13 | 38 | 3. Build once to see current state: |
14 | | - - ./mvnw compile |
| 39 | + - ./mvnw compile |
15 | 40 |
|
16 | 41 | ## 2) What you will need to create/finish |
17 | | -You will work primarily in src/main/java/com/example. Some files already exist but contain TODOs or empty methods. Implement the following: |
| 42 | + |
| 43 | +You will work primarily in src/main/java/com/example. Some files already exist but contain TODOs or empty methods. |
| 44 | +Implement the following: |
18 | 45 |
|
19 | 46 | - Category (value object) |
20 | | - - Use a private constructor and a public static factory Category.of(String name). |
21 | | - - Validate input: null => "Category name can't be null"; empty/blank => "Category name can't be blank". |
22 | | - - Normalize name with initial capital letter (e.g., "fruit" -> "Fruit"). |
23 | | - - Cache/flyweight: return the same instance for the same normalized name. |
| 47 | + - Use a private constructor and a public static factory Category.of(String name). |
| 48 | + - Validate input: null => "Category name can't be null"; empty/blank => "Category name can't be blank". |
| 49 | + - Normalize name with initial capital letter (e.g., "fruit" -> "Fruit"). |
| 50 | + - Cache/flyweight: return the same instance for the same normalized name. |
24 | 51 |
|
25 | 52 | - Product (abstract base class) |
26 | | - - Keep UUID id, String name, Category category, BigDecimal price. |
27 | | - - Provide getters named uuid(), name(), category(), price() and a setter price(BigDecimal). |
28 | | - - Provide an abstract String productDetails() for polymorphism. |
| 53 | + - Keep UUID id, String name, Category category, BigDecimal price. |
| 54 | + - Provide getters named uuid(), name(), category(), price() and a setter price(BigDecimal). |
| 55 | + - Provide an abstract String productDetails() for polymorphism. |
29 | 56 |
|
30 | 57 | - FoodProduct (extends Product) |
31 | | - - Implements Perishable and Shippable. |
32 | | - - Fields: LocalDate expirationDate, BigDecimal weight (kg). |
33 | | - - Validations: negative price -> IllegalArgumentException("Price cannot be negative."); negative weight -> IllegalArgumentException("Weight cannot be negative."). |
34 | | - - productDetails() should look like: "Food: Milk, Expires: 2025-12-24". |
35 | | - - Shipping rule: cost = weight * 50. |
| 58 | + - Implements Perishable and Shippable. |
| 59 | + - Fields: LocalDate expirationDate, BigDecimal weight (kg). |
| 60 | + - Validations: negative price -> IllegalArgumentException("Price cannot be negative."); negative weight -> |
| 61 | + IllegalArgumentException("Weight cannot be negative."). |
| 62 | + - productDetails() should look like: "Food: Milk, Expires: 2025-12-24". |
| 63 | + - Shipping rule: cost = weight * 50. |
36 | 64 |
|
37 | 65 | - ElectronicsProduct (extends Product) |
38 | | - - Implements Shippable. |
39 | | - - Fields: int warrantyMonths, BigDecimal weight (kg). |
40 | | - - Validation: negative warranty -> IllegalArgumentException("Warranty months cannot be negative."). |
41 | | - - productDetails() should look like: "Electronics: Laptop, Warranty: 24 months". |
42 | | - - Shipping rule: base 79, add 49 if weight > 5.0 kg. |
| 66 | + - Implements Shippable. |
| 67 | + - Fields: int warrantyMonths, BigDecimal weight (kg). |
| 68 | + - Validation: negative warranty -> IllegalArgumentException("Warranty months cannot be negative."). |
| 69 | + - productDetails() should look like: "Electronics: Laptop, Warranty: 24 months". |
| 70 | + - Shipping rule: base 79, add 49 if weight > 5.0 kg. |
43 | 71 |
|
44 | 72 | - Interfaces |
45 | | - - Perishable: expose expirationDate() and a default isExpired() based on LocalDate.now(). |
46 | | - - Shippable: expose calculateShippingCost() and weight() (used by shipping optimizer in extra tests). |
| 73 | + - Perishable: expose expirationDate() and a default isExpired() based on LocalDate.now(). |
| 74 | + - Shippable: expose calculateShippingCost() and weight() (used by shipping optimizer in extra tests). |
47 | 75 |
|
48 | 76 | - Warehouse (singleton per name) |
49 | | - - getInstance(String name) returns the same instance per unique name. |
50 | | - - addProduct(Product): throw IllegalArgumentException("Product cannot be null.") if null. |
51 | | - - getProducts(): return an unmodifiable copy. |
52 | | - - getProductById(UUID): return Optional. |
53 | | - - updateProductPrice(UUID, BigDecimal): when not found, throw NoSuchElementException("Product not found with id: <uuid>"). Also track changed products in getChangedProducts(). |
54 | | - - expiredProducts(): return List<Perishable> that are expired. |
55 | | - - shippableProducts(): return List<Shippable> from stored products. |
56 | | - - remove(UUID): remove the matching product if present. |
| 77 | + - getInstance(String name) returns the same instance per unique name. |
| 78 | + - addProduct(Product): throw IllegalArgumentException("Product cannot be null.") if null. |
| 79 | + - getProducts(): return an unmodifiable copy. |
| 80 | + - getProductById(UUID): return Optional. |
| 81 | + - updateProductPrice(UUID, BigDecimal): when not found, throw NoSuchElementException("Product not found with |
| 82 | + id: <uuid>"). Also track changed products in getChangedProducts(). |
| 83 | + - expiredProducts(): return List<Perishable> that are expired. |
| 84 | + - shippableProducts(): return List<Shippable> from stored products. |
| 85 | + - remove(UUID): remove the matching product if present. |
57 | 86 |
|
58 | 87 | - WarehouseAnalyzer (extra credit) |
59 | | - - Implement the advanced methods used by EdgeCaseTest: price-range search (inclusive), expiring-within-days, case-insensitive name search, above-price search, weighted average per category (round to 2 decimals), price outliers (population stddev), shipping group optimization (first‑fit decreasing by weight), expiration-based discounts, inventory validation summary, and inventory statistics. |
| 88 | + - Implement the advanced methods used by EdgeCaseTest: price-range search (inclusive), expiring-within-days, |
| 89 | + case-insensitive name search, above-price search, weighted average per category (round to 2 decimals), price |
| 90 | + outliers (population stddev), shipping group optimization (first‑fit decreasing by weight), expiration-based |
| 91 | + discounts, inventory validation summary, and inventory statistics. |
60 | 92 |
|
61 | 93 | ## 3) Workflow to follow |
| 94 | + |
62 | 95 | 1. Implement the missing classes/interfaces and methods so the project compiles. |
63 | 96 | 2. Run tests: |
64 | | - - Basic first: ./mvnw -Dtest=BasicTest test |
65 | | - - When green, commit with a clear message. |
| 97 | + - Basic first: ./mvnw -Dtest=BasicTest test |
| 98 | + - When green, commit with a clear message. |
66 | 99 | 3. Extra credit: make EdgeCaseTest green: |
67 | | - - ./mvnw -Dtest=EdgeCaseTest test |
68 | | -4. Commit after each major milestone (e.g., "Implement Product & FoodProduct", "Warehouse behaviors", "Analyzer advanced features"). |
| 100 | + - ./mvnw -Dtest=EdgeCaseTest test |
| 101 | +4. Commit after each major milestone (e.g., "Implement Product & FoodProduct", "Warehouse behaviors", "Analyzer advanced |
| 102 | + features"). |
69 | 103 | 5. Push when BasicTest is fully green (and EdgeCaseTest too if you do the extra credit). |
70 | 104 |
|
71 | 105 | ## 4) Tips |
72 | | -- Prefer BigDecimal for prices and weights (exact values in tests). Where an interface requires Double (e.g., weight()), convert BigDecimal to double on return. |
| 106 | + |
| 107 | +- Prefer BigDecimal for prices and weights (exact values in tests). Where an interface requires Double (e.g., weight()), |
| 108 | + convert BigDecimal to double on return. |
73 | 109 | - Always round monetary results to 2 decimals using HALF_UP when tests assert exact values. |
74 | 110 | - Keep public APIs exactly as tests expect (method names, exception messages). |
75 | 111 | - Ensure Warehouse.clearProducts() is called in tests; do not share state between tests. |
|
0 commit comments