Skip to content

Model#14

Closed
Rickank wants to merge 21 commits intomainfrom
model
Closed

Model#14
Rickank wants to merge 21 commits intomainfrom
model

Conversation

@Rickank
Copy link

@Rickank Rickank commented Nov 13, 2025

test

Summary by CodeRabbit

  • New Features

    • Real-time messaging UI: message list with timestamps, input area, Send action, auto-scroll, and user-facing error alerts
    • Wider application window and improved async send/receive for live updates
  • Tests

    • Comprehensive test suite covering send/receive flows, edge cases and end-to-end server interaction
  • Chores

    • .env added to ignore list and environment-config support
    • Project dependencies updated/added for runtime and testing

@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Walkthrough

Adds an ntfy messaging feature with interface and HTTP implementation, integrates dotenv and Jackson, converts the greeting app into a stateful JavaFX chat (model, controller, FXML), adds builders and a singleton, updates module/pom/.gitignore, and introduces unit and integration tests with a test spy.

Changes

Cohort / File(s) Summary
Project configuration
pom.xml, .gitignore, src/main/java/module-info.java
Add dependencies: io.github.cdimascio:dotenv-java:3.2.0, tools.jackson.core:jackson-databind:3.0.1, test deps org.wiremock:wiremock:4.0.0-beta.15, org.slf4j:slf4j-simple:2.0.12. Add .env to .gitignore. Update module-info to require tools.jackson.databind, io.github.cdimascio.dotenv.java, and java.net.http.
Ntfy messaging core
src/main/java/com/example/NtfyConnection.java, src/main/java/com/example/NtfyConnectionImpl.java, src/main/java/com/example/NtfyMessageDto.java
Add NtfyConnection interface (async send with callback, receive handler). Add NtfyConnectionImpl implementing async POST send and streaming GET receive, using Dotenv for host and Jackson for JSON mapping. Add NtfyMessageDto record annotated with @JsonIgnoreProperties.
Model & state management
src/main/java/com/example/HelloModel.java
Replace stateless greeting with stateful model: ObservableList<NtfyMessageDto> messages, StringProperty messageToSend, constructor HelloModel(NtfyConnection), sendMessageAsync with validation and callbacks, startReceiving, and UI-thread synchronization (Platform.runLater).
JavaFX UI layer
src/main/java/com/example/HelloController.java, src/main/java/com/example/HelloFX.java, src/main/resources/com/example/hello-view.fxml
Replace single-label UI with VBox containing ListView, TextArea, and Send button; controller binds model, sets cell factory with timestamp formatting, auto-scrolls on additions, and handles async send with error alert. Window width increased from 640 to 1280.
Utility / builders / singleton
src/main/java/com/example/ManyParameters.java, src/main/java/com/example/ManyParametersBuilder.java, src/main/java/com/example/Singelton.java
Add ManyParameters class (5-arg constructor), ManyParametersBuilder fluent setters and createManyParameters(), and a simple Singelton class with private constructor and getInstance().
Tests & test doubles
src/test/java/com/example/HelloModelTest.java, src/test/java/com/example/NtfyConnectionSpy.java
Add HelloModelTest covering send/receive, validation, error paths, and WireMock integration. Add NtfyConnectionSpy test double capturing sends and simulating incoming messages.

Sequence Diagram(s)

sequenceDiagram
    participant U as User (UI)
    participant C as HelloController
    participant M as HelloModel
    participant NC as NtfyConnectionImpl
    participant S as HTTP Server

    U->>C: enter text & click Send
    C->>M: sendMessageAsync()
    activate M
    M->>M: validate input
    alt valid
        M->>NC: send(message, callback)
        activate NC
        NC->>S: POST /mytopic (message)
        S-->>NC: 2xx / non-2xx
        NC-->>M: callback(true/false)
        deactivate NC
        alt success
            M->>M: clear input
            M->>C: model listener -> UI update
            C->>U: ListView updates & auto-scroll
        else failure
            M-->>C: report failure
            C->>U: show error alert
        end
    else invalid
        M-->>C: ignore / reject
    end
    deactivate M

    par receive stream
        NC->>S: GET /mytopic/json (stream)
        S-->>NC: lines of JSON
        loop each line
            NC->>NC: parse JSON -> NtfyMessageDto
            NC->>M: handler.accept(dto)
            M->>M: Platform.runLater -> messages.add(dto)
            M->>C: notify listeners
            C->>U: append to ListView
        end
    end
