88import java .io .IOException ;
99import java .nio .file .Files ;
1010import java .nio .file .Path ;
11+ import java .time .Instant ;
1112import java .time .LocalDate ;
1213import java .time .LocalDateTime ;
1314import java .time .ZoneId ;
1415import java .util .List ;
16+ import java .util .ArrayList ;
17+ import java .util .HashSet ;
18+ import java .util .concurrent .Executors ;
19+ import java .util .concurrent .Future ;
20+ import java .util .concurrent .TimeUnit ;
1521import java .util .stream .Stream ;
1622
1723import static org .assertj .core .api .Assertions .assertThat ;
@@ -27,6 +33,47 @@ void setUp() throws IOException {
2733 repository = new FileSystemTaskRepository (new FileSystemResource (workspaceDir ));
2834 }
2935
36+ @ Test
37+ void concurrentCreatesNeverShareAFile () throws Exception {
38+ Instant timestamp = Instant .parse ("2026-03-21T10:00:00Z" );
39+ try (var executor = Executors .newFixedThreadPool (8 )) {
40+ var futures = new ArrayList <Future <Task >>();
41+ for (int index = 0 ; index < 20 ; index ++) {
42+ String description = "Request " + index ;
43+ futures .add (executor .submit (() -> repository .save (Task .newTask ("same-name" , timestamp , description ))));
44+ }
45+ var ids = new HashSet <String >();
46+ for (var future : futures ) {
47+ Task saved = future .get (10 , TimeUnit .SECONDS );
48+ assertThat (ids .add (saved .getId ())).isTrue ();
49+ assertThat (repository .getTaskById (saved .getId ()).getDescription ()).isEqualTo (saved .getDescription ());
50+ }
51+ }
52+ }
53+
54+ @ Test
55+ void sameNameAndTimestampTasksRemainIndependent () {
56+ var timestamp = Instant .parse ("2026-03-21T10:00:00Z" );
57+ Task first = repository .save (Task .newTask ("check-mail" , timestamp , "First request" ));
58+ Task second = repository .save (Task .newTask ("check-mail" , timestamp , "Second request" ));
59+
60+ assertThat (second .getId ()).isNotEqualTo (first .getId ());
61+ assertThat (repository .getTaskById (first .getId ()).getDescription ()).isEqualTo ("First request" );
62+ assertThat (repository .getTaskById (second .getId ()).getDescription ()).isEqualTo ("Second request" );
63+ repository .save (first .withStatus (Task .Status .completed ));
64+ assertThat (repository .getTaskById (second .getId ()).getStatus ()).isEqualTo (Task .Status .todo );
65+ }
66+
67+ @ Test
68+ void sanitizedNameCollisionsRemainIndependent () {
69+ var timestamp = Instant .parse ("2026-03-21T10:00:00Z" );
70+ Task first = repository .save (Task .newTask ("check mail" , timestamp , "First" ));
71+ Task second = repository .save (Task .newTask ("check?mail" , timestamp , "Second" ));
72+
73+ assertThat (second .getId ()).isNotEqualTo (first .getId ());
74+ assertThat (repository .getTaskById (first .getId ()).getName ()).isEqualTo ("check mail" );
75+ }
76+
3077 @ Test
3178 void saveTaskCreatesFileWithCorrectContent () throws IOException {
3279 Task task = Task .newTask ("handle-email" , "Process unread email messages" );
0 commit comments