@coralai/sps-cli 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +59 -4
  2. package/dist/commands/consoleCommand.d.ts +2 -0
  3. package/dist/commands/consoleCommand.d.ts.map +1 -0
  4. package/dist/commands/consoleCommand.js +129 -0
  5. package/dist/commands/consoleCommand.js.map +1 -0
  6. package/dist/commands/projectInit.d.ts.map +1 -1
  7. package/dist/commands/projectInit.js +40 -53
  8. package/dist/commands/projectInit.js.map +1 -1
  9. package/dist/commands/setup.d.ts.map +1 -1
  10. package/dist/commands/setup.js +14 -2
  11. package/dist/commands/setup.js.map +1 -1
  12. package/dist/commands/skillCommand.d.ts +2 -0
  13. package/dist/commands/skillCommand.d.ts.map +1 -0
  14. package/dist/commands/skillCommand.js +235 -0
  15. package/dist/commands/skillCommand.js.map +1 -0
  16. package/dist/console-assets/assets/index-Bhd2f9AP.js +125 -0
  17. package/dist/console-assets/assets/index-bsAN2a12.css +1 -0
  18. package/dist/console-assets/index.html +16 -0
  19. package/dist/console-server/index.d.ts +29 -0
  20. package/dist/console-server/index.d.ts.map +1 -0
  21. package/dist/console-server/index.js +145 -0
  22. package/dist/console-server/index.js.map +1 -0
  23. package/dist/console-server/lib/lockFile.d.ts +17 -0
  24. package/dist/console-server/lib/lockFile.d.ts.map +1 -0
  25. package/dist/console-server/lib/lockFile.js +61 -0
  26. package/dist/console-server/lib/lockFile.js.map +1 -0
  27. package/dist/console-server/lib/portPicker.d.ts +3 -0
  28. package/dist/console-server/lib/portPicker.d.ts.map +1 -0
  29. package/dist/console-server/lib/portPicker.js +25 -0
  30. package/dist/console-server/lib/portPicker.js.map +1 -0
  31. package/dist/console-server/routes/projects.d.ts +11 -0
  32. package/dist/console-server/routes/projects.d.ts.map +1 -0
  33. package/dist/console-server/routes/projects.js +149 -0
  34. package/dist/console-server/routes/projects.js.map +1 -0
  35. package/dist/console-server/routes/system.d.ts +7 -0
  36. package/dist/console-server/routes/system.d.ts.map +1 -0
  37. package/dist/console-server/routes/system.js +19 -0
  38. package/dist/console-server/routes/system.js.map +1 -0
  39. package/dist/console-server/sse/eventBus.d.ts +25 -0
  40. package/dist/console-server/sse/eventBus.d.ts.map +1 -0
  41. package/dist/console-server/sse/eventBus.js +32 -0
  42. package/dist/console-server/sse/eventBus.js.map +1 -0
  43. package/dist/console-server/watchers/cardWatcher.d.ts +9 -0
  44. package/dist/console-server/watchers/cardWatcher.d.ts.map +1 -0
  45. package/dist/console-server/watchers/cardWatcher.js +42 -0
  46. package/dist/console-server/watchers/cardWatcher.js.map +1 -0
  47. package/dist/core/skillStore.d.ts +46 -0
  48. package/dist/core/skillStore.d.ts.map +1 -0
  49. package/dist/core/skillStore.js +210 -0
  50. package/dist/core/skillStore.js.map +1 -0
  51. package/dist/core/skillStore.test.d.ts +2 -0
  52. package/dist/core/skillStore.test.d.ts.map +1 -0
  53. package/dist/core/skillStore.test.js +203 -0
  54. package/dist/core/skillStore.test.js.map +1 -0
  55. package/dist/main.js +27 -17
  56. package/dist/main.js.map +1 -1
  57. package/package.json +8 -2
  58. package/skills/architecture-decision-records/SKILL.md +207 -0
  59. package/skills/backend/SKILL.md +62 -0
  60. package/skills/backend/references/api-design.md +168 -0
  61. package/skills/backend/references/caching.md +181 -0
  62. package/skills/backend/references/data-access.md +173 -0
  63. package/skills/backend/references/layering.md +181 -0
  64. package/skills/backend/references/observability.md +190 -0
  65. package/skills/backend/references/resilience.md +201 -0
  66. package/skills/backend/references/security.md +186 -0
  67. package/skills/backend-architect/SKILL.md +119 -0
  68. package/skills/code-reviewer/SKILL.md +143 -0
  69. package/skills/coding-standards/SKILL.md +60 -0
  70. package/skills/coding-standards/references/clean-code.md +258 -0
  71. package/skills/coding-standards/references/code-review.md +192 -0
  72. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  73. package/skills/coding-standards/references/error-strategy.md +193 -0
  74. package/skills/coding-standards/references/naming.md +185 -0
  75. package/skills/coding-standards/references/tdd.md +171 -0
  76. package/skills/database/SKILL.md +53 -0
  77. package/skills/database/references/indexing.md +190 -0
  78. package/skills/database/references/migrations.md +199 -0
  79. package/skills/database/references/nosql.md +185 -0
  80. package/skills/database/references/queries.md +295 -0
  81. package/skills/database/references/scaling.md +203 -0
  82. package/skills/database/references/schema.md +191 -0
  83. package/skills/database-optimizer/SKILL.md +168 -0
  84. package/skills/debugging-workflow/SKILL.md +244 -0
  85. package/skills/devops/SKILL.md +55 -0
  86. package/skills/devops/references/ci-cd.md +204 -0
  87. package/skills/devops/references/containers.md +272 -0
  88. package/skills/devops/references/deploy.md +201 -0
  89. package/skills/devops/references/iac.md +252 -0
  90. package/skills/devops/references/observability.md +228 -0
  91. package/skills/devops/references/secrets.md +178 -0
  92. package/skills/devops-automator/SKILL.md +164 -0
  93. package/skills/frontend/SKILL.md +52 -0
  94. package/skills/frontend/references/accessibility.md +222 -0
  95. package/skills/frontend/references/components.md +206 -0
  96. package/skills/frontend/references/performance.md +219 -0
  97. package/skills/frontend/references/routing.md +209 -0
  98. package/skills/frontend/references/state.md +190 -0
  99. package/skills/frontend/references/testing.md +216 -0
  100. package/skills/frontend-developer/SKILL.md +115 -0
  101. package/skills/git-workflow/SKILL.md +355 -0
  102. package/skills/golang/SKILL.md +49 -0
  103. package/skills/golang/references/concurrency.md +284 -0
  104. package/skills/golang/references/errors.md +241 -0
  105. package/skills/golang/references/idioms.md +285 -0
  106. package/skills/golang/references/testing.md +238 -0
  107. package/skills/java/SKILL.md +50 -0
  108. package/skills/java/references/concurrency.md +194 -0
  109. package/skills/java/references/idioms.md +283 -0
  110. package/skills/java/references/testing.md +228 -0
  111. package/skills/kotlin/SKILL.md +47 -0
  112. package/skills/kotlin/references/coroutines.md +240 -0
  113. package/skills/kotlin/references/idioms.md +268 -0
  114. package/skills/kotlin/references/testing.md +219 -0
  115. package/skills/mobile/SKILL.md +50 -0
  116. package/skills/mobile/references/architecture.md +204 -0
  117. package/skills/mobile/references/navigation.md +158 -0
  118. package/skills/mobile/references/performance.md +152 -0
  119. package/skills/mobile/references/platform.md +166 -0
  120. package/skills/mobile/references/state-and-data.md +174 -0
  121. package/skills/python/SKILL.md +51 -0
  122. package/skills/python/THIRD_PARTY.md +14 -0
  123. package/skills/python/references/async.md +218 -0
  124. package/skills/python/references/error-handling.md +254 -0
  125. package/skills/python/references/idioms.md +279 -0
  126. package/skills/python/references/packaging.md +233 -0
  127. package/skills/python/references/testing.md +269 -0
  128. package/skills/python/references/typing.md +292 -0
  129. package/skills/qa-tester/SKILL.md +186 -0
  130. package/skills/rust/SKILL.md +50 -0
  131. package/skills/rust/references/async.md +224 -0
  132. package/skills/rust/references/errors.md +240 -0
  133. package/skills/rust/references/ownership.md +263 -0
  134. package/skills/rust/references/testing.md +274 -0
  135. package/skills/rust/references/traits.md +250 -0
  136. package/skills/security-engineer/SKILL.md +157 -0
  137. package/skills/swift/SKILL.md +48 -0
  138. package/skills/swift/references/concurrency.md +280 -0
  139. package/skills/swift/references/idioms.md +334 -0
  140. package/skills/swift/references/testing.md +229 -0
  141. package/skills/typescript/SKILL.md +51 -0
  142. package/skills/typescript/references/async.md +241 -0
  143. package/skills/typescript/references/errors.md +208 -0
  144. package/skills/typescript/references/idioms.md +246 -0
  145. package/skills/typescript/references/testing.md +225 -0
  146. package/skills/typescript/references/tooling.md +208 -0
  147. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,194 @@