Loading
sequenceDiagram
    participant T as Test
    participant Spy as NtfyConnectionSpy
    participant M as HelloModel

    T->>M: new HelloModel(Spy)
    T->>M: set messageToSend & sendMessageAsync(cb)
    M->>Spy: send(message, callback)
    Spy-->>M: callback(true)
    M-->>T: callback(true)
    T->>Spy: simulateIncoming(dto)
    Spy->>M: handler.accept(dto)
    M->>M: Platform.runLater -> messages.add(dto)
    T->>M: assert getMessages() contains dto
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files needing extra attention:
    • src/main/java/com/example/NtfyConnectionImpl.java — async HTTP send/receive, streaming lines, JSON parsing, timeouts and error logging.
    • src/main/java/com/example/HelloModel.java — concurrency (Platform.runLater), callback handling, validation and lifecycle of receiving.
    • src/main/java/com/example/HelloController.java and src/main/resources/com/example/hello-view.fxml — UI bindings, cell factory formatting, auto-scroll and alert behavior.
    • src/test/java/com/example/HelloModelTest.java — timing-sensitive tests and WireMock integration reliability.

Poem

🐰 I hop through code with tiny paws,
dotenv finds the host that calls,
messages stream and UI gleams,
spies confirm the testing schemes,
a rabbit cheers — the chat now brawls.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.64% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'Model' is vague and generic, providing no meaningful information about the changeset despite substantial changes across multiple files including UI refactoring, new dependencies, and messaging functionality. Use a more descriptive title that captures the main change, such as 'Implement messaging model with Ntfy integration' or 'Add chat model and UI components for message handling'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (5)
src/main/java/com/example/ManyParametersBuilder.java (2)

35-37: Consider adding validation before construction.

The builder allows createManyParameters() to be called without setting required fields. Fields like computerName and method could be null, and numeric fields could have invalid values. Consider adding validation to ensure the constructed object is in a valid state.

Example validation:

public ManyParameters createManyParameters() {
    if (computerName == null || computerName.isEmpty()) {
        throw new IllegalStateException("computerName must be set");
    }
    if (method == null || method.isEmpty()) {
        throw new IllegalStateException("method must be set");
    }
    if (timeout < 0) {
        throw new IllegalStateException("timeout must be non-negative");
    }
    return new ManyParameters(computerName, timeout, method, size, data);
}

30-33: Consider defensive copying of the byte array.

The data parameter is stored directly without cloning. If callers retain a reference to the array, they can modify it after passing it to the builder, potentially causing unexpected behavior. Consider cloning the array to prevent external modification.

Apply this diff to add defensive copying:

     public ManyParametersBuilder setData(byte[] data) {
-        this.data = data;
+        this.data = data != null ? data.clone() : null;
         return this;
     }
src/main/java/com/example/NtfyConnection.java (1)

7-9: Remove redundant public modifiers.

Interface methods are implicitly public in Java, so the explicit public modifiers are redundant.

Apply this diff to simplify the interface:

-    public boolean send(String message);
+    boolean send(String message);
 
-    public void receive(Consumer<NtfyMessageDto> messageHandler);
+    void receive(Consumer<NtfyMessageDto> messageHandler);
src/main/java/com/example/NtfyMessageDto.java (1)

5-7: Consider null-safety for String fields.

The record allows null values for id, event, topic, and message fields. Downstream code in HelloModel.receiveMessage() (lines 68-70) already validates message, but other fields could be null if the JSON payload is incomplete.

Consider adding validation or using annotations like @JsonProperty(required = true) to ensure required fields are present during deserialization.

src/test/java/com/example/HelloModelTest.java (1)

69-127: Consider consolidating duplicate test cases.

The tests shouldRejectBlankMessages, shouldFailWhenSendingEmptyText, and shouldFailWhenSendingNullMessage overlap significantly. The first test already covers both empty strings and null inputs (lines 73-88).

