Skip to content

Commit 03daff5

Browse files
committed
adopt content
1 parent 00e698c commit 03daff5

8 files changed

Lines changed: 212 additions & 230 deletions

File tree

labs/lab-7/src/test/java/pragmatech/digital/workshops/lab7/solutions/SharedPostgresContainerExtension.java

Lines changed: 0 additions & 102 deletions
This file was deleted.

labs/lab-8/CLAUDE.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Claude Instructions
2+
3+
You are to follow a strict test-driven development (TDD) workflow for all code changes. The process is as follows:
4+
5+
1. Write Tests First:
6+
7+
- Begin by writing comprehensive tests (unit, integration, or end-to-end) that define the expected behavior of the code.
8+
- Use explicit input/output pairs and edge cases.
9+
- Do not write any implementation code at this stage, even if the functionality does not yet exist.
10+
- Confirm that you are only writing tests, not mock implementations.
11+
12+
2. Run and Confirm Failing Tests:
13+
14+
- Run the tests and confirm that they fail, as the implementation does not exist yet.
15+
- Do not write or suggest any implementation code at this stage.
16+
17+
3. Commit the Tests:
18+
19+
Once the tests are complete and you are satisfied with their coverage, commit only the test code.
20+
21+
4. Write Implementation Code:
22+
23+
- Write the minimal implementation code required to make the tests pass.
24+
- Do not modify the tests during this step.
25+
- Iterate: If the tests do not pass, adjust the implementation and rerun the tests until all pass.
26+
- Optionally, verify with independent subagents or reviewers that the implementation is not overfitting to the tests.
27+
28+
5. Commit the Implementation:
29+
30+
- Once all tests pass and you are satisfied with the implementation, commit the code changes.
31+
32+
33+
**Guidelines:**
34+
35+
- Always keep tests and implementation changes in separate commits.
36+
- Do not write any implementation code before the tests are written and committed.
37+
- Do not modify the tests after they are committed, unless explicitly instructed to do so.
38+
- Be explicit and transparent about each step, confirming completion before moving to the next.
39+
40+
**Your goal:** Incrementally build and verify code by iterating between writing tests and writing implementation, ensuring each change is easily verifiable and traceable.
41+
42+
## Test Code
43+
44+
When writing test code, please follow these guidelines:
45+
46+
- Use JUnit 5 for all test classes
47+
- Name test methods using the pattern: `should<ExpectedBehavior>When<Condition>`
48+
- Structure each test with the Arrange-Act-Assert pattern
49+
- Use AssertJ for all assertions
50+
- Avoid for loops and if statements in tests
51+
- Avoid comments in the test code
52+
- Name the class under test variable as cut
53+
- Create a separate test class for each production class
54+
- Follow a consistent setup pattern for all tests
55+
- Use `@DisplayName` for more descriptive test names in reports
56+
- Group related tests with `@Nested` classes
57+
- Use parameterized tests for testing multiple scenarios
58+
- Mock external dependencies with Mockito
59+
- Use Java records for test data classes
60+
- When constructing test data for existing domain classes, use the Builder and Object Mother pattern
61+
- Use Java text blocks for larger JSON data

labs/lab-8/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Lab 8: General FAQ and Customer Specific Issues
22

3+
## AI
4+
5+
```
6+
For my BookController, please implement a new endpoint to export all avialable books as CSV. Include all available fields of the Book and sort it by default by the creation date.
7+
8+
Make sure this can only be done by authenticated users with the "ADMIN" role.
9+
```
10+
311
This lab covers practical topics that frequently come up in real-world Spring Boot testing projects: GitHub Actions pipeline best practices, Maven tips for faster builds, and test organization using JUnit 5 `@Tag` with Maven profiles.
412

513
## Learning Objectives

labs/lab-8/src/main/java/pragmatech/digital/workshops/lab8/service/BookService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package pragmatech.digital.workshops.lab8.service;
22

3+
import java.time.LocalDate;
34
import java.util.List;
45
import java.util.Optional;
56

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package pragmatech.digital.workshops.lab8.service;
22