1
+ # Java — Concurrency
2
+
3
+ Virtual threads (21+), `ExecutorService`, `CompletableFuture`, structured concurrency.
4
+
5
+ ## Virtual threads — Java 21+
6
+
7
+ Cheap threads mapped many-to-one onto OS threads. For I/O-bound work, replace platform-thread pools.
8
+
9
+ ```java
10
+ // One virtual thread per request — simple, no thread-pool sizing
11
+ var executor = Executors.newVirtualThreadPerTaskExecutor();
12
+ executor.submit(() -> handle(req));
13
+
14
+ // Or inline:
15
+ Thread.startVirtualThread(() -> handle(req));
16
+ ```
17
+
18
+ Virtual threads block cheaply. A thread per request becomes a reasonable model. `Thread.sleep`, blocking I/O (JDBC, filesystem), locks — all yield the carrier thread rather than burning it.
19
+
20
+ When NOT to use virtual threads:
21
+ - CPU-bound work (thousands of virtual threads running compute just migrate between OS threads; no gain).
22
+ - Code heavy on `synchronized` blocks (pinning problem — before Java 24 the carrier is locked; better in 24+).
23
+ - Legacy reactive code where migration is a separate project.
24
+
25
+ ## Platform threads — `ExecutorService`
26
+
27
+ For CPU-bound work or when you need explicit thread-pool sizing.
28
+
29
+ ```java
30
+ try (var exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())) {
31
+ var futures = items.stream()
32
+ .map(i -> exec.submit(() -> compute(i)))
33
+ .toList();
34
+ for (var f : futures) results.add(f.get());
35
+ }
36
+ ```
37
+
38
+ `try-with-resources` on `ExecutorService` (Java 19+) shuts it down cleanly.
39
+
40
+ Sizing:
41
+ - CPU-bound: `n = cores` or `cores + 1`.
42
+ - Mixed: `n = cores × (1 + wait/compute ratio)`.
43
+ - If you can't estimate, virtual threads take the question away.
44
+
45
+ ## `CompletableFuture` — composable async
46
+
47
+ ```java
48
+ CompletableFuture<User> userF = fetchUser(id);
49
+ CompletableFuture<Prefs> prefsF = fetchPrefs(id);
50
+
51
+ CompletableFuture<Screen> result = userF.thenCombine(prefsF, Screen::new);
52
+
53
+ result.thenAccept(screen -> render(screen))
54
+ .exceptionally(err -> { log.error(err); return null; });
55
+ ```
56
+
57
+ `supplyAsync`, `thenApply`, `thenCompose`, `thenCombine`, `allOf`, `anyOf`, `exceptionally` — the basic toolkit.
58
+
59
+ **Always** provide an explicit `Executor`:
60
+
61
+ ```java
62
+ CompletableFuture.supplyAsync(this::load, executor)
63
+ ```
64
+
65
+ Default is `ForkJoinPool.commonPool()` — OK for CPU-bound, wrong for I/O. Named executors are clearer and tune-able.
66
+
67
+ Rules:
68
+ - `.get()` blocks; use `.join()` in lambdas if you must.
69
+ - Don't mix async chains with blocking calls; stay reactive all the way or don't bother.
70
+ - With virtual threads, plain imperative code is usually preferable to `CompletableFuture` chains.
71
+
72
+ ## Structured concurrency (preview → stable)
73
+
74
+ ```java
75
+ try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
76
+ Subtask<User> userT = scope.fork(() -> fetchUser(id));
77
+ Subtask<Prefs> prefsT = scope.fork(() -> fetchPrefs(id));
78
+
79
+ scope.join().throwIfFailed();
80
+ return new Screen(userT.get(), prefsT.get());
81
+ }
82
+ ```
83
+
84
+ Ties child tasks' lifecycle to the enclosing scope. Compiler / runtime ensures all children finish (or are cancelled) before the scope exits. Pairs nicely with virtual threads.
85
+
86
+ Check your JDK version — this API has been in preview; use the stable namespace (available in 25 LTS+).
87
+
88
+ ## Locks
89
+
90
+ | Use | Tool |
91
+ |---|---|
92
+ | Short critical section | `synchronized` block |
93
+ | Read-heavy | `ReadWriteLock` / `StampedLock` |
94
+ | Atomic counter / flag | `java.util.concurrent.atomic.*` |
95
+ | One-time init | `volatile` + double-checked init, or `Holder` class pattern |
96
+
97
+ ```java
98
+ private final Object lock = new Object();
99
+ private int counter;
100
+
101
+ void inc() {
102
+ synchronized (lock) { counter++; }
103
+ }
104
+ ```
105
+
106
+ Prefer `AtomicInteger` / `AtomicReference` over mutex + primitive for simple cases.
107
+
108
+ `Lock` interface (`ReentrantLock`) for tryLock, timeouts, fairness. Don't reach for it unless you need one of those.
109
+
110
+ ## Concurrent collections
111
+
112
+ | Type | Use |
113
+ |---|---|
114
+ | `ConcurrentHashMap<K,V>` | Thread-safe map; scales to many cores |
115
+ | `CopyOnWriteArrayList<T>` | Mostly read, rarely written; snapshots |
116
+ | `BlockingQueue<T>` (`LinkedBlockingQueue`, etc.) | Producer / consumer |
117
+ | `ConcurrentLinkedQueue<T>` | Unbounded lock-free queue |
118
+
119
+ Don't wrap a regular `HashMap` in `Collections.synchronizedMap` and call it concurrent — it serializes every operation.
120
+
121
+ ## `volatile`
122
+
123
+ Publishes writes to other threads. Does NOT make compound operations atomic.
124
+
125
+ ```java
126
+ private volatile boolean running = true;
127
+ // other thread can see running = false without extra sync
128
+ ```
129
+
130
+ Don't use `volatile` for counters. Use `AtomicInteger` / `AtomicLong` — `i++` is not atomic on `volatile int`.
131
+
132
+ ## `ThreadLocal`
133
+
134
+ Per-thread storage. With platform threads + thread pools, a `ThreadLocal` can leak across requests because threads are reused. Use `try` / `finally` to clean up, or switch to `ScopedValue` (Java 21+).
135
+
136
+ ```java
137
+ private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
138
+
139
+ ScopedValue.where(CURRENT_USER, user).run(() -> {
140
+ // CURRENT_USER.get() is user for the duration of this block
141
+ });
142
+ ```
143
+
144
+ `ScopedValue` is immutable, bound by lexical scope, plays well with virtual threads.
145
+
146
+ ## Avoid blocking in async frameworks
147
+
148
+ If you're on Reactor / RxJava / Mutiny (Quarkus), a blocking call on an event loop stalls the whole world. Mark blocking operations for a different scheduler:
149
+
150
+ ```java
151
+ Mono.fromCallable(this::blockingThing).subscribeOn(Schedulers.boundedElastic());
152
+ ```
153
+
154
+ Or move to virtual threads and stop needing reactive at all.
155
+
156
+ ## Shutdown hooks
157
+
158
+ ```java
159
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
160
+ exec.shutdown();
161
+ try { exec.awaitTermination(30, TimeUnit.SECONDS); } catch (Exception ignored) {}
162
+ db.close();
163
+ }));
164
+ ```
165
+
166
+ Web frameworks do most of this for you. In plain-Java apps, do it explicitly.
167
+
168
+ ## Timeouts
169
+
170
+ Every network / DB call has a timeout. Defaults are usually "infinite" or "very long".
171
+
172
+ ```java
173
+ // HttpClient
174
+ var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
175
+ var req = HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(5)).GET().build();
176
+
177
+ // JDBC
178
+ statement.setQueryTimeout(5);
179
+ ```
180
+
181
+ See `backend/references/resilience.md` for the full budget model.
182
+
183
+ ## Anti-patterns
184
+
185
+ | Anti-pattern | Fix |
186
+ |---|---|
187
+ | `Thread.sleep` in request handlers | Use scheduled executors; or `VirtualThread.sleep` if truly needed |
188
+ | `new Thread(...).start()` for each task | Use an `ExecutorService` |
189
+ | `synchronized` across I/O calls | Hold locks briefly; never across network |
190
+ | Catching `InterruptedException` and discarding | `Thread.currentThread().interrupt()` before returning, or rethrow |
191
+ | `CompletableFuture` chains without an explicit executor | Always specify |
192
+ | `.get()` without timeout | `.get(timeout, unit)` or restructure async |
193
+ | Mutable shared state without synchronization | Lock it, use concurrent collections, or use an actor-ish pattern |
194
+ | `Future.cancel(true)` assumed to stop blocking I/O | Cancel interrupts; blocking calls may not honour it |
@@ -0,0 +1,283 @@
1
+ # Java — Idioms
2
+
3
+ Records, sealed types, streams, `Optional`, modern collections.
4
+
5
+ ## Records — data classes done right
6
+
7
+ ```java
8
+ public record User(String id, String email, boolean active) {}
9
+
10
+ var u = new User("u1", "a@x.com", true);
11
+ u.email(); // accessor
12
+ new User("u1", "a@x.com", false).equals(u); // false — structural equality
13
+ ```
14
+
15
+ Records give you `equals`, `hashCode`, `toString`, and component accessors for free.
16
+
17
+ Compact canonical constructor for validation:
18
+
19
+ ```java
20
+ public record Email(String value) {
21
+ public Email {
22
+ if (!value.contains("@")) throw new IllegalArgumentException("bad email");
23
+ }
24
+ }
25
+ ```
26
+
27
+ Don't use records when you need mutation, inheritance, or behaviour beyond data + validation.
28
+
29
+ ## Sealed types + pattern switch
30
+
31
+ Model sum types exhaustively.
32
+
33
+ ```java
34
+ public sealed interface Result<T>
35
+ permits Result.Ok, Result.Err {
36
+ record Ok<T>(T value) implements Result<T> {}
37
+ record Err<T>(String error) implements Result<T> {}
38
+ }
39
+
40
+ String handle(Result<User> r) {
41
+ return switch (r) {
42
+ case Result.Ok(var u) -> "got " + u.email();
43
+ case Result.Err(var msg) -> "fail " + msg;
44
+ }; // no default — compiler enforces exhaustiveness
45
+ }
46
+ ```
47
+
48
+ `permits` limits implementers to the named classes. Add a new variant → every `switch` breaks until updated.
49
+
50
+ ## `var` — locals only
51
+
52
+ ```java
53
+ var users = userRepo.findAll(); // List<User>
54
+ var map = new HashMap<String, List<Order>>();
55
+ ```
56
+
57
+ Don't use `var` in method signatures, field declarations, or where the inferred type would surprise a reader.
58
+
59
+ ```java
60
+ // ❌ — what is x?
61
+ var x = svc.doThing();
62
+
63
+ // ✅ keep the type when not obvious
64
+ List<User> users = svc.loadUsers();
65
+ ```
66
+
67
+ ## `Optional<T>`
68
+
69
+ For **return values** that might legitimately be absent.
70
+
71
+ ```java
72
+ Optional<User> findById(String id) { ... }
73
+
74
+ findById(id)
75
+ .map(User::email)
76
+ .orElseThrow(() -> new NotFoundException("no user " + id));
77
+ ```
78
+
79
+ Never:
80
+ - `Optional` as a field type (serialization headache, mutable wrapper)
81
+ - `Optional` as a method parameter (overloads are clearer)
82
+ - `Optional.get()` without `isPresent()` (same bug as `unwrap`)
83
+
84
+ ## Collections
85
+
86
+ ```java
87
+ List<String> immutable = List.of("a", "b", "c");
88
+ Map<String, Integer> m = Map.of("a", 1, "b", 2);
89
+ Set<String> s = Set.of("x", "y");
90
+
91
+ var mutable = new ArrayList<String>();
92
+ mutable.add("a");
93
+
94
+ var copy = List.copyOf(mutable); // defensive copy, unmodifiable
95
+ ```
96
+
97
+ Prefer `List.of` / `Map.of` for small known data. Prefer `ArrayList` / `HashMap` for mutable.
98
+
99
+ ## Streams
100
+
101
+ Concise collection transformations.
102
+
103
+ ```java
104
+ var emails = users.stream()
105
+ .filter(User::active)
106
+ .map(User::email)
107
+ .toList();
108
+
109
+ var byRole = users.stream()
110
+ .collect(Collectors.groupingBy(User::role));
111
+
112
+ var count = users.stream().filter(User::active).count();
113
+ ```
114
+
115
+ Rules:
116
+ - `toList()` (Java 16+) over `collect(Collectors.toList())`.
117
+ - Don't use streams for simple for-loops with side effects — the loop is clearer.
118
+ - Don't mix stream and `forEach` for state mutation; prefer `collect` / `reduce`.
119
+
120
+ Stream of a single op doesn't improve readability:
121
+
122
+ ```java
123
+ // ❌
124
+ list.stream().filter(p).findFirst().orElse(null);
125
+
126
+ // ✅
127
+ list.stream().filter(p).findFirst().orElseThrow();
128
+ // or use a library method if it exists
129
+ ```
130
+
131
+ ## `switch` expressions
132
+
133
+ ```java
134
+ String label = switch (status) {
135
+ case ACTIVE -> "active";
136
+ case PENDING -> "pending";
137
+ case BANNED -> "banned";
138
+ };
139
+
140
+ // Pattern matching (preview/stable depending on JDK)
141
+ var description = switch (shape) {
142
+ case Circle c -> "circle r=" + c.radius();
143
+ case Square s -> "square s=" + s.side();
144
+ case null, default -> "unknown";
145
+ };
146
+ ```
147
+
148
+ Arrow `->` form has no fall-through. Traditional `case X:` has fall-through — avoid.
149
+
150
+ ## Exceptions
151
+
152
+ - **Checked** (`extends Exception`) — recoverable, documented in signature.
153
+ - **Unchecked** (`extends RuntimeException`) — programmer errors, boundary validation.
154
+
155
+ ```java
156
+ public class ValidationException extends RuntimeException {
157
+ private final String field;
158
+ public ValidationException(String field, String msg) {
159
+ super(msg);
160
+ this.field = field;
161
+ }
162
+ public String field() { return field; }
163
+ }
164
+ ```
165
+
166
+ Chain the cause:
167
+
168
+ ```java
169
+ try { ... }
170
+ catch (IOException e) {
171
+ throw new ConfigException("loading " + path, e); // ← pass e as cause
172
+ }
173
+ ```
174
+
175
+ Never catch `Throwable` / `Exception` and swallow. Catch specifics, log, handle or rethrow.
176
+
177
+ ## `final` — for invariants
178
+
179
+ - On class: no subclasses.
180
+ - On method: can't override.
181
+ - On field: immutable reference.
182
+ - On local / param: can't reassign (noisy; `var` encourages it implicitly).
183
+
184
+ Records' components are implicitly final.
185
+
186
+ Don't sprinkle `final` on every local — IDEs and modern reviewers treat it as clutter unless the team convention says otherwise.
187
+
188
+ ## Nullability
189
+
190
+ Java doesn't have built-in non-null types. Options:
191
+
192
+ - **`@Nullable` / `@NonNull`** from JSR-305, JetBrains, or Checker Framework.
193
+ - **Records + `@NotNull` in canonical constructor validation.**
194
+ - **Kotlin interop** — declare nullability to help Kotlin callers.
195
+
196
+ Pick one annotation set; configure your IDE + CI to enforce it.
197
+
198
+ ## `java.time.*` — not `Date`
199
+
200
+ ```java
201
+ var now = Instant.now();
202
+ var today = LocalDate.now();
203
+ var inZone = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
204
+ var later = now.plus(Duration.ofMinutes(30));
205
+
206
+ Instant.parse("2026-04-21T10:00:00Z");
207
+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(today);
208
+ ```
209
+
210
+ `java.util.Date` and `Calendar` are deprecated in spirit; every use is a code smell in new code.
211
+
212
+ ## Equality
213
+
214
+ - `equals()` for value equality; `==` for reference equality.
215
+ - `record` gets `equals` automatically.
216
+ - For classes, use IDE / Lombok / `EqualsBuilder` — never hand-write `equals` without `hashCode` to match.
217
+
218
+ ```java
219
+ Objects.equals(a, b); // null-safe
220
+ Objects.hash(field1, field2);
221
+ ```
222
+
223
+ ## String
224
+
225
+ ```java
226
+ String s = "hi";
227
+ String formatted = "user %s (%d)".formatted(name, count); // Java 15+
228
+ String multiline = """
229
+ Hello
230
+ %s
231
+ """.formatted(name); // text blocks (15+)
232
+
233
+ var sb = new StringBuilder(); // for building in loops
234
+ ```
235
+
236
+ `String.format` for static strings is fine. `"...".formatted(...)` reads better.
237
+
238
+ `String.repeat(n)`, `String.strip()` (vs `trim()` which is ASCII-only) are modern.
239
+
240
+ ## Lambdas vs. method references
241
+
242
+ ```java
243
+ users.stream().map(u -> u.email()); // lambda
244
+ users.stream().map(User::email); // method ref — preferred when trivial
245
+
246
+ users.stream().filter(u -> u.active());
247
+ users.stream().filter(User::active);
248
+
249
+ // Constructor reference
250
+ users.stream().map(name -> new User(name)).toList();
251
+ users.stream().map(User::new).toList();
252
+ ```
253
+
254
+ Method references when the operation is a direct member access. Lambdas when there's any extra logic.
255
+
256
+ ## Builder pattern
257
+
258
+ For types with many optional fields, use a builder. Lombok provides `@Builder`; libraries like Immutables generate it.
259
+
260
+ ```java
261
+ var req = HttpRequest.builder()
262
+ .url("https://x.com")
263
+ .timeout(Duration.ofSeconds(5))
264
+ .header("X-Auth", token)
265
+ .build();
266
+ ```
267
+
268
+ For records, a static factory + named params via builder is common.
269
+
270
+ ## Anti-patterns
271
+
272
+ | Anti-pattern | Fix |
273
+ |---|---|
274
+ | `public` mutable fields | Getters or records |
275
+ | `if (x == null) throw new NullPointerException(...)` everywhere | Non-null annotations + validate at boundary |
276
+ | `instanceof x + cast` chains | Pattern switch / sealed types |
277
+ | Anonymous inner class for a SAM | Lambda |
278
+ | `Collections.unmodifiableList(new ArrayList<>(...))` | `List.copyOf(...)` |
279
+ | Hand-rolled `equals` / `hashCode` on data classes | Record or generated code |
280
+ | Catching `Exception e` to "simplify" | Catch specific; let unknown propagate |
281
+ | `System.out.println` in production | Use SLF4J / `java.util.logging` |
282
+ | `Thread.currentThread().sleep(...)` | `Thread.sleep(...)` — static method; use as such |
283
+ | `new Integer(x)` / `new String(x)` | Use valueOf / literal |
@@ -0,0 +1,228 @@
1
+ # Java — Testing
2
+
3
+ JUnit 5, AssertJ, Mockito, Testcontainers. For TDD, see `coding-standards/references/tdd.md`.
4
+
5
+ ## JUnit 5 — the default
6
+
7
+ ```java
8
+ import org.junit.jupiter.api.*;
9
+ import static org.junit.jupiter.api.Assertions.*;
10
+
11
+ class UserServiceTest {
12
+ private UserService service;
13
+
14
+ @BeforeEach
15
+ void setUp() { service = new UserService(new InMemoryUserRepo()); }
16
+
17
+ @Test
18
+ @DisplayName("creates a user with a generated id")
19
+ void create_returnsPersistedUser() {
20
+ var u = service.create("A", "a@x.com");
21
+ assertEquals("A", u.name());
22
+ assertNotNull(u.id());
23
+ }
24
+
25
+ @Test
26
+ void create_rejectsEmptyEmail() {
27
+ assertThrows(ValidationException.class, () -> service.create("A", ""));
28
+ }
29
+ }
30
+ ```
31
+
32
+ Use `@DisplayName` for behaviour-focused names that don't fit Java method-name rules.
33
+
34
+ ## AssertJ — fluent assertions
35
+
36
+ ```java
37
+ import static org.assertj.core.api.Assertions.*;
38
+
39
+ assertThat(users)
40
+ .hasSize(3)
41
+ .extracting(User::email)
42
+ .contains("a@x.com")
43
+ .doesNotContain("banned@x.com");
44
+
45
+ assertThat(user.role()).isEqualTo(Role.ADMIN);
46
+
47
+ assertThatThrownBy(() -> service.create("A", ""))
48
+ .isInstanceOf(ValidationException.class)
49
+ .hasMessageContaining("email");
50
+ ```
51
+
52
+ Much richer than JUnit's built-ins. Most Java codebases use AssertJ or Hamcrest. Pick one per project.
53
+
54
+ ## Mockito — when you need mocks
55
+
56
+ Fakes > mocks (see `coding-standards` / `typescript`/`python` refs). But Mockito works when a fake is too heavy.
57
+
58
+ ```java
59
+ import static org.mockito.Mockito.*;
60
+
61
+ @Test
62
+ void loadsFromRepo() {
63
+ var repo = mock(UserRepository.class);
64
+ when(repo.find("u1")).thenReturn(Optional.of(new User("u1", "a@x.com")));
65
+
66
+ var service = new UserService(repo);
67
+ assertEquals("a@x.com", service.getEmail("u1"));
68
+
69
+ verify(repo, times(1)).find("u1");
70
+ }
71
+ ```
72
+
73
+ `@Mock` + `@InjectMocks` for less boilerplate; enable with `@ExtendWith(MockitoExtension.class)`.
74
+
75
+ Don't mock types you own — change them instead. Mock boundaries (HTTP clients, clocks, external APIs).
76
+
77
+ ## Parametrized tests
78
+
79
+ ```java
80
+ @ParameterizedTest
81
+ @CsvSource({
82
+ "1, 2, 3",
83
+ "0, 0, 0",
84
+ "-1, 1, 0",
85
+ })
86
+ void add(int a, int b, int expected) {
87
+ assertEquals(expected, Math.addExact(a, b));
88
+ }
89
+
90
+ @ParameterizedTest
91
+ @EnumSource(Role.class)
92
+ void allRolesHaveLabel(Role r) {
93
+ assertNotNull(r.label());
94
+ }
95
+
96
+ @ParameterizedTest
97
+ @MethodSource("loginCases")
98
+ void login(LoginCase c) { ... }
99
+ static Stream<LoginCase> loginCases() { ... }
100
+ ```
101
+
102
+ ## Lifecycle annotations
103
+
104
+ | Annotation | Runs |
105
+ |---|---|
106
+ | `@BeforeEach` | Before each `@Test` |
107
+ | `@AfterEach` | After each `@Test` |
108
+ | `@BeforeAll` | Once before any test (static) |
109
+ | `@AfterAll` | Once after all tests (static) |
110
+ | `@Nested` class | Group related tests, fresh fixtures |
111
+
112
+ `@TestInstance(Lifecycle.PER_CLASS)` if you want non-static `@BeforeAll` or shared state.
113
+
114
+ ## `@Nested` for readability
115
+
116
+ ```java
117
+ @Nested
118
+ @DisplayName("when user is banned")
119
+ class WhenBanned {
120
+ @BeforeEach void setUp() { user = userWithStatus(BANNED); }
121
+
122
+ @Test void cannotLogin() { ... }
123
+ @Test void cannotPost() { ... }
124
+ }
125
+ ```
126
+
127
+ Reads as a spec. Tests stay organized without helper functions exploding the file.
128
+
129
+ ## Integration tests — Testcontainers
130
+
131
+ Real dependencies, Docker-driven, ephemeral.
132
+
133
+ ```java
134
+ @Testcontainers
135
+ class UserRepoIT {
136
+ @Container
137
+ static final PostgreSQLContainer<?> PG =
138
+ new PostgreSQLContainer<>("postgres:16-alpine");
139
+
140
+ @Test
141
+ void savesAndLoads() throws Exception {
142
+ var ds = dataSource(PG.getJdbcUrl(), PG.getUsername(), PG.getPassword());
143
+ var repo = new JdbcUserRepository(ds);
144
+ repo.save(new User("u1", "a@x.com"));
145
+ assertThat(repo.find("u1")).isPresent();
146
+ }
147
+ }
148
+ ```
149
+
150
+ Split integration tests (`*IT.java`) from unit tests (`*Test.java`) in the build so you don't run them every time.
151
+
152
+ ## Spring Boot tests
153
+
154
+ Layered:
155
+
156
+ ```java
157
+ @WebMvcTest(UserController.class) // only web layer + @MockBean repo
158
+ class UserControllerTest { ... }
159
+
160
+ @DataJpaTest // only JPA + embedded DB
161
+ class UserRepoTest { ... }
162
+
163
+ @SpringBootTest // full app context
164
+ class UserIntegrationTest { ... }
165
+ ```
166
+
167
+ Prefer `@WebMvcTest` / `@DataJpaTest` over `@SpringBootTest` — they load a slice, are much faster.
168
+
169
+ `@TestContainers` + `@SpringBootTest` for real-DB integration tests on the production stack.
170
+
171
+ ## Async tests
172
+
173
+ ```java
174
+ @Test
175
+ void futureCompletes() throws Exception {
176
+ var f = asyncService.load(id);
177
+ assertEquals("a@x.com", f.get(5, TimeUnit.SECONDS).email());
178
+ }
179
+ ```
180
+
181
+ For streams/flows, use Awaitility:
182
+
183
+ ```java
184
+ import static org.awaitility.Awaitility.await;
185
+
186
+ await().atMost(5, SECONDS).until(() -> queue.size() > 0);
187
+ ```
188
+
189
+ ## Property tests — jqwik
190
+
191
+ ```java
192
+ @Property
193
+ boolean sortIsIdempotent(@ForAll List<@IntRange(min = -1000, max = 1000) Integer> xs) {
194
+ var sorted = xs.stream().sorted().toList();
195
+ var twice = sorted.stream().sorted().toList();
196
+ return sorted.equals(twice);
197
+ }
198
+ ```
199
+
200
+ Niche but valuable for parsers, invariants.
201
+
202
+ ## Coverage
203
+
204
+ JaCoCo is the default. Set a threshold in Maven / Gradle and fail CI on drop.
205
+
206
+ ```xml
207
+ <!-- Maven: jacoco-maven-plugin -->
208
+ <rule>
209
+ <element>BUNDLE</element>
210
+ <limits>
211
+ <limit><counter>LINE</counter><minimum>0.80</minimum></limit>
212
+ </limits>
213
+ </rule>
214
+ ```
215
+
216
+ ## Anti-patterns
217
+
218
+ | Anti-pattern | Fix |
219
+ |---|---|
220
+ | `Thread.sleep` to wait for async | Awaitility / explicit futures |
221
+ | `@Test(expected = ...)` (JUnit 4) | JUnit 5: `assertThrows` |
222
+ | Mocking what you don't own without wrapping | Create a wrapper type; mock the wrapper |
223
+ | Tests that load full Spring context for a pure-logic test | `@WebMvcTest` / plain JUnit |
224
+ | Mockito `any()` for every arg | Assert specific args — catches bugs |
225
+ | Mocking final classes without Mockito inline extension | Enable `mockito-inline` or refactor to an interface |
226
+ | Shared mutable static state across tests | `@BeforeEach` reset or move state into instance |
227
+ | Snapshot / text asserts for time-dependent output | Inject a clock |
228
+ | Tests named `test1`, `test2` | Describe behaviour — `create_rejectsEmptyEmail` |