Consider removing the duplicate tests or converting them to a @ParameterizedTest to reduce maintenance overhead:

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {"  ", "\t", "\n"})
void shouldRejectInvalidMessages(String input) throws InterruptedException {
    // Test implementation
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c51e8 and bf7e2bf.

📒 Files selected for processing (15)
  • .gitignore (1 hunks)
  • pom.xml (3 hunks)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloFX.java (1 hunks)
  • src/main/java/com/example/HelloModel.java (1 hunks)
  • src/main/java/com/example/ManyParameters.java (1 hunks)
  • src/main/java/com/example/ManyParametersBuilder.java (1 hunks)
  • src/main/java/com/example/NtfyConnection.java (1 hunks)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/main/java/com/example/NtfyMessageDto.java (1 hunks)
  • src/main/java/com/example/Singelton.java (1 hunks)
  • src/main/java/module-info.java (1 hunks)
  • src/main/resources/com/example/hello-view.fxml (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
  • src/test/java/com/example/NtfyConnectionSpy.java (1 hunks)
🔇 Additional comments (12)
src/main/java/com/example/HelloFX.java (1)

15-15: LGTM! Scene width updated to accommodate the new messaging UI.

The increased width (1280) provides adequate space for the ListView and input controls added in the FXML.

.gitignore (1)

3-3: LGTM! Correctly excludes environment configuration.

Adding .env to gitignore is the right approach for keeping sensitive configuration (such as API keys and endpoints) out of version control.

src/main/java/module-info.java (1)

4-6: LGTM! Module dependencies correctly declared.

The added dependencies (Jackson for JSON, dotenv for configuration, and HTTP client) align with the Ntfy messaging implementation requirements.

src/main/resources/com/example/hello-view.fxml (1)

7-30: LGTM! Well-structured FXML layout.

The VBox-based layout with ListView, TextArea, and Button is properly configured with appropriate spacing, padding, and growth settings. The control IDs and event bindings align correctly with the controller implementation.

pom.xml (2)

12-12: Verify Java 25 compatibility.

Java 25 is set as the compiler release target, which is very recent. Ensure that all developers and CI/CD environments have access to this version, or consider using a more widely available LTS version like Java 21.


48-57: LGTM! Dependencies added for messaging feature.

The dotenv-java and jackson-databind dependencies are appropriate for environment-based configuration and JSON processing in the Ntfy messaging implementation.

src/main/java/com/example/HelloController.java (2)

43-64: Well-implemented message display and auto-scroll.

The custom cell factory with timestamp formatting and the auto-scroll functionality provide a good user experience. The use of Platform.runLater ensures thread-safe UI updates.


67-70: LGTM! Clean message sending implementation.

The sendMessage handler correctly delegates to the model and clears the input field.

src/test/java/com/example/NtfyConnectionSpy.java (1)

5-24: LGTM! Clean test spy implementation.

The test spy correctly implements the NtfyConnection interface with appropriate behavior for testing: capturing sent messages and simulating incoming messages through a registered handler.

src/main/java/com/example/HelloModel.java (1)

66-73: Excellent use of Platform.runLater for thread-safe UI updates.

The incoming message handler correctly marshals updates to the JavaFX Application Thread using Platform.runLater(), preventing potential thread-safety violations when modifying the observable list.

src/test/java/com/example/HelloModelTest.java (2)

20-25: Good JavaFX Platform initialization pattern.

The @BeforeAll setup correctly initializes the JavaFX Platform for tests that require JavaFX thread operations, preventing IllegalStateException when using Platform.runLater().


269-283: Excellent integration test with WireMock.

The integration test properly validates end-to-end communication with a mocked HTTP server, including request body verification using matchers.

Comment on lines +58 to +63
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>4.0.0-beta.15</version>
<scope>test</scope>
</dependency>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Consider using a stable WireMock version.

The project uses WireMock version 4.0.0-beta.15, which is a beta release and may have stability or compatibility issues.


🌐 Web query:

What is the latest stable version of WireMock?

💡 Result:

The latest stable WireMock release is 3.13.1 (released June 13, 2025). [1][2]

Sources:
[1] WireMock blog — "WireMock 3.13.1 Released" (June 13, 2025).
[2] GitHub release — wiremock/wiremock tag 3.13.1.


Use WireMock 3.13.1, the latest stable release.

The project currently uses WireMock 4.0.0-beta.15, a beta release that may have stability or compatibility issues. The latest stable version is 3.13.1, which should be preferred for production dependencies.

🤖 Prompt for AI Agents
In pom.xml around lines 58 to 63, replace the WireMock dependency version
4.0.0-beta.15 with the stable release 3.13.1; keep the groupId, artifactId and
<scope>test</scope> unchanged so tests continue to use WireMock but now
reference the stable 3.13.1 version.

public class HelloController {

private final HelloModel model = new HelloModel();
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tight coupling makes testing difficult.

The HelloModel is instantiated with a hardcoded NtfyConnectionImpl, which prevents dependency injection and makes unit testing challenging. Consider accepting the model (or connection) as a constructor parameter or through a setter method.

Apply this diff to enable dependency injection:

+    private HelloModel model;
+
+    public HelloController() {
+        this(new HelloModel(new NtfyConnectionImpl()));
+    }
+
+    // Package-private constructor for testing
+    HelloController(HelloModel model) {
+        this.model = model;
+    }

-    private final HelloModel model = new HelloModel(new NtfyConnectionImpl());

Alternatively, use a factory pattern or dependency injection framework.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
private HelloModel model;
public HelloController() {
this(new HelloModel(new NtfyConnectionImpl()));
}
// Package-private constructor for testing
HelloController(HelloModel model) {
this.model = model;
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around line 15: the controller
currently instantiates HelloModel with a hardcoded new NtfyConnectionImpl(),
causing tight coupling that prevents DI and unit testing; change the class to
accept a HelloModel (or at minimum a NtfyConnection) via constructor injection
(add a constructor taking HelloModel or NtfyConnection and assign it to the
final field) and remove the direct new expression so tests can pass a mock;
optionally provide a no-arg constructor that delegates to the default
NtfyConnectionImpl for production.

Comment on lines +26 to +29
// Formatter för tid
private final DateTimeFormatter timeFormatter =
DateTimeFormatter.ofPattern("HH:mm:ss")
.withZone(ZoneId.systemDefault());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use consistent language in code comments.

The code contains Swedish comments (e.g., "Formatter för tid", "Visa hälsning", "Koppla ListView till modellens meddelanden") while the rest of the codebase uses English. Use English consistently throughout for better maintainability and collaboration.

Also applies to: 33-34, 36-37, 39-40, 42-43, 56-57

🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 26-29 (and also
apply to lines 33-34, 36-37, 39-40, 42-43, 56-57), replace the Swedish comments
with English equivalents to match the project convention; update comments like
"Formatter för tid" to "Formatter for time" (or "Time formatter"), "Visa
hälsning" to "Show greeting", "Koppla ListView till modellens meddelanden" to
"Bind ListView to model messages", etc., ensuring concise, consistent English
wording and preserving the same intent for each comment.

Comment on lines +5 to +8
public ManyParameters(String computerName, int timeout,
String method, int size, byte[] data) {

}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Empty constructor body - incomplete implementation.

The constructor accepts five parameters but doesn't store or use them. Since the class declares no fields, constructed instances will have no state. This makes the builder pattern ineffective, as ManyParametersBuilder.createManyParameters() produces empty objects.

Add fields and assignments:

 public class ManyParameters {
+    private final String computerName;
+    private final int timeout;
+    private final String method;
+    private final int size;
+    private final byte[] data;

     public ManyParameters(String computerName, int timeout,
                           String method, int size, byte[] data) {
-
+        this.computerName = computerName;
+        this.timeout = timeout;
+        this.method = method;
+        this.size = size;
+        this.data = data;
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/ManyParameters.java around lines 5 to 8, the
constructor accepts parameters but has an empty body and the class declares no
fields; add private final fields for computerName (String), timeout (int),
method (String), size (int) and data (byte[]) to the class and assign each
constructor parameter to the corresponding field inside the constructor; also
add simple getters for each field (or make fields non-final with setters if
mutability is required) and update the builder
(ManyParametersBuilder.createManyParameters()) to populate these new fields when
constructing instances.

} catch (IOException e) {
System.out.println("Error sending message");
} catch (InterruptedException e) {
System.out.println("Interruped sending message");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in error message.

Line 45 has a typo: Interruped should be Interrupted.

Apply this diff:

-            System.out.println("Interruped sending message");
+            System.out.println("Interrupted sending message");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
System.out.println("Interruped sending message");
System.out.println("Interrupted sending message");
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 45, the error
message printed contains a typo ("Interruped"); update the string to the correct
spelling ("Interrupted sending message") so the log/readout is correct. Ensure
only the message text is changed and no other logic is altered.

Comment on lines +3 to +14
public class Singelton {

private final static Singelton instance = new Singelton();

private Singelton(){

}

public static Singelton getInstance(){
return instance;
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the purpose of this singleton class.

This singleton class is not used in any of the provided files and its purpose in the messaging feature is unclear. If it's not needed, consider removing it to avoid dead code.

Run the following script to check for usages of this class:


🏁 Script executed:

#!/bin/bash
# Search for usages of Singelton class
rg -nP '\bSingelton\b' --type=java -C3

Length of output: 827


Remove unused singleton class and fix spelling error.

The verification confirms this class is dead code—no usages exist outside its own definition file. Additionally, the class name has a spelling error ("Singelton" instead of "Singleton"). Either remove the class or correct the spelling and add it to the messaging feature if it's needed.

🤖 Prompt for AI Agents
In src/main/java/com/example/Singelton.java around lines 3 to 14: this class is
dead code and misnamed ("Singelton" vs "Singleton"); either delete the file if
the singleton is not used, or rename the class and file to "Singleton" and
update constructor and getInstance() signatures accordingly, then register or
reference the fixed Singleton from the messaging feature (add usages) so it is
not unused; ensure class visibility and package declarations remain correct and
run a project-wide search/compile to update any imports or references.

@@ -0,0 +1,14 @@
package com.example;

public class Singelton {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the typo in the class name.

The class name is misspelled as "Singelton" instead of "Singleton".

Apply this diff to correct the spelling:

-public class Singelton {
+public class Singleton {
 
-    private final static Singelton instance = new Singelton();
+    private final static Singleton instance = new Singleton();
 
-    private Singelton(){
+    private Singleton(){
 
     }
 
-    public static Singelton getInstance(){
+    public static Singleton getInstance(){
         return instance;
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/Singelton.java around line 3, the class name is
misspelled as "Singelton"; rename the class to "Singleton" and update the
filename to match (Singleton.java), then update any references/usages/imports
across the codebase to use the corrected class name to prevent compilation
errors.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
pom.xml (1)

58-63: Unresolved: WireMock beta version still in use — use stable 3.13.1 instead.

This critical issue was flagged in a previous review but remains unaddressed. The project uses WireMock 4.0.0-beta.15, a beta release with potential stability or compatibility issues. The latest stable version is 3.13.1, which should be preferred.

Apply this diff to use the stable release:

-            <version>4.0.0-beta.15</version>
+            <version>3.13.1</version>
src/main/java/com/example/HelloModel.java (1)

43-49: Synchronous send still blocks the caller thread.

This issue was previously identified: calling connection.send(msg) synchronously will freeze the JavaFX UI thread if invoked from a button click or other UI event handler.

The past review comment suggested using sendMessageAsync() instead, but as noted below, sendMessageAsync() also has the same blocking issue and needs to be fixed first.

🧹 Nitpick comments (2)
src/test/java/com/example/HelloModelTest.java (2)

73-132: Consider consolidating duplicate test coverage.

The test shouldRejectBlankMessages (lines 73-94) already validates both empty string and null inputs. The subsequent tests shouldFailWhenSendingEmptyText and shouldFailWhenSendingNullMessage duplicate this coverage without adding new assertions.

Consider removing the two redundant tests or, if you want separate test methods for clarity, remove the loop in shouldRejectBlankMessages and keep only the individual tests.


46-70: Test only verifies the last message, not consecutive sends.

The test name suggests it validates handling of multiple consecutive sends, but line 69 only asserts that the final message was "Second". It doesn't verify whether "First" was actually sent or whether the connection received both calls.

If the spy should record all messages, consider verifying both were sent:

assertThat(connectionSpy.allMessages).containsExactly("First", "Second");

Or, if the spy only tracks the latest message and both sends are expected to succeed independently, consider renaming the test to clarify its intent (e.g., shouldReturnSuccessForMultipleConsecutiveSends).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e888be and 1cfcb6b.

📒 Files selected for processing (3)
  • pom.xml (3 hunks)
  • src/main/java/com/example/HelloModel.java (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
🔇 Additional comments (10)
pom.xml (3)

48-52: Dependency additions align with PR objectives.

The dotenv-java (3.2.0) and slf4j-simple (2.0.12) additions are appropriate for environment configuration and test logging, respectively. These align with the AI summary context indicating module-info.java declares requires for dotenv.java.

Also applies to: 64-69


80-80: Formatting improvement approved.

The indentation alignment for the javafx-maven-plugin option is a minor but correct fix.


53-57: Jackson 3.0.1 is compatible with Java 25, module-info declarations, and the current codebase imports.

Jackson works with newer JDKs, all the way to JDK 25, and Java 17 is required for all 3.x components. The module imports in your codebase are correct: jackson-annotations remains in the "old" com.fasterxml.jackson.core group and does NOT move to tools.jackson.core, to allow 2.x/3.x compatibility. Your NtfyMessageDto.java import from com.fasterxml.jackson.annotation.JsonIgnoreProperties is fully compatible with Jackson 3.0.1. Jackson 3.0 uses native module-info.java, which aligns with your module declaration. No breaking compatibility issues exist.

src/test/java/com/example/HelloModelTest.java (3)

20-30: LGTM! Robust JavaFX initialization for tests.

The setup properly handles headless environments and the IllegalStateException when Platform is already initialized.


192-274: LGTM! Comprehensive receiving message validation.

The tests thoroughly verify that:

  • Valid messages are added to the observable list
  • Null, blank, and whitespace-only messages are filtered out
  • The list remains empty when only invalid messages are received

The use of ListChangeListener with CountDownLatch properly tests the asynchronous observable list updates.


277-292: LGTM! Solid integration test with WireMock.

The test properly verifies end-to-end HTTP communication by stubbing endpoints and validating the request body was sent correctly.

src/main/java/com/example/HelloModel.java (4)

3-14: LGTM! Clean JavaFX integration setup.

The field declarations properly use JavaFX observable collections and properties for UI binding. The dependency on NtfyConnection enables testability through injection.


21-41: LGTM! Standard JavaFX property accessors.

The property accessors follow JavaFX conventions and enable proper UI binding.


66-86: LGTM! Proper JavaFX thread management for receiving messages.

The receiveMessage() method correctly validates incoming messages and uses runOnFx() to ensure UI updates happen on the JavaFX Application Thread. The fallback in runOnFx() for uninitialized Platform is a good defensive pattern for testing scenarios.


16-19: No changes needed to HelloModel constructor.

The original concern about immediate receiveMessage() invocation is not applicable here. The receive() method is asynchronous (uses http.sendAsync()), so calling it immediately is safe. The callback includes null/blank message checks. The constructor correctly uses the injected NtfyConnection dependency without modification.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/test/java/com/example/HelloModelTest.java (1)

218-278: Trim the 5 s waits in negative-message tests

Each of these negative-path tests (shouldDiscardNullIncomingMessage, shouldIgnoreMessagesWithBlankContent, shouldRejectAllInvalidIncomingMessages) waits the full 5 seconds for latch.await(…) even though the happy path is “nothing happens.” That adds ~15 seconds of dead time to the suite on every run. Please tighten the timeout (for example to 100 ms) or restructure so you only fall back to a long wait when something actually fires. A quick change like

-        boolean messageAdded = latch.await(5, TimeUnit.SECONDS);
+        boolean messageAdded = latch.await(100, TimeUnit.MILLISECONDS);

applied to each test keeps the intent intact while making the suite much faster.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfcb6b and b665727.

📒 Files selected for processing (1)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/main/java/com/example/HelloController.java (2)

24-56: Keep controller comments in English

The inline comments reverted to Swedish again. Please translate them back to English for consistency with the rest of the codebase.

-        //visa välkomstmeddelande
+        // Show welcome message
...
-        //formatera meddelanden i ListView
+        // Format messages in the ListView
...
-        // Scrolla automatiskt till senaste meddelandet
+        // Auto-scroll to the latest message
...
-        //skicka asynkront – HelloModel hanterar rensning och callback
+        // Send asynchronously – HelloModel handles clearing and callback

13-13: Stop hard-wiring NtfyConnectionImpl in the controller

Instantiating HelloModel with new NtfyConnectionImpl() inside the controller forces every controller load to hit Dotenv and require HOST_NAME, which breaks tests and prevents injecting spies/mocks. Please restore constructor injection so the controller can be provided a model (or connection) from outside.

-    private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
+    private final HelloModel model;
+
+    public HelloController() {
+        this(new HelloModel(new NtfyConnectionImpl()));
+    }
+
+    HelloController(HelloModel model) {
+        this.model = model;
+    }
🧹 Nitpick comments (1)
src/main/java/com/example/NtfyConnection.java (1)

10-12: Drop the commented-out legacy signatures

These legacy method declarations live outside the interface and create noise/confusion. Please remove them so the file reflects only the current contract.

-
-    //public boolean send(String message);
-
-    //public void receive(Consumer<NtfyMessageDto> messageHandler);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b665727 and 120da00.

📒 Files selected for processing (6)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloModel.java (1 hunks)
  • src/main/java/com/example/NtfyConnection.java (1 hunks)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
  • src/test/java/com/example/NtfyConnectionSpy.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/com/example/NtfyConnectionSpy.java

package com.example;

import io.github.cdimascio.dotenv.Dotenv;
import tools.jackson.databind.ObjectMapper;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix ObjectMapper import package

tools.jackson.databind.ObjectMapper does not exist, so this file will not compile. Please correct the import to the standard Jackson package.

-import tools.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 4, the import
uses a non-existent package "tools.jackson.databind.ObjectMapper"; replace it
with the correct Jackson import "com.fasterxml.jackson.databind.ObjectMapper"
(and remove any leftover incorrect imports), then re-run compilation to verify
the class resolves correctly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/test/java/com/example/HelloModelTest.java (1)

167-197: The try-catch block still masks exception handling.

Despite the past review comment indicating this was addressed, the try-catch wrapper (lines 184-192) remains in the code. This prevents the test from detecting whether sendMessageAsync properly handles exceptions internally—if it throws synchronously, the test passes anyway because the catch manually invokes the failure path.

Remove the try-catch so the test fails if sendMessageAsync doesn't handle exceptions:

         CountDownLatch latch = new CountDownLatch(1);
         boolean[] wasSuccessful = new boolean[1];
 
-        //wrappa i try-catch för att fånga exception från connection
-        try {
-            model.sendMessageAsync(success -> {
-                wasSuccessful[0] = success;
-                latch.countDown();
-            });
-        } catch (Exception e) {
-            wasSuccessful[0] = false;
-            latch.countDown();
-        }
+        model.sendMessageAsync(success -> {
+            wasSuccessful[0] = success;
+            latch.countDown();
+        });
 
         boolean completed = latch.await(5, TimeUnit.SECONDS);
         assertThat(completed).as("Timed out waiting for exception handling").isTrue();
         assertThat(wasSuccessful[0]).isFalse();
🧹 Nitpick comments (3)
src/test/java/com/example/HelloModelTest.java (3)

21-30: Consider a more precise diagnostic message.

The catch block message "Headless environment detected" may mislead when IllegalStateException is thrown because the platform is already initialized rather than unavailable. Consider logging the exception type or message for clearer diagnostics.

-        } catch (IllegalStateException | UnsupportedOperationException e) {
-            System.out.println("Headless environment detected – skipping JavaFX startup");
+        } catch (IllegalStateException e) {
+            System.out.println("JavaFX already initialized – skipping startup");
+        } catch (UnsupportedOperationException e) {
+            System.out.println("Headless environment detected – skipping JavaFX startup");
         }

199-295: Replace Thread.sleep with deterministic synchronization.

Four tests use Thread.sleep(100) to "give the listener time to attach" (lines 219, 241, 261, 286). This makes tests slower and can cause flakiness if the timing assumption fails. Consider adding a CountDownLatch for listener attachment or verifying attachment before simulating messages.

Example pattern for deterministic listener attachment:

CountDownLatch listenerReady = new CountDownLatch(1);
model.getMessages().addListener((ListChangeListener<NtfyMessageDto>) c -> {
    listenerReady.countDown(); // Signal ready on first invocation
    while (c.next()) {
        if (c.wasAdded()) {
            messageReceived.set(true);
            latch.countDown();
        }
    }
});
listenerReady.await(1, TimeUnit.SECONDS); // Wait for attachment
connectionSpy.simulateIncoming(incomingMsg);

34-197: Consider using AtomicBoolean consistently for lambda state capture.

The test suite uses both boolean[] (lines 53, 85, 106, 126, 154, 181) and AtomicBoolean (line 206) to capture boolean state in lambdas. Using AtomicBoolean consistently is more idiomatic and clearer about intent.

Example conversion:

-        boolean[] results = new boolean[2];
+        AtomicBoolean firstResult = new AtomicBoolean();
+        AtomicBoolean secondResult = new AtomicBoolean();
 
         model.setMessageToSend("First");
         model.sendMessageAsync(success -> {
-            results[0] = success;
+            firstResult.set(success);
             latch.countDown();
         });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29fefae and 4c8aaf9.

📒 Files selected for processing (1)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
🔇 Additional comments (2)
src/test/java/com/example/HelloModelTest.java (2)

32-72: LGTM!

The successful send tests properly handle async operations with CountDownLatch, include reasonable timeouts, and have descriptive assertion messages.


297-317: Integration test structure is solid.

The WireMock-based integration test properly validates end-to-end HTTP communication. The Thread.sleep(100) at line 306 is presumably for connection establishment—while not ideal, it's more acceptable in integration tests than unit tests.

If flakiness occurs, consider adding an explicit wait for the connection's receive loop to be established or making the connection expose a readiness signal.

Comment on lines +74 to +137
// invalid send tests
@Test
void shouldRejectBlankMessages() throws InterruptedException {
NtfyConnectionSpy connectionSpy = new NtfyConnectionSpy();
HelloModel model = new HelloModel(connectionSpy);

String[] invalidInputs = {"", null};

for (String input : invalidInputs) {
model.setMessageToSend(input);
CountDownLatch latch = new CountDownLatch(1);
boolean[] wasSuccessful = new boolean[1];

model.sendMessageAsync(success -> {
wasSuccessful[0] = success;
latch.countDown();
});

boolean completed = latch.await(5, TimeUnit.SECONDS);
assertThat(completed).as("Timed out waiting for blank message rejection").isTrue();
assertThat(wasSuccessful[0]).isFalse();
assertThat(connectionSpy.message).isNull();
}
}

@Test
void shouldFailWhenSendingEmptyText() throws InterruptedException {
NtfyConnectionSpy connectionSpy = new NtfyConnectionSpy();
HelloModel model = new HelloModel(connectionSpy);
model.setMessageToSend("");

CountDownLatch latch = new CountDownLatch(1);
boolean[] wasSuccessful = new boolean[1];

model.sendMessageAsync(success -> {
wasSuccessful[0] = success;
latch.countDown();
});

boolean completed = latch.await(5, TimeUnit.SECONDS);
assertThat(completed).as("Timed out waiting for empty text rejection").isTrue();
assertThat(wasSuccessful[0]).isFalse();
assertThat(connectionSpy.message).isNull();
}

@Test
void shouldFailWhenSendingNullMessage() throws InterruptedException {
NtfyConnectionSpy connectionSpy = new NtfyConnectionSpy();
HelloModel model = new HelloModel(connectionSpy);
model.setMessageToSend(null);

CountDownLatch latch = new CountDownLatch(1);
boolean[] wasSuccessful = new boolean[1];

model.sendMessageAsync(success -> {
wasSuccessful[0] = success;
latch.countDown();
});

boolean completed = latch.await(5, TimeUnit.SECONDS);
assertThat(completed).as("Timed out waiting for null message rejection").isTrue();
assertThat(wasSuccessful[0]).isFalse();
assertThat(connectionSpy.message).isNull();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove duplicate test coverage.

The test shouldRejectBlankMessages (lines 76-97) already validates both empty string and null inputs via iteration. The tests shouldFailWhenSendingEmptyText (lines 99-117) and shouldFailWhenSendingNullMessage (lines 119-137) duplicate this coverage, making maintenance more costly without adding value.

Choose one approach:

  1. Keep the parameterized test (lines 76-97) and remove lines 99-137.
  2. Keep the individual tests for clearer failure messages and remove lines 76-97.

If you prefer individual tests for clarity, apply this diff:

-    // invalid send tests
-    @Test
-    void shouldRejectBlankMessages() throws InterruptedException {
-        NtfyConnectionSpy connectionSpy = new NtfyConnectionSpy();
-        HelloModel model = new HelloModel(connectionSpy);
-
-        String[] invalidInputs = {"", null};
-
-        for (String input : invalidInputs) {
-            model.setMessageToSend(input);
-            CountDownLatch latch = new CountDownLatch(1);
-            boolean[] wasSuccessful = new boolean[1];
-
-            model.sendMessageAsync(success -> {
-                wasSuccessful[0] = success;
-                latch.countDown();
-            });
-
-            boolean completed = latch.await(5, TimeUnit.SECONDS);
-            assertThat(completed).as("Timed out waiting for blank message rejection").isTrue();
-            assertThat(wasSuccessful[0]).isFalse();
-            assertThat(connectionSpy.message).isNull();
-        }
-    }
-
+    // invalid send tests
     @Test
     void shouldFailWhenSendingEmptyText() throws InterruptedException {
🤖 Prompt for AI Agents
In src/test/java/com/example/HelloModelTest.java around lines 74 to 137,
duplicate test coverage exists: the parameterized test shouldRejectBlankMessages
(lines ~76-97) already covers both empty string and null, so remove the two
redundant tests shouldFailWhenSendingEmptyText and
shouldFailWhenSendingNullMessage (lines ~99-137). Delete those two test methods
(including their setup, latch, assertions) so only the single
parameterized-style loop test remains; run tests to ensure behavior is
unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/test/java/com/example/HelloModelTest.java (2)

100-138: Remove duplicate test coverage.

The tests shouldFailWhenSendingEmptyText (lines 100-118) and shouldFailWhenSendingNullMessage (lines 120-138) duplicate the coverage already provided by shouldRejectBlankMessages (lines 76-98), which iterates over both empty string and null inputs.

Consider removing these two redundant tests to reduce maintenance overhead.


168-200: Test still masks whether exceptions are properly handled internally.

Despite a past review comment indicating this was addressed, the try-catch block around sendMessageAsync (lines 187-195) remains and still undermines the test's intent:

  • If sendMessageAsync throws synchronously, that indicates the implementation failed to handle exceptions internally
  • The catch block manually invokes the failure path, preventing detection of whether the callback was properly invoked
  • This makes it impossible to distinguish between "callback was called with false" and "exception escaped and test caught it"

Remove the outer try-catch and let the test fail if the implementation doesn't handle exceptions properly:

-        //wrappa i try-catch för att fånga exception från connection
-        try {
-            model.sendMessageAsync(success -> {
-                wasSuccessful[0] = success;
-                latch.countDown();
-            });
-        } catch (Exception e) {
-            wasSuccessful[0] = false;
-            latch.countDown();
-        }
+        model.sendMessageAsync(success -> {
+            wasSuccessful[0] = success;
+            latch.countDown();
+        });
src/main/java/com/example/NtfyConnectionImpl.java (2)

4-4: Critical: Fix ObjectMapper import package.

The import tools.jackson.databind.ObjectMapper is incorrect and will cause a compilation failure. The Jackson library uses the com.fasterxml.jackson.databind package.

Apply this diff:

-import tools.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectMapper;

Based on coding guidelines


35-54: Replace non-standard "Cache" header with standard "Cache-Control: no-store".

Line 40 uses a non-standard "Cache" header with value "no", which will be ignored by HTTP clients and servers. The standard "Cache-Control: no-store" prevents any caching of the request or response, which is appropriate for transient message sends.

Apply this diff:

         HttpRequest request = HttpRequest.newBuilder()
                 .POST(HttpRequest.BodyPublishers.ofString(message))
                 .uri(URI.create(hostName + "/mytopic"))
-                .header("Cache", "no")
+                .header("Cache-Control", "no-store")
                 .timeout(Duration.ofSeconds(10))
                 .build();
🧹 Nitpick comments (2)
src/main/java/com/example/NtfyConnectionImpl.java (2)

28-33: Consider consistent connect timeout and add null check.

The parameterized constructor sets a 5-second connect timeout while the default constructor uses 10 seconds. This inconsistency may be intentional (e.g., faster timeout for tests), but it's unclear.

Additionally, consider adding a null check for the hostName parameter to fail fast:

 public NtfyConnectionImpl(String hostName) {
+    this.hostName = Objects.requireNonNull(hostName, "hostName must not be null");
-    this.hostName = hostName;
     this.http = HttpClient.newBuilder()
-            .connectTimeout(Duration.ofSeconds(5))
+            .connectTimeout(Duration.ofSeconds(10))
             .build();
 }

56-86: Consider structured logging instead of stderr.

The error handling is comprehensive and properly catches exceptions at each level. However, using System.err.println() for logging (lines 72, 79, 83) makes it difficult to configure log levels or integrate with logging frameworks.

Consider adding SLF4J or java.util.logging for production-ready logging:

private static final Logger logger = LoggerFactory.getLogger(NtfyConnectionImpl.class);
// Then replace System.err.println with:
logger.error("Failed to parse message: {}", line);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c8aaf9 and e07b438.

📒 Files selected for processing (2)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
🔇 Additional comments (2)
src/test/java/com/example/HelloModelTest.java (2)

142-166: LGTM!

The test correctly simulates a failing connection by invoking callback.accept(false) and properly waits for the asynchronous callback using the latch. The assertion correctly verifies that the message is preserved for potential retry.


300-320: LGTM!

The integration test properly uses WireMock to simulate the HTTP server, injects the dynamic port, and verifies the actual request body. This provides good end-to-end coverage of the HTTP interaction.

@Rickank Rickank closed this Nov 13, 2025
@Rickank Rickank deleted the model branch November 13, 2025 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant