gnodet-bot commented on code in PR #29805:
URL: https://github.com/apache/camel/pull/29805#discussion_r4565520157


##########
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/UserConfigDefaultsTest.java:
##########
@@ -78,6 +78,68 @@ void localConfigurationTakesPrecedence() throws Exception {
         assertThat(run("echo")).isEqualTo("sort=age repos=null");
     }
 
+    @Test
+    void malformedUserConfigDoesNotCrashCli() throws Exception {
+        // A Windows-style path stored without escaping produces a malformed 
\\uXXXX sequence,
+        // which throws IllegalArgumentException from Properties.load — not 
IOException.
+        // The CLI must warn and continue instead of propagating the exception.
+        UserConfigHelper.createUserConfig("path=C:\\users\\name");
+
+        // The command must succeed (not throw); the option falls back to its 
own default.
+        assertThat(runCapturingWarning("echo")).isEqualTo("sort=pid 
repos=null");
+    }
+
+    @Test
+    void malformedConfigLoadPropertiesReturnsFalseAndWarns() throws Exception {
+        // loadProperties must not throw and must return false for a malformed 
config file
+        UserConfigHelper.createUserConfig("path=C:\\users\\name");
+
+        StringPrinter out = new StringPrinter();
+        boolean[] loaded = { true };
+        loaded[0] = CommandLineHelper.loadProperties(props -> {
+            // should NOT be called since the 3-arg overload does not call 
consumer on failure

Review Comment:
   ⚠️ **Test logic bug**: This test doesn't actually verify that the consumer 
is not called.
   
   In Java, `loaded[0] = CommandLineHelper.loadProperties(lambda, false, out)` 
evaluates the full method call (including any lambda invocations) first, then 
assigns the return value. So:
   1. `loadProperties` runs — if it calls the lambda, `loaded[0]` is set to 
`true`
   2. `loadProperties` returns `false`
   3. `false` is assigned to `loaded[0]`, **overwriting** whatever the lambda 
set
   
   The final `assertThat(loaded[0]).isFalse()` only verifies the return value 
of `loadProperties`, not whether the consumer was called. The test comment says 
`// should NOT be called` but the assertion doesn't enforce that.
   
   A correct approach would use a separate flag:
   ```suggestion
           boolean[] consumerCalled = { false };
           loaded[0] = CommandLineHelper.loadProperties(props -> {
               // should NOT be called since the 3-arg overload does not call 
consumer on failure
               consumerCalled[0] = true;
           }, false, out);
   ```
   Then assert `assertThat(consumerCalled[0]).as("consumer must not be called 
on failure").isFalse();`



##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java:
##########
@@ -813,28 +853,65 @@ public static JsonObject createPluginConfig(Path 
configPath) {
             if (parent != null) {
                 Files.createDirectories(parent);
             }
-            Files.writeString(f, config.toJson(),
-                    StandardOpenOption.CREATE,
-                    StandardOpenOption.WRITE,
-                    StandardOpenOption.TRUNCATE_EXISTING);
+            atomicWriteJson(f, config.toJson());
         } catch (IOException e) {
             throw new RuntimeCamelException("Failed to create plugin 
configuration", e);
         }
 
         return config;
     }
 
+    /**
+     * Writes {@code content} to {@code target} atomically: writes to a 
sibling temp file first, then moves it into
+     * place with {@link StandardCopyOption#REPLACE_EXISTING}. When atomic 
move is not supported by the underlying
+     * filesystem (e.g. across mounts), falls back to a plain overwriting 
write, which is still safer than
+     * truncate-then-write because the write is a single syscall.
+     */

Review Comment:
   💬 **Nitpick** (Javadoc accuracy): The fallback path description has two 
minor inaccuracies:
   
   1. `AtomicMoveNotSupportedException` is NOT thrown for cross-mount moves — 
the "e.g. across mounts" example is misleading. It fires on exotic filesystems 
that don't support the atomic rename flag (some NFS/FUSE configurations).
   2. Since the temp file is always created in the **same directory** as the 
target (`Files.createTempFile(dir, ...)`), the fallback 
`Files.move(REPLACE_EXISTING)` will always use `rename(2)` — so the safety 
claim is correct, but the explanation attributes it to the wrong reason.
   
   Suggested reword: `"falls back to a plain overwriting move (rename(2)), 
which is still safe — a reader never observes a half-written file."` (The "e.g. 
across mounts" example should be dropped or corrected.)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to