3+
import java.math.BigDecimal;
34
import java.time.Clock;
45
import java.time.LocalDate;
56
import java.time.temporal.ChronoUnit;
@@ -11,28 +12,32 @@
1112
@Service
1213
public class LateReturnFeeCalculator {
1314

15+
private static final BigDecimal RATE_TIER_ONE = new BigDecimal("1.00");
16+
private static final BigDecimal RATE_TIER_TWO = new BigDecimal("1.50");
17+
private static final BigDecimal RATE_TIER_THREE = new BigDecimal("2.00");
18+
1419
private final Clock clock;
1520

1621
public LateReturnFeeCalculator(Clock clock) {
1722
this.clock = clock;
1823
}
1924

20-
public double calculateFee(Book book, LocalDate borrowedDate) {
25+
public BigDecimal calculateFee(Book book, LocalDate borrowedDate) {
2126
if (book.getStatus() != BookStatus.BORROWED) {
22-
return 0.0;
27+
return BigDecimal.ZERO;
2328
}
2429

2530
LocalDate today = LocalDate.now(clock);
2631
long daysOverdue = ChronoUnit.DAYS.between(borrowedDate, today);
2732

2833
if (daysOverdue <= 0) {
29-
return 0.0;
34+
return BigDecimal.ZERO;
3035
} else if (daysOverdue <= 7) {
31-
return daysOverdue * 1.0;
36+
return RATE_TIER_ONE.multiply(BigDecimal.valueOf(daysOverdue));
3237
} else if (daysOverdue <= 14) {
33-
return daysOverdue * 1.5;
38+
return RATE_TIER_TWO.multiply(BigDecimal.valueOf(daysOverdue));
3439
} else {
35-
return daysOverdue * 2.0;
40+
return RATE_TIER_THREE.multiply(BigDecimal.valueOf(daysOverdue));
3641
}
3742
}
3843
}

labs/lab-8/src/test/java/pragmatech/digital/workshops/lab8/experiment/LateReturnFeeCalculatorTest.java

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package pragmatech.digital.workshops.lab8.experiment;
22

3+
import java.math.BigDecimal;
34
import java.time.Clock;
45
import java.time.Instant;
56
import java.time.LocalDate;
@@ -40,19 +41,19 @@ void shouldReturnZeroFeeWhenBookIsAvailable() {
4041
Book book = new Book("978-0-13-468599-1", "Clean Code", "Martin", LocalDate.of(2008, 8, 1));
4142
book.setStatus(BookStatus.AVAILABLE);
4243

43-
double fee = cut.calculateFee(book, LocalDate.of(2025, 5, 1));
44+
BigDecimal fee = cut.calculateFee(book, LocalDate.of(2025, 5, 1));
4445

45-
assertThat(fee).isEqualTo(0.0);
46+
assertThat(fee).isEqualByComparingTo(BigDecimal.ZERO);
4647
}
4748

4849
@Test
4950
void shouldReturnZeroFeeWhenBookIsReserved() {
5051
Book book = new Book("978-0-13-468599-1", "Clean Code", "Martin", LocalDate.of(2008, 8, 1));
5152
book.setStatus(BookStatus.RESERVED);
5253

53-
double fee = cut.calculateFee(book, LocalDate.of(2025, 5, 1));
54+
BigDecimal fee = cut.calculateFee(book, LocalDate.of(2025, 5, 1));
5455

55-
assertThat(fee).isEqualTo(0.0);
56+
assertThat(fee).isEqualByComparingTo(BigDecimal.ZERO);
5657
}
5758
}
5859

@@ -69,58 +70,58 @@ void setUp() {
6970

7071
@Test
7172
void shouldReturnZeroFeeWhenReturnedOnTime() {
72-
double fee = cut.calculateFee(borrowedBook, LocalDate.of(2025, 6, 1));
73+
BigDecimal fee = cut.calculateFee(borrowedBook, LocalDate.of(2025, 6, 1));
7374

74-
assertThat(fee).isEqualTo(0.0);
75+
assertThat(fee).isEqualByComparingTo(BigDecimal.ZERO);
7576
}
7677

7778
@Test
7879
void shouldReturnZeroFeeWhenBorrowedInFuture() {
79-
double fee = cut.calculateFee(borrowedBook, LocalDate.of(2025, 6, 15));
80+
BigDecimal fee = cut.calculateFee(borrowedBook, LocalDate.of(2025, 6, 15));
8081

81-
assertThat(fee).isEqualTo(0.0);
82+
assertThat(fee).isEqualByComparingTo(BigDecimal.ZERO);
8283
}
8384

8485
@ParameterizedTest(name = "{0} days overdue -> fee ${1}")
8586
@CsvSource({
86-
"1, 1.0",
87-
"3, 3.0",
88-
"7, 7.0"
87+
"1, 1.00",
88+
"3, 3.00",
89+
"7, 7.00"
8990
})
90-
void shouldChargeOneDollarPerDayWhenOneToSevenDaysOverdue(long daysOverdue, double expectedFee) {
91+
void shouldChargeOneDollarPerDayWhenOneToSevenDaysOverdue(long daysOverdue, BigDecimal expectedFee) {
9192
LocalDate borrowedDate = LocalDate.of(2025, 6, 1).minusDays(daysOverdue);
9293

93-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
94+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
9495

95-
assertThat(fee).isEqualTo(expectedFee);
96+
assertThat(fee).isEqualByComparingTo(expectedFee);
9697
}
9798

9899
@ParameterizedTest(name = "{0} days overdue -> fee ${1}")
99100
@CsvSource({
100-
"8, 12.0",
101-
"10, 15.0",
102-
"14, 21.0"
101+
"8, 12.00",
102+
"10, 15.00",
103+
"14, 21.00"
103104
})
104-
void shouldChargeDollarFiftyPerDayWhenEightToFourteenDaysOverdue(long daysOverdue, double expectedFee) {
105+
void shouldChargeDollarFiftyPerDayWhenEightToFourteenDaysOverdue(long daysOverdue, BigDecimal expectedFee) {
105106
LocalDate borrowedDate = LocalDate.of(2025, 6, 1).minusDays(daysOverdue);
106107

107-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
108+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
108109

109-
assertThat(fee).isEqualTo(expectedFee);
110+
assertThat(fee).isEqualByComparingTo(expectedFee);
110111
}
111112

112113
@ParameterizedTest(name = "{0} days overdue -> fee ${1}")
113114
@CsvSource({
114-
"15, 30.0",
115-
"20, 40.0",
116-
"30, 60.0"
115+
"15, 30.00",
116+
"20, 40.00",
117+
"30, 60.00"
117118
})
118-
void shouldChargeTwoDollarsPerDayWhenFifteenOrMoreDaysOverdue(long daysOverdue, double expectedFee) {
119+
void shouldChargeTwoDollarsPerDayWhenFifteenOrMoreDaysOverdue(long daysOverdue, BigDecimal expectedFee) {
119120
LocalDate borrowedDate = LocalDate.of(2025, 6, 1).minusDays(daysOverdue);
120121

121-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
122+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
122123

123-
assertThat(fee).isEqualTo(expectedFee);
124+
assertThat(fee).isEqualByComparingTo(expectedFee);
124125
}
125126

126127
@Nested
@@ -130,36 +131,36 @@ class BoundaryValues {
130131
void shouldApplyTierOneBoundaryAtSevenDays() {
131132
LocalDate borrowedDate = LocalDate.of(2025, 5, 25);
132133

133-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
134+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
134135

135-
assertThat(fee).isEqualTo(7.0);
136+
assertThat(fee).isEqualByComparingTo(new BigDecimal("7.00"));
136137
}
137138

138139
@Test
139140
void shouldApplyTierTwoBoundaryAtEightDays() {
140141
LocalDate borrowedDate = LocalDate.of(2025, 5, 24);
141142

142-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
143+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
143144

144-
assertThat(fee).isEqualTo(12.0);
145+
assertThat(fee).isEqualByComparingTo(new BigDecimal("12.00"));
145146
}
146147

147148
@Test
148149
void shouldApplyTierTwoBoundaryAtFourteenDays() {
149150
LocalDate borrowedDate = LocalDate.of(2025, 5, 18);
150151

151-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
152+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
152153

153-
assertThat(fee).isEqualTo(21.0);
154+
assertThat(fee).isEqualByComparingTo(new BigDecimal("21.00"));
154155
}
155156

156157
@Test
157158
void shouldApplyTierThreeBoundaryAtFifteenDays() {
158159
LocalDate borrowedDate = LocalDate.of(2025, 5, 17);
159160

160-
double fee = cut.calculateFee(borrowedBook, borrowedDate);
161+
BigDecimal fee = cut.calculateFee(borrowedBook, borrowedDate);
161162

162-
assertThat(fee).isEqualTo(30.0);
163+
assertThat(fee).isEqualByComparingTo(new BigDecimal("30.00"));
163164
}
164165
}
165166
}
2.23 MB
Loading

0 commit comments

Comments
 (0)