From 764ee3cc265ad6ba05fc76fd5a662d620bed4353 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Thu, 23 Apr 2026 01:20:53 +0200
Subject: [PATCH 01/10] Refresh README to match current module structure
---
README.md | 80 +++++------
.../impl/AdminPasswordServiceImpl.java | 38 +++++
.../service/impl/DebugServiceImpl.java | 58 ++++++++
.../service/impl/DeviceInfoServiceImpl.java | 26 ++++
.../impl/NetworkConfigurationServiceImpl.java | 42 ++++++
.../service/impl/PixelProgramServiceImpl.java | 136 ++++++++++++++++++
.../service/impl/RebootServiceImpl.java | 25 ++++
.../service/impl/RuntimeDataServiceImpl.java | 58 ++++++++
.../impl/UserManagementServiceImpl.java | 105 ++++++++++++++
9 files changed, 527 insertions(+), 41 deletions(-)
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
diff --git a/README.md b/README.md
index 209a998..2cfe84d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# WPI Core
-**WPI Core** is a Java library providing foundational abstractions for secure API communication, including authentication, endpoint access control, and request/response modeling, designed to be consumed by higher-level WPI client implementations.
+**WPI Core** is a Java library providing foundational abstractions for secure API communication, including authentication, endpoint access control, request/response modeling, and application-level service contracts for WPI clients.
Part of the **Waterflow Pixel (WP)** ecosystem developed by [VerbaTechTeam](https://github.com/VerbaTechTeam) — *We create words that devices understand.*
@@ -12,7 +12,7 @@ Part of the **Waterflow Pixel (WP)** ecosystem developed by [VerbaTechTeam](http
| Repository | Layer | Description |
|---|---|---|
-| **wpi-core** *(this repo)* | WPI | Core API abstractions in Java — auth, endpoints, request model |
+| **wpi-core** *(this repo)* | WPI | Core API abstractions in Java — auth, endpoints, request model, service contracts |
| [waterflow-pixel-unit](https://github.com/VerbaTechTeam/waterflow-pixel-unit) | WPU | MicroPython firmware for Raspberry Pi Pico 2W with built-in REST HTTP server for direct LED strip control |
For a full overview of the system architecture and roadmap (including the planned **WPC** controller layer and **wpu-emulator**), see the [VerbaTechTeam organization profile](https://github.com/VerbaTechTeam).
@@ -48,39 +48,35 @@ Then reference it in your project's `pom.xml`:
## Architecture
-The library follows a clean, layered architecture:
+The library follows a layered architecture:
```
pl.vtt.wpi.core
├── application
│ ├── config # AuthorizationHolder (thread-local auth state)
-│ ├── exception # Domain exceptions (e.g. IncorrectUsernameOrPasswordException)
-│ ├── service # Service interfaces (LoginService)
-│ │ └── impl # Internal implementations (not exported)
-│ └── util # RequestFactory, RequestHandler interfaces
-└── domain
- └── model
- ├── Authorization.java
- ├── Credentials.java
- ├── Request.java
- └── endpoint
- ├── EndpointDescriptor.java
- ├── Method.java
- ├── RequestTarget.java
- ├── Resource.java
- └── UserGroup.java
+│ ├── exception # Application-level exceptions
+│ └── service
+│ ├── ... # Service interfaces (LoginService, RuntimeDataService, etc.)
+│ └── impl # Internal service implementations
+├── domain
+│ ├── dto
+│ ├── model
+│ └── port # Input/Output port contracts + endpoint-specific port implementations
+└── infrastructure
+ ├── Request / Response
+ ├── RequestFactory / RequestHandler / RequestSender
+ └── factory # SynchronizedRequestFactory
```
## Key Concepts
### Authentication
-Login is handled by `LoginService`. Internally, `LoginServiceImpl` delegates authorization to a dedicated port (`AuthOutputPort`), so request execution is decoupled from service orchestration.
+Login is handled by `LoginService`. Internally, `LoginServiceImpl` delegates authorization to a dedicated output port (`AuthOutputPort`), so request execution is decoupled from service orchestration.
-On success, the resulting `Credentials` (username + token) are stored as a Basic Auth header in `AuthorizationHolder` — a thread-local holder used to attach authorization to outgoing requests.
+On success, resulting `Credentials` (username + token) are stored in `AuthorizationHolder` — a thread-local holder used to attach authorization to outgoing requests.
```java
-// Provided by a higher-level module
LoginService loginService = ...;
Credentials credentials = loginService.login("admin", "password");
@@ -108,29 +104,37 @@ boolean allowed = RequestTarget.DATA_UPDATE.allow(Method.PUT, userGroups);
### Request Model
-A `Request` carries the target URL, authorization header, and an optional typed payload:
+A `Request` carries timestamp, HTTP method, target URL, authorization header, and an optional typed payload:
```java
-record Request(Method method, String url, Authorization authorization, T payload) {}
+Request request = new Request<>(
+ null, // timestamp (null => now)
+ Method.GET,
+ "https://host/api/data",
+ authorization,
+ payload
+);
```
-### Implementing a Client
+### Application Services
-`wpi-core` exposes only the `LoginService` interface — the implementation is internal to the module. A higher-level module (e.g. `wpi-desktop`) is responsible for instantiating the service and exposing it to the client.
+The package `pl.vtt.wpi.core.application.service` currently exposes interfaces for:
-From the client's perspective, usage is limited to the public interface:
+- `LoginService`
+- `AdminPasswordService`
+- `UserManagementService`
+- `RuntimeDataService`
+- `PixelProgramService`
+- `NetworkConfigurationService`
+- `DeviceInfoService`
+- `DebugService`
+- `RebootService`
-```java
-// Provided by a higher-level module
-LoginService loginService = ...;
-
-Credentials credentials = loginService.login("admin", "password");
-loginService.logout();
-```
+Concrete implementations are provided in `pl.vtt.wpi.core.application.service.impl`.
### Domain Ports
-The package `pl.vtt.wpi.core.domain.port` contains concrete port implementations for domain operations:
+The package `pl.vtt.wpi.core.domain.port` contains port contracts and endpoint-specific implementations:
- **Output ports**: `AuthOutputPort`, `DeviceInfoOutputPort`, `RuntimeDataOutputPort`, `CurrentStateOutputPort`, `PixelProgramsOutputPort`, `UsersOutputPort`
- **Input ports**: `RuntimeDataInputPort`, `WifiConfigInputPort`, `PixelProgramsInputPort`, `UserCreateInputPort`, `RestartInputPort`, `LogsDeleteInputPort`
@@ -147,13 +151,7 @@ mvn test
mvn package
```
-Unit tests are written with JUnit Jupiter 5 and currently verify:
-
-- successful login flow (`Credentials` returned + Authorization header updated),
-- validation for null/blank username and password,
-- incorrect credentials / null auth response handling,
-- cleanup of `AuthorizationHolder` after failed login attempts (including runtime failures),
-- logout behavior.
+Unit tests are written with JUnit Jupiter 5 and currently include coverage for core port behaviors and login service logic.
## CI
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
new file mode 100644
index 0000000..887f659
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
@@ -0,0 +1,38 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.Objects;
+import pl.vtt.wpi.core.application.service.AdminPasswordService;
+import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+
+public class AdminPasswordServiceImpl implements AdminPasswordService {
+ private final InputPort adminPasswordResetInputPort;
+
+ public AdminPasswordServiceImpl(InputPort adminPasswordResetInputPort) {
+ this.adminPasswordResetInputPort = Objects.requireNonNull(adminPasswordResetInputPort,
+ "adminPasswordResetInputPort cannot be null");
+ }
+
+ @Override
+ public void resetPassword(String secureKey, PasswordDto passwordDto) {
+ if (secureKey == null || secureKey.isBlank()) {
+ throw new IllegalArgumentException("Secure key cannot be null or blank");
+ }
+ if (passwordDto == null || passwordDto.password() == null || passwordDto.passwordConfirmation() == null) {
+ throw new IllegalArgumentException("Password data cannot be null");
+ }
+ if (!passwordDto.password().equals(passwordDto.passwordConfirmation())) {
+ throw new IllegalArgumentException("Password and password confirmation do not match");
+ }
+ try {
+ adminPasswordResetInputPort.send(new AdminPasswordResetRequest(secureKey, passwordDto));
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot reset admin password", cause);
+ }
+ }
+
+ public record AdminPasswordResetRequest(String secureKey, PasswordDto passwordDto) {
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
new file mode 100644
index 0000000..655ba5d
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
@@ -0,0 +1,58 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.List;
+import java.util.Objects;
+import pl.vtt.wpi.core.application.service.DebugService;
+import pl.vtt.wpi.core.domain.model.device.CurrentState;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.OutputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+public class DebugServiceImpl implements DebugService {
+ private final OutputPort> logsOutputPort;
+ private final InputPort logsDeleteInputPort;
+ private final OutputPort currentStateOutputPort;
+
+ public DebugServiceImpl(OutputPort> logsOutputPort,
+ InputPort logsDeleteInputPort,
+ OutputPort currentStateOutputPort) {
+ this.logsOutputPort = Objects.requireNonNull(logsOutputPort, "logsOutputPort cannot be null");
+ this.logsDeleteInputPort = Objects.requireNonNull(logsDeleteInputPort,
+ "logsDeleteInputPort cannot be null");
+ this.currentStateOutputPort = Objects.requireNonNull(currentStateOutputPort,
+ "currentStateOutputPort cannot be null");
+ }
+
+ @Override
+ public List pollLogs() {
+ List logs = peekLogs();
+ try {
+ logsDeleteInputPort.send(null);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot clear logs", cause);
+ }
+ return logs;
+ }
+
+ @Override
+ public List peekLogs() {
+ try {
+ return logsOutputPort.load();
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot read logs", cause);
+ }
+ }
+
+ @Override
+ public CurrentState getCurrentState() {
+ try {
+ return currentStateOutputPort.load();
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot read current state", cause);
+ }
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
new file mode 100644
index 0000000..7e5e31c
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
@@ -0,0 +1,26 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.Objects;
+import pl.vtt.wpi.core.application.service.DeviceInfoService;
+import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
+import pl.vtt.wpi.core.domain.port.OutputPort;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+public class DeviceInfoServiceImpl implements DeviceInfoService {
+ private final OutputPort deviceInfoOutputPort;
+
+ public DeviceInfoServiceImpl(OutputPort deviceInfoOutputPort) {
+ this.deviceInfoOutputPort = Objects.requireNonNull(deviceInfoOutputPort,
+ "deviceInfoOutputPort cannot be null");
+ }
+
+ @Override
+ public DeviceInfo read() {
+ try {
+ return deviceInfoOutputPort.load();
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot read device info", cause);
+ }
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
new file mode 100644
index 0000000..651bd98
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
@@ -0,0 +1,42 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.Objects;
+import pl.vtt.wpi.core.application.service.NetworkConfigurationService;
+import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
+import pl.vtt.wpi.core.domain.model.device.WifiConfig;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+
+public class NetworkConfigurationServiceImpl implements NetworkConfigurationService {
+ private final InputPort wifiConfigInputPort;
+
+ public NetworkConfigurationServiceImpl(InputPort wifiConfigInputPort) {
+ this.wifiConfigInputPort = Objects.requireNonNull(wifiConfigInputPort,
+ "wifiConfigInputPort cannot be null");
+ }
+
+ @Override
+ public void config(WifiConfig config) {
+ send(config);
+ }
+
+ @Override
+ public void config(AccessPointConfig config) {
+ if (config == null) {
+ throw new IllegalArgumentException("Access point config cannot be null");
+ }
+ send(new WifiConfig(config.ssid(), config.password()));
+ }
+
+ private void send(WifiConfig config) {
+ if (config == null) {
+ throw new IllegalArgumentException("WiFi config cannot be null");
+ }
+ try {
+ wifiConfigInputPort.send(config);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot update network configuration", cause);
+ }
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
new file mode 100644
index 0000000..c93e3ae
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -0,0 +1,136 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
+import pl.vtt.wpi.core.application.service.PixelProgramService;
+import pl.vtt.wpi.core.domain.model.color.RgbColor;
+import pl.vtt.wpi.core.domain.model.device.PixelProgram;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.OutputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+public class PixelProgramServiceImpl implements PixelProgramService {
+ private final OutputPort> pixelProgramsOutputPort;
+ private final InputPort> pixelProgramsInputPort;
+
+ public PixelProgramServiceImpl(OutputPort> pixelProgramsOutputPort,
+ InputPort> pixelProgramsInputPort) {
+ this.pixelProgramsOutputPort = Objects.requireNonNull(pixelProgramsOutputPort,
+ "pixelProgramsOutputPort cannot be null");
+ this.pixelProgramsInputPort = Objects.requireNonNull(pixelProgramsInputPort,
+ "pixelProgramsInputPort cannot be null");
+ }
+
+ @Override
+ public List getAll() {
+ return new ArrayList<>(loadPrograms());
+ }
+
+ @Override
+ public void insert(List pixelPrograms) {
+ List current = getAll();
+ if (pixelPrograms == null || pixelPrograms.isEmpty()) {
+ return;
+ }
+ current.addAll(pixelPrograms);
+ try {
+ set(current);
+ } catch (DataInconsistencyException e) {
+ throw new RuntimeException("Cannot insert pixel programs", e);
+ }
+ }
+
+ @Override
+ public void set(List pixelPrograms) throws DataInconsistencyException {
+ if (pixelPrograms == null) {
+ throw new DataInconsistencyException("Pixel programs cannot be null");
+ }
+ try {
+ pixelProgramsInputPort.send(List.copyOf(pixelPrograms));
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new DataInconsistencyException("Cannot update pixel programs", cause);
+ }
+ }
+
+ @Override
+ public PixelProgram get(int index) throws PixelProgramNotFoundException {
+ return loadPrograms().stream()
+ .filter(pixelProgram -> pixelProgram.index() == index)
+ .findFirst()
+ .orElseThrow(() -> new PixelProgramNotFoundException("Pixel program %d not found".formatted(index)));
+ }
+
+ @Override
+ public PixelProgram singleton(List pixelProgram) {
+ return new PixelProgram(0, pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
+ }
+
+ @Override
+ public PixelProgram save(List pixelProgram) {
+ List programs = getAll();
+ int nextIndex = programs.stream()
+ .map(PixelProgram::index)
+ .max(Comparator.naturalOrder())
+ .map(i -> i + 1)
+ .orElse(0);
+ PixelProgram saved = new PixelProgram(nextIndex,
+ pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
+ programs.add(saved);
+ try {
+ set(programs);
+ } catch (DataInconsistencyException e) {
+ throw new RuntimeException("Cannot save pixel program", e);
+ }
+ return saved;
+ }
+
+ @Override
+ public PixelProgram update(int index, List pixelProgram) throws PixelProgramNotFoundException {
+ List programs = getAll();
+ int replaceIndex = findListIndex(programs, index);
+ PixelProgram updated = new PixelProgram(index,
+ pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
+ programs.set(replaceIndex, updated);
+ try {
+ set(programs);
+ } catch (DataInconsistencyException e) {
+ throw new RuntimeException("Cannot update pixel program", e);
+ }
+ return updated;
+ }
+
+ @Override
+ public PixelProgram remove(int index) throws PixelProgramNotFoundException, DataInconsistencyException {
+ List programs = getAll();
+ int removeIndex = findListIndex(programs, index);
+ PixelProgram removed = programs.remove(removeIndex);
+ set(programs);
+ return removed;
+ }
+
+ private List loadPrograms() {
+ try {
+ List pixelPrograms = pixelProgramsOutputPort.load();
+ return pixelPrograms == null ? List.of() : pixelPrograms;
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot read pixel programs", cause);
+ }
+ }
+
+ private int findListIndex(List programs, int index) throws PixelProgramNotFoundException {
+ for (int i = 0; i < programs.size(); i++) {
+ PixelProgram program = programs.get(i);
+ if (program != null && program.index() == index) {
+ return i;
+ }
+ }
+ throw new PixelProgramNotFoundException("Pixel program %d not found".formatted(index));
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
new file mode 100644
index 0000000..cd906ba
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
@@ -0,0 +1,25 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.Objects;
+import pl.vtt.wpi.core.application.service.RebootService;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+
+public class RebootServiceImpl implements RebootService {
+ private final InputPort rebootInputPort;
+
+ public RebootServiceImpl(InputPort rebootInputPort) {
+ this.rebootInputPort = Objects.requireNonNull(rebootInputPort,
+ "rebootInputPort cannot be null");
+ }
+
+ @Override
+ public void reboot() {
+ try {
+ rebootInputPort.send(null);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot reboot device", cause);
+ }
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
new file mode 100644
index 0000000..a2ab974
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
@@ -0,0 +1,58 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.application.service.RuntimeDataService;
+import pl.vtt.wpi.core.domain.model.device.RuntimeData;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.OutputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+public class RuntimeDataServiceImpl implements RuntimeDataService {
+ private final OutputPort runtimeDataOutputPort;
+ private final InputPort runtimeDataInputPort;
+
+ public RuntimeDataServiceImpl(OutputPort runtimeDataOutputPort,
+ InputPort runtimeDataInputPort) {
+ this.runtimeDataOutputPort = Objects.requireNonNull(runtimeDataOutputPort,
+ "runtimeDataOutputPort cannot be null");
+ this.runtimeDataInputPort = Objects.requireNonNull(runtimeDataInputPort,
+ "runtimeDataInputPort cannot be null");
+ }
+
+ @Override
+ public RuntimeData read() {
+ try {
+ return runtimeDataOutputPort.load();
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot read runtime data", cause);
+ }
+ }
+
+ @Override
+ public void set(RuntimeData runtimeData) throws DataInconsistencyException {
+ if (runtimeData == null) {
+ throw new DataInconsistencyException("Runtime data cannot be null");
+ }
+ send(runtimeData, "Cannot set runtime data");
+ }
+
+ @Override
+ public void update(RuntimeData runtimeData) throws DataInconsistencyException {
+ if (runtimeData == null) {
+ throw new DataInconsistencyException("Runtime data cannot be null");
+ }
+ send(runtimeData, "Cannot update runtime data");
+ }
+
+ private void send(RuntimeData runtimeData, String message) throws DataInconsistencyException {
+ try {
+ runtimeDataInputPort.send(runtimeData);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new DataInconsistencyException(message, cause);
+ }
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
new file mode 100644
index 0000000..f46f6ef
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
@@ -0,0 +1,105 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.List;
+import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
+import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
+import pl.vtt.wpi.core.application.exception.UserNotExistsException;
+import pl.vtt.wpi.core.application.service.UserManagementService;
+import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.domain.model.User;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.OutputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+public class UserManagementServiceImpl implements UserManagementService {
+ private final OutputPort> usersOutputPort;
+ private final InputPort userCreateInputPort;
+ private final InputPort changePasswordInputPort;
+ private final InputPort removeUserInputPort;
+
+ public UserManagementServiceImpl(OutputPort> usersOutputPort,
+ InputPort userCreateInputPort,
+ InputPort changePasswordInputPort,
+ InputPort removeUserInputPort) {
+ this.usersOutputPort = Objects.requireNonNull(usersOutputPort, "usersOutputPort cannot be null");
+ this.userCreateInputPort = Objects.requireNonNull(userCreateInputPort,
+ "userCreateInputPort cannot be null");
+ this.changePasswordInputPort = Objects.requireNonNull(changePasswordInputPort,
+ "changePasswordInputPort cannot be null");
+ this.removeUserInputPort = Objects.requireNonNull(removeUserInputPort,
+ "removeUserInputPort cannot be null");
+ }
+
+ @Override
+ public void createUser(User user, PasswordDto passwordDto)
+ throws UserAlreadyExistsException, InvalidPasswordException {
+ validatePassword(passwordDto);
+ if (user == null) {
+ throw new IllegalArgumentException("User cannot be null");
+ }
+ boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
+ if (exists) {
+ throw new UserAlreadyExistsException();
+ }
+ try {
+ userCreateInputPort.send(user);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot create user", cause);
+ }
+ }
+
+ @Override
+ public void changePassword(PasswordDto passwordDto)
+ throws InvalidPasswordException {
+ validatePassword(passwordDto);
+ try {
+ changePasswordInputPort.send(passwordDto);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot change password", cause);
+ }
+ }
+
+ @Override
+ public void removeUser(User user)
+ throws UserNotExistsException {
+ if (user == null) {
+ throw new IllegalArgumentException("User cannot be null");
+ }
+ boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
+ if (!exists) {
+ throw new UserNotExistsException();
+ }
+ try {
+ removeUserInputPort.send(user);
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot remove user", cause);
+ }
+ }
+
+ private List loadUsers() {
+ try {
+ List users = usersOutputPort.load();
+ return users == null ? List.of() : users;
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new RuntimeException("Cannot load users", cause);
+ }
+ }
+
+ private void validatePassword(PasswordDto passwordDto) throws InvalidPasswordException {
+ if (passwordDto == null || passwordDto.password() == null || passwordDto.passwordConfirmation() == null) {
+ throw new InvalidPasswordException("Password data is required");
+ }
+ if (passwordDto.password().isBlank()) {
+ throw new InvalidPasswordException("Password cannot be blank");
+ }
+ if (!passwordDto.password().equals(passwordDto.passwordConfirmation())) {
+ throw new InvalidPasswordException("Password and password confirmation must be identical");
+ }
+ }
+}
From b879984e3a68442f55937ac8bdee557ac0451eb6 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Fri, 24 Apr 2026 13:41:48 +0200
Subject: [PATCH 02/10] Address review findings for service implementations
---
README.md | 2 +-
.../application/service/DebugService.java | 4 +
.../impl/AdminPasswordServiceImpl.java | 3 +
.../service/impl/DebugServiceImpl.java | 7 ++
.../service/impl/PixelProgramServiceImpl.java | 103 +++++++++++-------
.../impl/UserManagementServiceImpl.java | 11 +-
.../core/domain/dto/UserCreateRequest.java | 6 +
.../port/input/UserCreateInputPort.java | 16 +--
.../domain/port/UserCreateInputPortTest.java | 31 ++++--
9 files changed, 120 insertions(+), 63 deletions(-)
create mode 100644 src/main/java/pl/vtt/wpi/core/domain/dto/UserCreateRequest.java
diff --git a/README.md b/README.md
index 2cfe84d..97847b4 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ Then reference it in your project's `pom.xml`:
The library follows a layered architecture:
-```
+```text
pl.vtt.wpi.core
├── application
│ ├── config # AuthorizationHolder (thread-local auth state)
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
index 2e46182..58809d0 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
@@ -5,6 +5,10 @@
import java.util.List;
public interface DebugService {
+ /**
+ * Best-effort non-atomic read-and-clear operation.
+ * Reading logs and clearing logs happen in separate calls and may race with concurrent log writes.
+ */
List pollLogs();
List peekLogs();
CurrentState getCurrentState();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
index 887f659..8db8c10 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
@@ -22,6 +22,9 @@ public void resetPassword(String secureKey, PasswordDto passwordDto) {
if (passwordDto == null || passwordDto.password() == null || passwordDto.passwordConfirmation() == null) {
throw new IllegalArgumentException("Password data cannot be null");
}
+ if (passwordDto.password().isBlank() || passwordDto.passwordConfirmation().isBlank()) {
+ throw new IllegalArgumentException("Password and password confirmation cannot be blank");
+ }
if (!passwordDto.password().equals(passwordDto.passwordConfirmation())) {
throw new IllegalArgumentException("Password and password confirmation do not match");
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
index 655ba5d..39d876c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
@@ -24,6 +24,13 @@ public DebugServiceImpl(OutputPort> logsOutputPort,
"currentStateOutputPort cannot be null");
}
+ /**
+ * Best-effort non-atomic read-and-clear operation.
+ *
+ * This method performs {@link #peekLogs()} and then issues a separate delete call.
+ * Log entries appended between these calls may be deleted without appearing in the returned list.
+ *
+ */
@Override
public List pollLogs() {
List logs = peekLogs();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
index c93e3ae..ba58429 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -4,6 +4,8 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
import pl.vtt.wpi.core.application.service.PixelProgramService;
@@ -14,9 +16,17 @@
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+/**
+ * Default {@link PixelProgramService} implementation.
+ *
+ * Mutating operations are synchronized per service instance with a lock to avoid
+ * read-modify-write races between {@code load()} and {@code send()} calls.
+ *
+ */
public class PixelProgramServiceImpl implements PixelProgramService {
private final OutputPort> pixelProgramsOutputPort;
private final InputPort> pixelProgramsInputPort;
+ private final Lock mutationLock = new ReentrantLock();
public PixelProgramServiceImpl(OutputPort> pixelProgramsOutputPort,
InputPort> pixelProgramsInputPort) {
@@ -33,37 +43,43 @@ public List getAll() {
@Override
public void insert(List pixelPrograms) {
- List current = getAll();
if (pixelPrograms == null || pixelPrograms.isEmpty()) {
return;
}
- current.addAll(pixelPrograms);
+ mutationLock.lock();
try {
+ List current = getAll();
+ current.addAll(pixelPrograms);
set(current);
} catch (DataInconsistencyException e) {
- throw new RuntimeException("Cannot insert pixel programs", e);
+ throw new IllegalStateException("Cannot insert pixel programs", e);
+ } finally {
+ mutationLock.unlock();
}
}
@Override
public void set(List pixelPrograms) throws DataInconsistencyException {
- if (pixelPrograms == null) {
- throw new DataInconsistencyException("Pixel programs cannot be null");
- }
+ mutationLock.lock();
try {
- pixelProgramsInputPort.send(List.copyOf(pixelPrograms));
- } catch (InputPortException e) {
- Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DataInconsistencyException("Cannot update pixel programs", cause);
+ if (pixelPrograms == null) {
+ throw new DataInconsistencyException("Pixel programs cannot be null");
+ }
+ try {
+ pixelProgramsInputPort.send(List.copyOf(pixelPrograms));
+ } catch (InputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new DataInconsistencyException("Cannot update pixel programs", cause);
+ }
+ } finally {
+ mutationLock.unlock();
}
}
@Override
public PixelProgram get(int index) throws PixelProgramNotFoundException {
- return loadPrograms().stream()
- .filter(pixelProgram -> pixelProgram.index() == index)
- .findFirst()
- .orElseThrow(() -> new PixelProgramNotFoundException("Pixel program %d not found".formatted(index)));
+ List programs = loadPrograms();
+ return programs.get(findListIndex(programs, index));
}
@Override
@@ -73,45 +89,56 @@ public PixelProgram singleton(List pixelProgram) {
@Override
public PixelProgram save(List pixelProgram) {
- List programs = getAll();
- int nextIndex = programs.stream()
- .map(PixelProgram::index)
- .max(Comparator.naturalOrder())
- .map(i -> i + 1)
- .orElse(0);
- PixelProgram saved = new PixelProgram(nextIndex,
- pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
- programs.add(saved);
+ mutationLock.lock();
try {
+ List programs = getAll();
+ int nextIndex = programs.stream()
+ .map(PixelProgram::index)
+ .max(Comparator.naturalOrder())
+ .map(i -> i + 1)
+ .orElse(0);
+ PixelProgram saved = new PixelProgram(nextIndex,
+ pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
+ programs.add(saved);
set(programs);
+ return saved;
} catch (DataInconsistencyException e) {
- throw new RuntimeException("Cannot save pixel program", e);
+ throw new IllegalStateException("Cannot save pixel program", e);
+ } finally {
+ mutationLock.unlock();
}
- return saved;
}
@Override
public PixelProgram update(int index, List pixelProgram) throws PixelProgramNotFoundException {
- List programs = getAll();
- int replaceIndex = findListIndex(programs, index);
- PixelProgram updated = new PixelProgram(index,
- pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
- programs.set(replaceIndex, updated);
+ mutationLock.lock();
try {
+ List programs = getAll();
+ int replaceIndex = findListIndex(programs, index);
+ PixelProgram updated = new PixelProgram(index,
+ pixelProgram == null ? List.of() : List.copyOf(pixelProgram));
+ programs.set(replaceIndex, updated);
set(programs);
+ return updated;
} catch (DataInconsistencyException e) {
- throw new RuntimeException("Cannot update pixel program", e);
+ throw new IllegalStateException("Cannot update pixel program", e);
+ } finally {
+ mutationLock.unlock();
}
- return updated;
}
@Override
public PixelProgram remove(int index) throws PixelProgramNotFoundException, DataInconsistencyException {
- List programs = getAll();
- int removeIndex = findListIndex(programs, index);
- PixelProgram removed = programs.remove(removeIndex);
- set(programs);
- return removed;
+ mutationLock.lock();
+ try {
+ List programs = getAll();
+ int removeIndex = findListIndex(programs, index);
+ PixelProgram removed = programs.remove(removeIndex);
+ set(programs);
+ return removed;
+ } finally {
+ mutationLock.unlock();
+ }
}
private List loadPrograms() {
@@ -120,7 +147,7 @@ private List loadPrograms() {
return pixelPrograms == null ? List.of() : pixelPrograms;
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot read pixel programs", cause);
+ throw new IllegalStateException("Cannot read pixel programs", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
index f46f6ef..00b505f 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
@@ -7,6 +7,7 @@
import pl.vtt.wpi.core.application.exception.UserNotExistsException;
import pl.vtt.wpi.core.application.service.UserManagementService;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.domain.dto.UserCreateRequest;
import pl.vtt.wpi.core.domain.model.User;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.OutputPort;
@@ -15,17 +16,17 @@
public class UserManagementServiceImpl implements UserManagementService {
private final OutputPort> usersOutputPort;
- private final InputPort userCreateInputPort;
+ private final InputPort userCreateRequestInputPort;
private final InputPort changePasswordInputPort;
private final InputPort removeUserInputPort;
public UserManagementServiceImpl(OutputPort> usersOutputPort,
- InputPort userCreateInputPort,
+ InputPort userCreateRequestInputPort,
InputPort changePasswordInputPort,
InputPort removeUserInputPort) {
this.usersOutputPort = Objects.requireNonNull(usersOutputPort, "usersOutputPort cannot be null");
- this.userCreateInputPort = Objects.requireNonNull(userCreateInputPort,
- "userCreateInputPort cannot be null");
+ this.userCreateRequestInputPort = Objects.requireNonNull(userCreateRequestInputPort,
+ "userCreateRequestInputPort cannot be null");
this.changePasswordInputPort = Objects.requireNonNull(changePasswordInputPort,
"changePasswordInputPort cannot be null");
this.removeUserInputPort = Objects.requireNonNull(removeUserInputPort,
@@ -44,7 +45,7 @@ public void createUser(User user, PasswordDto passwordDto)
throw new UserAlreadyExistsException();
}
try {
- userCreateInputPort.send(user);
+ userCreateRequestInputPort.send(new UserCreateRequest(user, passwordDto));
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
throw new RuntimeException("Cannot create user", cause);
diff --git a/src/main/java/pl/vtt/wpi/core/domain/dto/UserCreateRequest.java b/src/main/java/pl/vtt/wpi/core/domain/dto/UserCreateRequest.java
new file mode 100644
index 0000000..a2830da
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/domain/dto/UserCreateRequest.java
@@ -0,0 +1,6 @@
+package pl.vtt.wpi.core.domain.dto;
+
+import pl.vtt.wpi.core.domain.model.User;
+
+public record UserCreateRequest(User user, PasswordDto passwordDto) {
+}
diff --git a/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java b/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java
index 7f88db5..2f41e91 100644
--- a/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java
+++ b/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java
@@ -2,30 +2,30 @@
import pl.vtt.wpi.core.infrastructure.RequestFactory;
import pl.vtt.wpi.core.infrastructure.RequestSender;
+import pl.vtt.wpi.core.domain.dto.UserCreateRequest;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
import pl.vtt.wpi.core.infrastructure.Request;
-import pl.vtt.wpi.core.domain.model.User;
import pl.vtt.wpi.core.domain.model.endpoint.Method;
import pl.vtt.wpi.core.domain.model.endpoint.RequestTarget;
import pl.vtt.wpi.core.domain.port.InputPort;
-public class UserCreateInputPort implements InputPort {
- private final RequestFactory requestFactory;
+public class UserCreateInputPort implements InputPort {
+ private final RequestFactory requestFactory;
private final RequestSender requestSender;
- public UserCreateInputPort(RequestFactory requestFactory,
+ public UserCreateInputPort(RequestFactory requestFactory,
RequestSender requestSender) {
this.requestFactory = requestFactory;
this.requestSender = requestSender;
}
@Override
- public void send(User obj) throws InputPortException {
- if (obj == null) {
- throw new InputPortException("User cannot be null");
+ public void send(UserCreateRequest obj) throws InputPortException {
+ if (obj == null || obj.user() == null || obj.passwordDto() == null) {
+ throw new InputPortException("User and password data cannot be null");
}
try {
- Request request = requestFactory.create(obj, Method.POST, RequestTarget.USERS_CREATE);
+ Request request = requestFactory.create(obj, Method.POST, RequestTarget.USERS_CREATE);
requestSender.send(request);
} catch (Exception e) {
throw new InputPortException("Cannot create user", e);
diff --git a/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java b/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java
index d7a098a..fb17e40 100644
--- a/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java
+++ b/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java
@@ -3,6 +3,8 @@
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.domain.dto.UserCreateRequest;
import pl.vtt.wpi.core.infrastructure.RequestFactory;
import pl.vtt.wpi.core.infrastructure.RequestSender;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
@@ -23,14 +25,16 @@ class UserCreateInputPortTest {
@Test
void send_success_invokesFactoryAndSender() throws Exception {
User user = new User("admin", EnumSet.of(UserGroup.ADMIN));
+ PasswordDto passwordDto = new PasswordDto("secret", "secret");
+ UserCreateRequest userCreateRequest = new UserCreateRequest(user, passwordDto);
AtomicReference usedMethod = new AtomicReference<>();
AtomicReference usedTarget = new AtomicReference<>();
- AtomicReference usedPayload = new AtomicReference<>();
+ AtomicReference usedPayload = new AtomicReference<>();
AtomicReference> sentRequest = new AtomicReference<>();
- Request request = new Request<>(null, Method.POST,
- RequestTarget.USERS_CREATE.url("http://localhost"), null, user);
- RequestFactory requestFactory = (payload, method, target, _) -> {
+ Request request = new Request<>(null, Method.POST,
+ RequestTarget.USERS_CREATE.url("http://localhost"), null, userCreateRequest);
+ RequestFactory requestFactory = (payload, method, target, _) -> {
usedMethod.set(method);
usedTarget.set(target);
usedPayload.set(payload);
@@ -40,11 +44,11 @@ void send_success_invokesFactoryAndSender() throws Exception {
UserCreateInputPort port = new UserCreateInputPort(requestFactory, requestSender);
- port.send(user);
+ port.send(userCreateRequest);
assertEquals(Method.POST, usedMethod.get());
assertEquals(RequestTarget.USERS_CREATE, usedTarget.get());
- assertSame(user, usedPayload.get());
+ assertSame(userCreateRequest, usedPayload.get());
assertSame(request, sentRequest.get());
}
@@ -54,13 +58,13 @@ void send_nullUser_throwsInputPortException() {
InputPortException exception = assertThrows(InputPortException.class, () -> port.send(null));
- assertTrue(exception.getMessage().contains("User cannot be null"));
+ assertTrue(exception.getMessage().contains("User and password data cannot be null"));
}
@Test
void send_factoryException_wrapsWithCause() {
RuntimeException originalCause = new RuntimeException("factory failure");
- RequestFactory requestFactory = (_, _, _, _) -> {
+ RequestFactory requestFactory = (_, _, _, _) -> {
throw originalCause;
};
RequestSender requestSender = _ -> {};
@@ -68,7 +72,10 @@ void send_factoryException_wrapsWithCause() {
UserCreateInputPort port = new UserCreateInputPort(requestFactory, requestSender);
InputPortException exception = assertThrows(InputPortException.class,
- () -> port.send(new User("admin", EnumSet.of(UserGroup.ADMIN))));
+ () -> port.send(new UserCreateRequest(
+ new User("admin", EnumSet.of(UserGroup.ADMIN)),
+ new PasswordDto("secret", "secret")
+ )));
assertTrue(exception.getMessage().contains("Cannot create user"));
assertSame(originalCause, exception.getCause());
@@ -77,9 +84,11 @@ void send_factoryException_wrapsWithCause() {
@Test
void send_senderException_wrapsWithCause() {
User user = new User("admin", EnumSet.of(UserGroup.ADMIN));
+ PasswordDto passwordDto = new PasswordDto("secret", "secret");
+ UserCreateRequest userCreateRequest = new UserCreateRequest(user, passwordDto);
RuntimeException originalCause = new RuntimeException("send failure");
- RequestFactory requestFactory = (payload, method, target, _) ->
+ RequestFactory requestFactory = (payload, method, target, _) ->
new Request<>(null, method, target.url("http://localhost"), null, payload);
RequestSender requestSender = _ -> {
throw originalCause;
@@ -87,7 +96,7 @@ void send_senderException_wrapsWithCause() {
UserCreateInputPort port = new UserCreateInputPort(requestFactory, requestSender);
- InputPortException exception = assertThrows(InputPortException.class, () -> port.send(user));
+ InputPortException exception = assertThrows(InputPortException.class, () -> port.send(userCreateRequest));
assertTrue(exception.getMessage().contains("Cannot create user"));
assertSame(originalCause, exception.getCause());
From 737c5f5906c2efce04aef4c809eb2039775f85f5 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Fri, 24 Apr 2026 14:37:33 +0200
Subject: [PATCH 03/10] Address review comments for runtime, pixel, and
user-create flows
---
README.md | 2 +-
.../service/RuntimeDataService.java | 1 -
.../service/impl/PixelProgramServiceImpl.java | 5 ++-
.../service/impl/RuntimeDataServiceImpl.java | 40 ++++++++++++++-----
.../port/input/UserCreateInputPort.java | 3 ++
.../domain/port/UserCreateInputPortTest.java | 22 +++++++++-
6 files changed, 60 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index 97847b4..69bcc23 100644
--- a/README.md
+++ b/README.md
@@ -151,7 +151,7 @@ mvn test
mvn package
```
-Unit tests are written with JUnit Jupiter 5 and currently include coverage for core port behaviors and login service logic.
+Unit tests are written with JUnit Jupiter 5 and currently include coverage for port-level behavior and application-service logic (including `UserCreateInputPort` / `UserCreateRequest`, login flow, and related service orchestration paths).
## CI
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java b/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
index c61f0c9..29e956b 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
@@ -6,5 +6,4 @@
public interface RuntimeDataService {
RuntimeData read();
void set(RuntimeData runtimeData) throws DataInconsistencyException;
- void update(RuntimeData runtimeData) throws DataInconsistencyException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
index ba58429..0108a07 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -65,6 +65,9 @@ public void set(List pixelPrograms) throws DataInconsistencyExcept
if (pixelPrograms == null) {
throw new DataInconsistencyException("Pixel programs cannot be null");
}
+ if (pixelPrograms.stream().anyMatch(Objects::isNull)) {
+ throw new DataInconsistencyException("Pixel programs cannot contain null elements");
+ }
try {
pixelProgramsInputPort.send(List.copyOf(pixelPrograms));
} catch (InputPortException e) {
@@ -91,7 +94,7 @@ public PixelProgram singleton(List pixelProgram) {
public PixelProgram save(List pixelProgram) {
mutationLock.lock();
try {
- List programs = getAll();
+ List programs = new ArrayList<>(getAll());
int nextIndex = programs.stream()
.map(PixelProgram::index)
.max(Comparator.naturalOrder())
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
index a2ab974..42660af 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
@@ -1,8 +1,11 @@
package pl.vtt.wpi.core.application.service.impl;
+import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Objects;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.service.RuntimeDataService;
+import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.OutputPort;
@@ -11,12 +14,16 @@
public class RuntimeDataServiceImpl implements RuntimeDataService {
private final OutputPort runtimeDataOutputPort;
+ private final OutputPort> pixelProgramsOutputPort;
private final InputPort runtimeDataInputPort;
public RuntimeDataServiceImpl(OutputPort runtimeDataOutputPort,
+ OutputPort> pixelProgramsOutputPort,
InputPort runtimeDataInputPort) {
this.runtimeDataOutputPort = Objects.requireNonNull(runtimeDataOutputPort,
"runtimeDataOutputPort cannot be null");
+ this.pixelProgramsOutputPort = Objects.requireNonNull(pixelProgramsOutputPort,
+ "pixelProgramsOutputPort cannot be null");
this.runtimeDataInputPort = Objects.requireNonNull(runtimeDataInputPort,
"runtimeDataInputPort cannot be null");
}
@@ -34,19 +41,12 @@ public RuntimeData read() {
@Override
public void set(RuntimeData runtimeData) throws DataInconsistencyException {
if (runtimeData == null) {
- throw new DataInconsistencyException("Runtime data cannot be null");
+ throw new NullPointerException("Runtime data cannot be null");
}
+ validatePixelProgramExists(runtimeData);
send(runtimeData, "Cannot set runtime data");
}
- @Override
- public void update(RuntimeData runtimeData) throws DataInconsistencyException {
- if (runtimeData == null) {
- throw new DataInconsistencyException("Runtime data cannot be null");
- }
- send(runtimeData, "Cannot update runtime data");
- }
-
private void send(RuntimeData runtimeData, String message) throws DataInconsistencyException {
try {
runtimeDataInputPort.send(runtimeData);
@@ -55,4 +55,26 @@ private void send(RuntimeData runtimeData, String message) throws DataInconsiste
throw new DataInconsistencyException(message, cause);
}
}
+
+ private void validatePixelProgramExists(RuntimeData runtimeData) throws DataInconsistencyException {
+ if (runtimeData.pixelProgram() == null) {
+ return;
+ }
+ List pixelPrograms;
+ try {
+ pixelPrograms = pixelProgramsOutputPort.load();
+ } catch (OutputPortException e) {
+ Throwable cause = e.getCause() == null ? e : e.getCause();
+ throw new DataInconsistencyException("Cannot validate runtime data", cause);
+ }
+
+ boolean exists = pixelPrograms != null
+ && pixelPrograms.stream().anyMatch(program ->
+ program != null && program.index() == runtimeData.pixelProgram());
+
+ if (!exists) {
+ String message = "Pixel program %d does not exist".formatted(runtimeData.pixelProgram());
+ throw new DataInconsistencyException(message, new NoSuchElementException(message));
+ }
+ }
}
diff --git a/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java b/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java
index 2f41e91..e0c185e 100644
--- a/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java
+++ b/src/main/java/pl/vtt/wpi/core/domain/port/input/UserCreateInputPort.java
@@ -28,6 +28,9 @@ public void send(UserCreateRequest obj) throws InputPortException {
Request request = requestFactory.create(obj, Method.POST, RequestTarget.USERS_CREATE);
requestSender.send(request);
} catch (Exception e) {
+ if (e instanceof InputPortException inputPortException) {
+ throw inputPortException;
+ }
throw new InputPortException("Cannot create user", e);
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java b/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java
index fb17e40..f87f27c 100644
--- a/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java
+++ b/src/test/java/pl/vtt/wpi/core/domain/port/UserCreateInputPortTest.java
@@ -53,7 +53,7 @@ void send_success_invokesFactoryAndSender() throws Exception {
}
@Test
- void send_nullUser_throwsInputPortException() {
+ void send_nullRequest_throwsInputPortException() {
UserCreateInputPort port = new UserCreateInputPort((_, _, _, _) -> null, _ -> {});
InputPortException exception = assertThrows(InputPortException.class, () -> port.send(null));
@@ -61,6 +61,26 @@ void send_nullUser_throwsInputPortException() {
assertTrue(exception.getMessage().contains("User and password data cannot be null"));
}
+ @Test
+ void send_nullUserInRequest_throwsInputPortException() {
+ UserCreateInputPort port = new UserCreateInputPort((_, _, _, _) -> null, _ -> {});
+
+ InputPortException exception = assertThrows(InputPortException.class, () ->
+ port.send(new UserCreateRequest(null, new PasswordDto("secret", "secret"))));
+
+ assertTrue(exception.getMessage().contains("User and password data cannot be null"));
+ }
+
+ @Test
+ void send_nullPasswordInRequest_throwsInputPortException() {
+ UserCreateInputPort port = new UserCreateInputPort((_, _, _, _) -> null, _ -> {});
+
+ InputPortException exception = assertThrows(InputPortException.class, () ->
+ port.send(new UserCreateRequest(new User("admin", EnumSet.of(UserGroup.ADMIN)), null)));
+
+ assertTrue(exception.getMessage().contains("User and password data cannot be null"));
+ }
+
@Test
void send_factoryException_wrapsWithCause() {
RuntimeException originalCause = new RuntimeException("factory failure");
From 4f892a95521daef291a980b612fbcf3980b2ae48 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Fri, 24 Apr 2026 20:36:38 +0200
Subject: [PATCH 04/10] Add unit tests for application service implementations
---
.../impl/AdminPasswordServiceImplTest.java | 33 ++++++++++
.../service/impl/DebugServiceImplTest.java | 43 +++++++++++++
.../impl/DeviceInfoServiceImplTest.java | 30 +++++++++
.../NetworkConfigurationServiceImplTest.java | 21 ++++++
.../impl/PixelProgramServiceImplTest.java | 45 +++++++++++++
.../service/impl/RebootServiceImplTest.java | 32 ++++++++++
.../impl/RuntimeDataServiceImplTest.java | 56 ++++++++++++++++
.../impl/UserManagementServiceImplTest.java | 64 +++++++++++++++++++
8 files changed, 324 insertions(+)
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
create mode 100644 src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
new file mode 100644
index 0000000..e1cd695
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
@@ -0,0 +1,33 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.domain.port.InputPort;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class AdminPasswordServiceImplTest {
+
+ @Test
+ void resetPassword_validData_sendsRequest() {
+ AtomicReference sent = new AtomicReference<>();
+ InputPort inputPort = sent::set;
+ AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(inputPort);
+
+ PasswordDto passwordDto = new PasswordDto("secret", "secret");
+ service.resetPassword("secure-key", passwordDto);
+
+ assertEquals("secure-key", sent.get().secureKey());
+ assertEquals(passwordDto, sent.get().passwordDto());
+ }
+
+ @Test
+ void resetPassword_blankPassword_throwsIllegalArgumentException() {
+ AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(_ -> {});
+
+ assertThrows(IllegalArgumentException.class,
+ () -> service.resetPassword("secure-key", new PasswordDto(" ", " ")));
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
new file mode 100644
index 0000000..d9dcb7d
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
@@ -0,0 +1,43 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.domain.model.device.CurrentState;
+import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.OutputPort;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DebugServiceImplTest {
+
+ @Test
+ void pollLogs_readsAndClearsLogs() {
+ OutputPort> logsOutputPort = () -> List.of("a", "b");
+ AtomicBoolean cleared = new AtomicBoolean(false);
+ InputPort deleteInputPort = _ -> cleared.set(true);
+
+ DebugServiceImpl service = new DebugServiceImpl(logsOutputPort, deleteInputPort,
+ () -> new CurrentState.Builder().build());
+
+ List logs = service.pollLogs();
+
+ assertEquals(List.of("a", "b"), logs);
+ assertTrue(cleared.get());
+ }
+
+ @Test
+ void peekLogs_outputFailure_wrapsException() {
+ DebugServiceImpl service = new DebugServiceImpl(
+ () -> { throw new OutputPortException("x", new IllegalStateException("boom")); },
+ _ -> {},
+ () -> new CurrentState.Builder().build()
+ );
+
+ RuntimeException exception = assertThrows(RuntimeException.class, service::peekLogs);
+ assertEquals("Cannot read logs", exception.getMessage());
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
new file mode 100644
index 0000000..156a90d
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
@@ -0,0 +1,30 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DeviceInfoServiceImplTest {
+
+ @Test
+ void read_success_returnsDeviceInfo() {
+ DeviceInfo expected = new DeviceInfo(UUID.randomUUID(), "m", "p", "a", "1", "auth", "mail");
+ DeviceInfoServiceImpl service = new DeviceInfoServiceImpl(() -> expected);
+
+ assertEquals(expected, service.read());
+ }
+
+ @Test
+ void read_portFailure_wrapsException() {
+ DeviceInfoServiceImpl service = new DeviceInfoServiceImpl(
+ () -> { throw new OutputPortException("x", new IllegalArgumentException("boom")); }
+ );
+
+ RuntimeException exception = assertThrows(RuntimeException.class, service::read);
+ assertEquals("Cannot read device info", exception.getMessage());
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java
new file mode 100644
index 0000000..cf71eae
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java
@@ -0,0 +1,21 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
+import pl.vtt.wpi.core.domain.model.device.WifiConfig;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class NetworkConfigurationServiceImplTest {
+
+ @Test
+ void config_accessPointConfig_isMappedToWifiConfig() {
+ AtomicReference sent = new AtomicReference<>();
+ NetworkConfigurationServiceImpl service = new NetworkConfigurationServiceImpl(sent::set);
+
+ service.config(new AccessPointConfig("ap", "pass"));
+
+ assertEquals(new WifiConfig("ap", "pass"), sent.get());
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
new file mode 100644
index 0000000..a89e164
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
@@ -0,0 +1,45 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
+import pl.vtt.wpi.core.domain.model.device.PixelProgram;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class PixelProgramServiceImplTest {
+
+ @Test
+ void set_nullElement_throwsDataInconsistencyException() {
+ PixelProgramServiceImpl service = new PixelProgramServiceImpl(() -> List.of(), _ -> {});
+
+ DataInconsistencyException exception = assertThrows(DataInconsistencyException.class,
+ () -> service.set(List.of((PixelProgram) null)));
+
+ assertEquals("Pixel programs cannot contain null elements", exception.getMessage());
+ }
+
+ @Test
+ void save_addsProgramWithNextIndexAndSendsAll() {
+ AtomicReference> sent = new AtomicReference<>();
+ PixelProgramServiceImpl service = new PixelProgramServiceImpl(
+ () -> List.of(new PixelProgram(0, List.of())),
+ sent::set
+ );
+
+ PixelProgram saved = service.save(List.of());
+
+ assertEquals(1, saved.index());
+ assertEquals(2, sent.get().size());
+ }
+
+ @Test
+ void get_missingProgram_throwsPixelProgramNotFoundException() {
+ PixelProgramServiceImpl service = new PixelProgramServiceImpl(() -> List.of(), _ -> {});
+
+ assertThrows(PixelProgramNotFoundException.class, () -> service.get(7));
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
new file mode 100644
index 0000000..fca9235
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
@@ -0,0 +1,32 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RebootServiceImplTest {
+
+ @Test
+ void reboot_success_sendsNullPayload() {
+ AtomicBoolean called = new AtomicBoolean(false);
+ RebootServiceImpl service = new RebootServiceImpl(_ -> called.set(true));
+
+ service.reboot();
+
+ assertTrue(called.get());
+ }
+
+ @Test
+ void reboot_portFailure_wrapsException() {
+ RebootServiceImpl service = new RebootServiceImpl(
+ _ -> { throw new InputPortException("x", new IllegalStateException("boom")); }
+ );
+
+ RuntimeException exception = assertThrows(RuntimeException.class, service::reboot);
+ assertEquals("Cannot reboot device", exception.getMessage());
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
new file mode 100644
index 0000000..8b8f3fe
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
@@ -0,0 +1,56 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.domain.model.device.PixelProgram;
+import pl.vtt.wpi.core.domain.model.device.RuntimeData;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class RuntimeDataServiceImplTest {
+
+ @Test
+ void set_nullRuntimeData_throwsNullPointerException() {
+ RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(() -> null, () -> List.of(), _ -> {});
+
+ NullPointerException exception = assertThrows(NullPointerException.class, () -> service.set(null));
+
+ assertEquals("Runtime data cannot be null", exception.getMessage());
+ }
+
+ @Test
+ void set_missingPixelProgram_throwsDataInconsistencyExceptionWithNoSuchElementCause() {
+ RuntimeData runtimeData = new RuntimeData.Builder().pixelProgram(10).build();
+ RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(
+ () -> runtimeData,
+ () -> List.of(new PixelProgram(0, List.of())),
+ _ -> {}
+ );
+
+ DataInconsistencyException exception = assertThrows(DataInconsistencyException.class,
+ () -> service.set(runtimeData));
+
+ assertEquals("Pixel program 10 does not exist", exception.getMessage());
+ assertInstanceOf(NoSuchElementException.class, exception.getCause());
+ }
+
+ @Test
+ void set_existingPixelProgram_sendsRuntimeData() throws Exception {
+ RuntimeData runtimeData = new RuntimeData.Builder().pixelProgram(1).build();
+ AtomicReference sent = new AtomicReference<>();
+ RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(
+ () -> runtimeData,
+ () -> List.of(new PixelProgram(1, List.of())),
+ sent::set
+ );
+
+ service.set(runtimeData);
+
+ assertEquals(runtimeData, sent.get());
+ }
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
new file mode 100644
index 0000000..ecfbc5d
--- /dev/null
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
@@ -0,0 +1,64 @@
+package pl.vtt.wpi.core.application.service.impl;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
+import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
+import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.domain.dto.UserCreateRequest;
+import pl.vtt.wpi.core.domain.model.User;
+import pl.vtt.wpi.core.domain.model.endpoint.UserGroup;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class UserManagementServiceImplTest {
+
+ @Test
+ void createUser_success_sendsUserCreateRequest() throws Exception {
+ User user = new User("john", EnumSet.of(UserGroup.ADMIN));
+ PasswordDto passwordDto = new PasswordDto("secret", "secret");
+ AtomicReference sent = new AtomicReference<>();
+
+ UserManagementServiceImpl service = new UserManagementServiceImpl(
+ List::of,
+ sent::set,
+ _ -> {},
+ _ -> {}
+ );
+
+ service.createUser(user, passwordDto);
+
+ assertEquals(new UserCreateRequest(user, passwordDto), sent.get());
+ }
+
+ @Test
+ void createUser_existingUser_throwsUserAlreadyExistsException() {
+ User user = new User("john", EnumSet.of(UserGroup.ADMIN));
+ PasswordDto passwordDto = new PasswordDto("secret", "secret");
+
+ UserManagementServiceImpl service = new UserManagementServiceImpl(
+ () -> List.of(user),
+ _ -> {},
+ _ -> {},
+ _ -> {}
+ );
+
+ assertThrows(UserAlreadyExistsException.class, () -> service.createUser(user, passwordDto));
+ }
+
+ @Test
+ void changePassword_blankPassword_throwsInvalidPasswordException() {
+ UserManagementServiceImpl service = new UserManagementServiceImpl(
+ List::of,
+ _ -> {},
+ _ -> {},
+ _ -> {}
+ );
+
+ assertThrows(InvalidPasswordException.class,
+ () -> service.changePassword(new PasswordDto(" ", " ")));
+ }
+}
From 0bc4a34c96b981a86bcbe5562115f5fb9c557d81 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Fri, 24 Apr 2026 20:45:51 +0200
Subject: [PATCH 05/10] Fix null-element test setup in
PixelProgramServiceImplTest
---
.../application/service/impl/PixelProgramServiceImplTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
index a89e164..8f4a993 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
@@ -17,7 +17,7 @@ void set_nullElement_throwsDataInconsistencyException() {
PixelProgramServiceImpl service = new PixelProgramServiceImpl(() -> List.of(), _ -> {});
DataInconsistencyException exception = assertThrows(DataInconsistencyException.class,
- () -> service.set(List.of((PixelProgram) null)));
+ () -> service.set(new java.util.ArrayList<>(java.util.Collections.singletonList(null))));
assertEquals("Pixel programs cannot contain null elements", exception.getMessage());
}
From d7165143a32a9232aeff89f0540f3eece735b433 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Mon, 27 Apr 2026 13:24:52 +0200
Subject: [PATCH 06/10] Introduce checked service exceptions across application
services
---
.../AdminPasswordServiceException.java | 11 ++++++++
.../exception/DebugServiceException.java | 11 ++++++++
.../exception/DeviceInfoServiceException.java | 11 ++++++++
.../exception/LoginServiceException.java | 11 ++++++++
.../NetworkConfigurationServiceException.java | 11 ++++++++
.../PixelProgramServiceException.java | 11 ++++++++
.../exception/RebootServiceException.java | 11 ++++++++
.../RuntimeDataServiceException.java | 11 ++++++++
.../UserManagementServiceException.java | 11 ++++++++
.../service/AdminPasswordService.java | 3 ++-
.../application/service/DebugService.java | 7 ++---
.../service/DeviceInfoService.java | 3 ++-
.../application/service/LoginService.java | 3 ++-
.../service/NetworkConfigurationService.java | 5 ++--
.../service/PixelProgramService.java | 13 ++++-----
.../application/service/RebootService.java | 4 ++-
.../service/RuntimeDataService.java | 5 ++--
.../service/UserManagementService.java | 7 ++---
.../impl/AdminPasswordServiceImpl.java | 13 ++++-----
.../service/impl/DebugServiceImpl.java | 13 ++++-----
.../service/impl/DeviceInfoServiceImpl.java | 5 ++--
.../service/impl/LoginServiceImpl.java | 9 ++++---
.../impl/NetworkConfigurationServiceImpl.java | 13 ++++-----
.../service/impl/PixelProgramServiceImpl.java | 27 +++++++++++--------
.../service/impl/RebootServiceImpl.java | 5 ++--
.../service/impl/RuntimeDataServiceImpl.java | 15 ++++++-----
.../impl/UserManagementServiceImpl.java | 21 ++++++++-------
.../impl/AdminPasswordServiceImplTest.java | 7 ++---
.../service/impl/DebugServiceImplTest.java | 5 ++--
.../impl/DeviceInfoServiceImplTest.java | 5 ++--
.../service/impl/LoginServiceImplTest.java | 6 +++--
.../NetworkConfigurationServiceImplTest.java | 2 +-
.../impl/PixelProgramServiceImplTest.java | 2 +-
.../service/impl/RebootServiceImplTest.java | 5 ++--
.../impl/RuntimeDataServiceImplTest.java | 6 +++--
.../impl/UserManagementServiceImplTest.java | 2 +-
36 files changed, 220 insertions(+), 90 deletions(-)
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java
new file mode 100644
index 0000000..87a32ac
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class AdminPasswordServiceException extends Exception {
+ public AdminPasswordServiceException(String message) {
+ super(message);
+ }
+
+ public AdminPasswordServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java
new file mode 100644
index 0000000..72b1b76
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class DebugServiceException extends Exception {
+ public DebugServiceException(String message) {
+ super(message);
+ }
+
+ public DebugServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java
new file mode 100644
index 0000000..38e6b09
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class DeviceInfoServiceException extends Exception {
+ public DeviceInfoServiceException(String message) {
+ super(message);
+ }
+
+ public DeviceInfoServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java
new file mode 100644
index 0000000..23ff371
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class LoginServiceException extends Exception {
+ public LoginServiceException(String message) {
+ super(message);
+ }
+
+ public LoginServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java
new file mode 100644
index 0000000..0616a38
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class NetworkConfigurationServiceException extends Exception {
+ public NetworkConfigurationServiceException(String message) {
+ super(message);
+ }
+
+ public NetworkConfigurationServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java
new file mode 100644
index 0000000..abb04c7
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class PixelProgramServiceException extends Exception {
+ public PixelProgramServiceException(String message) {
+ super(message);
+ }
+
+ public PixelProgramServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java
new file mode 100644
index 0000000..9d47290
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class RebootServiceException extends Exception {
+ public RebootServiceException(String message) {
+ super(message);
+ }
+
+ public RebootServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java
new file mode 100644
index 0000000..9cfd572
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class RuntimeDataServiceException extends Exception {
+ public RuntimeDataServiceException(String message) {
+ super(message);
+ }
+
+ public RuntimeDataServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java
new file mode 100644
index 0000000..39ef1d1
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class UserManagementServiceException extends Exception {
+ public UserManagementServiceException(String message) {
+ super(message);
+ }
+
+ public UserManagementServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java b/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java
index 36c51d8..9a3b84b 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java
@@ -1,7 +1,8 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
+import pl.vtt.wpi.core.application.exception.AdminPasswordServiceException;
public interface AdminPasswordService {
- void resetPassword(String secureKey, PasswordDto passwordDto);
+ void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
index 58809d0..8872309 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
@@ -1,6 +1,7 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.domain.model.device.CurrentState;
+import pl.vtt.wpi.core.application.exception.DebugServiceException;
import java.util.List;
@@ -9,7 +10,7 @@ public interface DebugService {
* Best-effort non-atomic read-and-clear operation.
* Reading logs and clearing logs happen in separate calls and may race with concurrent log writes.
*/
- List pollLogs();
- List peekLogs();
- CurrentState getCurrentState();
+ List pollLogs() throws DebugServiceException;
+ List peekLogs() throws DebugServiceException;
+ CurrentState getCurrentState() throws DebugServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java b/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java
index ec6e918..818e852 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java
@@ -1,7 +1,8 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
+import pl.vtt.wpi.core.application.exception.DeviceInfoServiceException;
public interface DeviceInfoService {
- DeviceInfo read();
+ DeviceInfo read() throws DeviceInfoServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java b/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java
index 4cc9f06..a199d1c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java
@@ -2,11 +2,12 @@
import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
+import pl.vtt.wpi.core.application.exception.LoginServiceException;
import pl.vtt.wpi.core.domain.model.Credentials;
public interface LoginService {
Credentials login(String username, String password)
- throws IncorrectUsernameOrPasswordException;
+ throws IncorrectUsernameOrPasswordException, LoginServiceException;
default void logout() {
AuthorizationHolder.clear();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java b/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
index 36ff65c..e831c3a 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
@@ -2,8 +2,9 @@
import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
import pl.vtt.wpi.core.domain.model.device.WifiConfig;
+import pl.vtt.wpi.core.application.exception.NetworkConfigurationServiceException;
public interface NetworkConfigurationService {
- void config(WifiConfig config);
- void config(AccessPointConfig config);
+ void config(WifiConfig config) throws NetworkConfigurationServiceException;
+ void config(AccessPointConfig config) throws NetworkConfigurationServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java b/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java
index b63bb88..4343330 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java
@@ -2,23 +2,24 @@
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
+import pl.vtt.wpi.core.application.exception.PixelProgramServiceException;
import pl.vtt.wpi.core.domain.model.color.RgbColor;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import java.util.List;
public interface PixelProgramService {
- List getAll();
- void insert(List pixelPrograms);
+ List getAll() throws PixelProgramServiceException;
+ void insert(List pixelPrograms) throws PixelProgramServiceException;
void set(List pixelPrograms)
throws DataInconsistencyException;
PixelProgram get(int index)
- throws PixelProgramNotFoundException;
+ throws PixelProgramNotFoundException, PixelProgramServiceException;
PixelProgram singleton(List pixelProgram);
- PixelProgram save(List pixelProgram);
+ PixelProgram save(List pixelProgram) throws PixelProgramServiceException;
PixelProgram update(int index, List pixelProgram)
- throws PixelProgramNotFoundException;
+ throws PixelProgramNotFoundException, PixelProgramServiceException;
PixelProgram remove(int index)
- throws PixelProgramNotFoundException, DataInconsistencyException;
+ throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java b/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java
index e69255e..ebf31c5 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java
@@ -1,5 +1,7 @@
package pl.vtt.wpi.core.application.service;
+import pl.vtt.wpi.core.application.exception.RebootServiceException;
+
public interface RebootService {
- void reboot();
+ void reboot() throws RebootServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java b/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
index 29e956b..b8d993d 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
@@ -1,9 +1,10 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.application.exception.RuntimeDataServiceException;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
public interface RuntimeDataService {
- RuntimeData read();
- void set(RuntimeData runtimeData) throws DataInconsistencyException;
+ RuntimeData read() throws RuntimeDataServiceException;
+ void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java b/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java
index 29391c8..918ad3b 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java
@@ -3,16 +3,17 @@
import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
import pl.vtt.wpi.core.application.exception.UserNotExistsException;
+import pl.vtt.wpi.core.application.exception.UserManagementServiceException;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.model.User;
public interface UserManagementService {
void createUser(User user, PasswordDto passwordDto)
- throws UserAlreadyExistsException, InvalidPasswordException;
+ throws UserAlreadyExistsException, InvalidPasswordException, UserManagementServiceException;
void changePassword(PasswordDto passwordDto)
- throws InvalidPasswordException;
+ throws InvalidPasswordException, UserManagementServiceException;
void removeUser(User user)
- throws UserNotExistsException;
+ throws UserNotExistsException, UserManagementServiceException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
index 8db8c10..280c1aa 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
@@ -1,6 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.AdminPasswordServiceException;
import pl.vtt.wpi.core.application.service.AdminPasswordService;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.port.InputPort;
@@ -15,24 +16,24 @@ public AdminPasswordServiceImpl(InputPort adminPasswo
}
@Override
- public void resetPassword(String secureKey, PasswordDto passwordDto) {
+ public void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordServiceException {
if (secureKey == null || secureKey.isBlank()) {
- throw new IllegalArgumentException("Secure key cannot be null or blank");
+ throw new AdminPasswordServiceException("Secure key cannot be null or blank");
}
if (passwordDto == null || passwordDto.password() == null || passwordDto.passwordConfirmation() == null) {
- throw new IllegalArgumentException("Password data cannot be null");
+ throw new AdminPasswordServiceException("Password data cannot be null");
}
if (passwordDto.password().isBlank() || passwordDto.passwordConfirmation().isBlank()) {
- throw new IllegalArgumentException("Password and password confirmation cannot be blank");
+ throw new AdminPasswordServiceException("Password and password confirmation cannot be blank");
}
if (!passwordDto.password().equals(passwordDto.passwordConfirmation())) {
- throw new IllegalArgumentException("Password and password confirmation do not match");
+ throw new AdminPasswordServiceException("Password and password confirmation do not match");
}
try {
adminPasswordResetInputPort.send(new AdminPasswordResetRequest(secureKey, passwordDto));
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot reset admin password", cause);
+ throw new AdminPasswordServiceException("Cannot reset admin password", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
index 39d876c..43138eb 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
@@ -2,6 +2,7 @@
import java.util.List;
import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.DebugServiceException;
import pl.vtt.wpi.core.application.service.DebugService;
import pl.vtt.wpi.core.domain.model.device.CurrentState;
import pl.vtt.wpi.core.domain.port.InputPort;
@@ -32,34 +33,34 @@ public DebugServiceImpl(OutputPort> logsOutputPort,
*
*/
@Override
- public List pollLogs() {
+ public List pollLogs() throws DebugServiceException {
List logs = peekLogs();
try {
logsDeleteInputPort.send(null);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot clear logs", cause);
+ throw new DebugServiceException("Cannot clear logs", cause);
}
return logs;
}
@Override
- public List peekLogs() {
+ public List peekLogs() throws DebugServiceException {
try {
return logsOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot read logs", cause);
+ throw new DebugServiceException("Cannot read logs", cause);
}
}
@Override
- public CurrentState getCurrentState() {
+ public CurrentState getCurrentState() throws DebugServiceException {
try {
return currentStateOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot read current state", cause);
+ throw new DebugServiceException("Cannot read current state", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
index 7e5e31c..b2db8b4 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
@@ -1,6 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.DeviceInfoServiceException;
import pl.vtt.wpi.core.application.service.DeviceInfoService;
import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
import pl.vtt.wpi.core.domain.port.OutputPort;
@@ -15,12 +16,12 @@ public DeviceInfoServiceImpl(OutputPort deviceInfoOutputPort) {
}
@Override
- public DeviceInfo read() {
+ public DeviceInfo read() throws DeviceInfoServiceException {
try {
return deviceInfoOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot read device info", cause);
+ throw new DeviceInfoServiceException("Cannot read device info", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
index f4b0912..ed6b901 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
@@ -2,6 +2,7 @@
import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
+import pl.vtt.wpi.core.application.exception.LoginServiceException;
import pl.vtt.wpi.core.application.service.LoginService;
import pl.vtt.wpi.core.domain.port.OutputPort;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
@@ -16,7 +17,7 @@ public LoginServiceImpl(OutputPort authPort) {
@Override
public Credentials login(String username, String password)
- throws IncorrectUsernameOrPasswordException {
+ throws IncorrectUsernameOrPasswordException, LoginServiceException {
if (username == null || username.isBlank() || password == null || password.isBlank()) {
throw new IncorrectUsernameOrPasswordException();
}
@@ -25,14 +26,14 @@ public Credentials login(String username, String password)
Credentials credentials = getCredentials();
AuthorizationHolder.authorize(credentials);
return credentials;
- } catch (IncorrectUsernameOrPasswordException | RuntimeException e) {
+ } catch (IncorrectUsernameOrPasswordException | LoginServiceException e) {
AuthorizationHolder.clear();
throw e;
}
}
private Credentials getCredentials()
- throws IncorrectUsernameOrPasswordException {
+ throws IncorrectUsernameOrPasswordException, LoginServiceException {
final Credentials responseBody;
try {
responseBody = authPort.load();
@@ -41,7 +42,7 @@ private Credentials getCredentials()
throw incorrect;
}
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Login failed", cause);
+ throw new LoginServiceException("Login failed", cause);
}
if (responseBody == null) {
throw new IncorrectUsernameOrPasswordException();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
index 651bd98..8f0b6d4 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
@@ -1,6 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.NetworkConfigurationServiceException;
import pl.vtt.wpi.core.application.service.NetworkConfigurationService;
import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
import pl.vtt.wpi.core.domain.model.device.WifiConfig;
@@ -16,27 +17,27 @@ public NetworkConfigurationServiceImpl(InputPort wifiConfigInputPort
}
@Override
- public void config(WifiConfig config) {
+ public void config(WifiConfig config) throws NetworkConfigurationServiceException {
send(config);
}
@Override
- public void config(AccessPointConfig config) {
+ public void config(AccessPointConfig config) throws NetworkConfigurationServiceException {
if (config == null) {
- throw new IllegalArgumentException("Access point config cannot be null");
+ throw new NetworkConfigurationServiceException("Access point config cannot be null");
}
send(new WifiConfig(config.ssid(), config.password()));
}
- private void send(WifiConfig config) {
+ private void send(WifiConfig config) throws NetworkConfigurationServiceException {
if (config == null) {
- throw new IllegalArgumentException("WiFi config cannot be null");
+ throw new NetworkConfigurationServiceException("WiFi config cannot be null");
}
try {
wifiConfigInputPort.send(config);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot update network configuration", cause);
+ throw new NetworkConfigurationServiceException("Cannot update network configuration", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
index 0108a07..fddd5e2 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -8,6 +8,7 @@
import java.util.concurrent.locks.ReentrantLock;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
+import pl.vtt.wpi.core.application.exception.PixelProgramServiceException;
import pl.vtt.wpi.core.application.service.PixelProgramService;
import pl.vtt.wpi.core.domain.model.color.RgbColor;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
@@ -37,12 +38,12 @@ public PixelProgramServiceImpl(OutputPort> pixelProgramsOutpu
}
@Override
- public List getAll() {
+ public List getAll() throws PixelProgramServiceException {
return new ArrayList<>(loadPrograms());
}
@Override
- public void insert(List pixelPrograms) {
+ public void insert(List pixelPrograms) throws PixelProgramServiceException {
if (pixelPrograms == null || pixelPrograms.isEmpty()) {
return;
}
@@ -52,7 +53,7 @@ public void insert(List pixelPrograms) {
current.addAll(pixelPrograms);
set(current);
} catch (DataInconsistencyException e) {
- throw new IllegalStateException("Cannot insert pixel programs", e);
+ throw new PixelProgramServiceException("Cannot insert pixel programs", e);
} finally {
mutationLock.unlock();
}
@@ -80,7 +81,7 @@ public void set(List pixelPrograms) throws DataInconsistencyExcept
}
@Override
- public PixelProgram get(int index) throws PixelProgramNotFoundException {
+ public PixelProgram get(int index) throws PixelProgramNotFoundException, PixelProgramServiceException {
List programs = loadPrograms();
return programs.get(findListIndex(programs, index));
}
@@ -91,7 +92,7 @@ public PixelProgram singleton(List pixelProgram) {
}
@Override
- public PixelProgram save(List pixelProgram) {
+ public PixelProgram save(List pixelProgram) throws PixelProgramServiceException {
mutationLock.lock();
try {
List programs = new ArrayList<>(getAll());
@@ -106,14 +107,15 @@ public PixelProgram save(List pixelProgram) {
set(programs);
return saved;
} catch (DataInconsistencyException e) {
- throw new IllegalStateException("Cannot save pixel program", e);
+ throw new PixelProgramServiceException("Cannot save pixel program", e);
} finally {
mutationLock.unlock();
}
}
@Override
- public PixelProgram update(int index, List pixelProgram) throws PixelProgramNotFoundException {
+ public PixelProgram update(int index, List pixelProgram)
+ throws PixelProgramNotFoundException, PixelProgramServiceException {
mutationLock.lock();
try {
List programs = getAll();
@@ -124,14 +126,15 @@ public PixelProgram update(int index, List pixelProgram) throws PixelP
set(programs);
return updated;
} catch (DataInconsistencyException e) {
- throw new IllegalStateException("Cannot update pixel program", e);
+ throw new PixelProgramServiceException("Cannot update pixel program", e);
} finally {
mutationLock.unlock();
}
}
@Override
- public PixelProgram remove(int index) throws PixelProgramNotFoundException, DataInconsistencyException {
+ public PixelProgram remove(int index)
+ throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramServiceException {
mutationLock.lock();
try {
List programs = getAll();
@@ -139,18 +142,20 @@ public PixelProgram remove(int index) throws PixelProgramNotFoundException, Data
PixelProgram removed = programs.remove(removeIndex);
set(programs);
return removed;
+ } catch (PixelProgramServiceException e) {
+ throw e;
} finally {
mutationLock.unlock();
}
}
- private List loadPrograms() {
+ private List loadPrograms() throws PixelProgramServiceException {
try {
List pixelPrograms = pixelProgramsOutputPort.load();
return pixelPrograms == null ? List.of() : pixelPrograms;
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new IllegalStateException("Cannot read pixel programs", cause);
+ throw new PixelProgramServiceException("Cannot read pixel programs", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
index cd906ba..9ab534f 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
@@ -1,6 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
+import pl.vtt.wpi.core.application.exception.RebootServiceException;
import pl.vtt.wpi.core.application.service.RebootService;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
@@ -14,12 +15,12 @@ public RebootServiceImpl(InputPort rebootInputPort) {
}
@Override
- public void reboot() {
+ public void reboot() throws RebootServiceException {
try {
rebootInputPort.send(null);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot reboot device", cause);
+ throw new RebootServiceException("Cannot reboot device", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
index 42660af..ef6af6c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
@@ -4,6 +4,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.application.exception.RuntimeDataServiceException;
import pl.vtt.wpi.core.application.service.RuntimeDataService;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
@@ -29,30 +30,30 @@ public RuntimeDataServiceImpl(OutputPort runtimeDataOutputPort,
}
@Override
- public RuntimeData read() {
+ public RuntimeData read() throws RuntimeDataServiceException {
try {
return runtimeDataOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot read runtime data", cause);
+ throw new RuntimeDataServiceException("Cannot read runtime data", cause);
}
}
@Override
- public void set(RuntimeData runtimeData) throws DataInconsistencyException {
+ public void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataServiceException {
if (runtimeData == null) {
- throw new NullPointerException("Runtime data cannot be null");
+ throw new RuntimeDataServiceException("Runtime data cannot be null");
}
validatePixelProgramExists(runtimeData);
- send(runtimeData, "Cannot set runtime data");
+ send(runtimeData);
}
- private void send(RuntimeData runtimeData, String message) throws DataInconsistencyException {
+ private void send(RuntimeData runtimeData) throws RuntimeDataServiceException {
try {
runtimeDataInputPort.send(runtimeData);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DataInconsistencyException(message, cause);
+ throw new RuntimeDataServiceException("Cannot set runtime data", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
index 00b505f..de4a0ac 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
@@ -4,6 +4,7 @@
import java.util.Objects;
import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
+import pl.vtt.wpi.core.application.exception.UserManagementServiceException;
import pl.vtt.wpi.core.application.exception.UserNotExistsException;
import pl.vtt.wpi.core.application.service.UserManagementService;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
@@ -35,10 +36,10 @@ public UserManagementServiceImpl(OutputPort> usersOutputPort,
@Override
public void createUser(User user, PasswordDto passwordDto)
- throws UserAlreadyExistsException, InvalidPasswordException {
+ throws UserAlreadyExistsException, InvalidPasswordException, UserManagementServiceException {
validatePassword(passwordDto);
if (user == null) {
- throw new IllegalArgumentException("User cannot be null");
+ throw new UserManagementServiceException("User cannot be null");
}
boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
if (exists) {
@@ -48,27 +49,27 @@ public void createUser(User user, PasswordDto passwordDto)
userCreateRequestInputPort.send(new UserCreateRequest(user, passwordDto));
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot create user", cause);
+ throw new UserManagementServiceException("Cannot create user", cause);
}
}
@Override
public void changePassword(PasswordDto passwordDto)
- throws InvalidPasswordException {
+ throws InvalidPasswordException, UserManagementServiceException {
validatePassword(passwordDto);
try {
changePasswordInputPort.send(passwordDto);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot change password", cause);
+ throw new UserManagementServiceException("Cannot change password", cause);
}
}
@Override
public void removeUser(User user)
- throws UserNotExistsException {
+ throws UserNotExistsException, UserManagementServiceException {
if (user == null) {
- throw new IllegalArgumentException("User cannot be null");
+ throw new UserManagementServiceException("User cannot be null");
}
boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
if (!exists) {
@@ -78,17 +79,17 @@ public void removeUser(User user)
removeUserInputPort.send(user);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot remove user", cause);
+ throw new UserManagementServiceException("Cannot remove user", cause);
}
}
- private List loadUsers() {
+ private List loadUsers() throws UserManagementServiceException {
try {
List users = usersOutputPort.load();
return users == null ? List.of() : users;
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeException("Cannot load users", cause);
+ throw new UserManagementServiceException("Cannot load users", cause);
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
index e1cd695..d9e2b33 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
@@ -2,6 +2,7 @@
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.AdminPasswordServiceException;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.port.InputPort;
@@ -11,7 +12,7 @@
class AdminPasswordServiceImplTest {
@Test
- void resetPassword_validData_sendsRequest() {
+ void resetPassword_validData_sendsRequest() throws Exception {
AtomicReference sent = new AtomicReference<>();
InputPort inputPort = sent::set;
AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(inputPort);
@@ -24,10 +25,10 @@ void resetPassword_validData_sendsRequest() {
}
@Test
- void resetPassword_blankPassword_throwsIllegalArgumentException() {
+ void resetPassword_blankPassword_throwsAdminPasswordServiceException() {
AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(_ -> {});
- assertThrows(IllegalArgumentException.class,
+ assertThrows(AdminPasswordServiceException.class,
() -> service.resetPassword("secure-key", new PasswordDto(" ", " ")));
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
index d9dcb7d..156095c 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
@@ -3,6 +3,7 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.DebugServiceException;
import pl.vtt.wpi.core.domain.model.device.CurrentState;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.OutputPort;
@@ -15,7 +16,7 @@
class DebugServiceImplTest {
@Test
- void pollLogs_readsAndClearsLogs() {
+ void pollLogs_readsAndClearsLogs() throws Exception {
OutputPort> logsOutputPort = () -> List.of("a", "b");
AtomicBoolean cleared = new AtomicBoolean(false);
InputPort deleteInputPort = _ -> cleared.set(true);
@@ -37,7 +38,7 @@ void peekLogs_outputFailure_wrapsException() {
() -> new CurrentState.Builder().build()
);
- RuntimeException exception = assertThrows(RuntimeException.class, service::peekLogs);
+ DebugServiceException exception = assertThrows(DebugServiceException.class, service::peekLogs);
assertEquals("Cannot read logs", exception.getMessage());
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
index 156a90d..2667843 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
@@ -2,6 +2,7 @@
import java.util.UUID;
import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.DeviceInfoServiceException;
import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
@@ -11,7 +12,7 @@
class DeviceInfoServiceImplTest {
@Test
- void read_success_returnsDeviceInfo() {
+ void read_success_returnsDeviceInfo() throws Exception {
DeviceInfo expected = new DeviceInfo(UUID.randomUUID(), "m", "p", "a", "1", "auth", "mail");
DeviceInfoServiceImpl service = new DeviceInfoServiceImpl(() -> expected);
@@ -24,7 +25,7 @@ void read_portFailure_wrapsException() {
() -> { throw new OutputPortException("x", new IllegalArgumentException("boom")); }
);
- RuntimeException exception = assertThrows(RuntimeException.class, service::read);
+ DeviceInfoServiceException exception = assertThrows(DeviceInfoServiceException.class, service::read);
assertEquals("Cannot read device info", exception.getMessage());
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
index 1445836..b561738 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
+import pl.vtt.wpi.core.application.exception.LoginServiceException;
import pl.vtt.wpi.core.domain.port.OutputPort;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
import pl.vtt.wpi.core.domain.model.Credentials;
@@ -37,7 +38,7 @@ void casual_login_ok() {
.encodeToString((username + ":" + token).getBytes(StandardCharsets.UTF_8));
assertEquals(expectedType, AuthorizationHolder.get().type());
assertEquals(expectedCredentials, AuthorizationHolder.get().credentials());
- } catch (IncorrectUsernameOrPasswordException e) {
+ } catch (IncorrectUsernameOrPasswordException | LoginServiceException e) {
fail(e);
}
}
@@ -65,7 +66,8 @@ void runtime_exception_clears_authorization() {
};
LoginServiceImpl instance = new LoginServiceImpl(port);
- RuntimeException exception = assertThrows(RuntimeException.class, () -> instance.login(username, password));
+ LoginServiceException exception = assertThrows(LoginServiceException.class,
+ () -> instance.login(username, password));
assertEquals("Login failed", exception.getMessage());
assertNotNull(exception.getCause());
assertEquals("connection lost", exception.getCause().getMessage());
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java
index cf71eae..3b8319f 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImplTest.java
@@ -10,7 +10,7 @@
class NetworkConfigurationServiceImplTest {
@Test
- void config_accessPointConfig_isMappedToWifiConfig() {
+ void config_accessPointConfig_isMappedToWifiConfig() throws Exception {
AtomicReference sent = new AtomicReference<>();
NetworkConfigurationServiceImpl service = new NetworkConfigurationServiceImpl(sent::set);
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
index 8f4a993..a0266c1 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
@@ -23,7 +23,7 @@ void set_nullElement_throwsDataInconsistencyException() {
}
@Test
- void save_addsProgramWithNextIndexAndSendsAll() {
+ void save_addsProgramWithNextIndexAndSendsAll() throws Exception {
AtomicReference> sent = new AtomicReference<>();
PixelProgramServiceImpl service = new PixelProgramServiceImpl(
() -> List.of(new PixelProgram(0, List.of())),
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
index fca9235..845953b 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
@@ -2,6 +2,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
+import pl.vtt.wpi.core.application.exception.RebootServiceException;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -11,7 +12,7 @@
class RebootServiceImplTest {
@Test
- void reboot_success_sendsNullPayload() {
+ void reboot_success_sendsNullPayload() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
RebootServiceImpl service = new RebootServiceImpl(_ -> called.set(true));
@@ -26,7 +27,7 @@ void reboot_portFailure_wrapsException() {
_ -> { throw new InputPortException("x", new IllegalStateException("boom")); }
);
- RuntimeException exception = assertThrows(RuntimeException.class, service::reboot);
+ RebootServiceException exception = assertThrows(RebootServiceException.class, service::reboot);
assertEquals("Cannot reboot device", exception.getMessage());
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
index 8b8f3fe..742eda7 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
@@ -5,6 +5,7 @@
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
+import pl.vtt.wpi.core.application.exception.RuntimeDataServiceException;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
@@ -15,10 +16,11 @@
class RuntimeDataServiceImplTest {
@Test
- void set_nullRuntimeData_throwsNullPointerException() {
+ void set_nullRuntimeData_throwsRuntimeDataServiceException() {
RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(() -> null, () -> List.of(), _ -> {});
- NullPointerException exception = assertThrows(NullPointerException.class, () -> service.set(null));
+ RuntimeDataServiceException exception = assertThrows(RuntimeDataServiceException.class,
+ () -> service.set(null));
assertEquals("Runtime data cannot be null", exception.getMessage());
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
index ecfbc5d..9688cc0 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
@@ -35,7 +35,7 @@ void createUser_success_sendsUserCreateRequest() throws Exception {
}
@Test
- void createUser_existingUser_throwsUserAlreadyExistsException() {
+ void createUser_existingUser_throwsUserAlreadyExistsException() throws Exception {
User user = new User("john", EnumSet.of(UserGroup.ADMIN));
PasswordDto passwordDto = new PasswordDto("secret", "secret");
From 027d2a6e449a278eded4b537c3f5c770ad3d1d35 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Mon, 27 Apr 2026 13:34:07 +0200
Subject: [PATCH 07/10] Rename checked exceptions to reason-oriented names
---
.../AdminPasswordResetException.java | 11 ++++++++
.../AdminPasswordServiceException.java | 11 --------
...enticationServiceUnavailableException.java | 11 ++++++++
.../exception/DebugDataAccessException.java | 11 ++++++++
.../exception/DebugServiceException.java | 11 --------
.../exception/DeviceInfoReadException.java | 11 ++++++++
.../exception/DeviceInfoServiceException.java | 11 --------
.../exception/DeviceRebootException.java | 11 ++++++++
.../exception/LoginServiceException.java | 11 --------
.../NetworkConfigurationException.java | 11 ++++++++
.../NetworkConfigurationServiceException.java | 11 --------
.../PixelProgramOperationException.java | 11 ++++++++
.../PixelProgramServiceException.java | 11 --------
.../exception/RebootServiceException.java | 11 --------
.../RuntimeDataOperationException.java | 11 ++++++++
.../RuntimeDataServiceException.java | 11 --------
.../UserManagementOperationException.java | 11 ++++++++
.../UserManagementServiceException.java | 11 --------
.../service/AdminPasswordService.java | 4 +--
.../application/service/DebugService.java | 8 +++---
.../service/DeviceInfoService.java | 4 +--
.../application/service/LoginService.java | 4 +--
.../service/NetworkConfigurationService.java | 6 ++---
.../service/PixelProgramService.java | 14 +++++-----
.../application/service/RebootService.java | 4 +--
.../service/RuntimeDataService.java | 6 ++---
.../service/UserManagementService.java | 8 +++---
.../impl/AdminPasswordServiceImpl.java | 14 +++++-----
.../service/impl/DebugServiceImpl.java | 14 +++++-----
.../service/impl/DeviceInfoServiceImpl.java | 6 ++---
.../service/impl/LoginServiceImpl.java | 10 +++----
.../impl/NetworkConfigurationServiceImpl.java | 14 +++++-----
.../service/impl/PixelProgramServiceImpl.java | 26 +++++++++----------
.../service/impl/RebootServiceImpl.java | 6 ++---
.../service/impl/RuntimeDataServiceImpl.java | 14 +++++-----
.../impl/UserManagementServiceImpl.java | 22 ++++++++--------
.../impl/AdminPasswordServiceImplTest.java | 6 ++---
.../service/impl/DebugServiceImplTest.java | 4 +--
.../impl/DeviceInfoServiceImplTest.java | 4 +--
.../service/impl/LoginServiceImplTest.java | 6 ++---
.../service/impl/RebootServiceImplTest.java | 4 +--
.../impl/RuntimeDataServiceImplTest.java | 6 ++---
42 files changed, 206 insertions(+), 206 deletions(-)
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java
delete mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java
new file mode 100644
index 0000000..b34ba50
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class AdminPasswordResetException extends Exception {
+ public AdminPasswordResetException(String message) {
+ super(message);
+ }
+
+ public AdminPasswordResetException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java
deleted file mode 100644
index 87a32ac..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class AdminPasswordServiceException extends Exception {
- public AdminPasswordServiceException(String message) {
- super(message);
- }
-
- public AdminPasswordServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java b/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java
new file mode 100644
index 0000000..85981c0
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class AuthenticationServiceUnavailableException extends Exception {
+ public AuthenticationServiceUnavailableException(String message) {
+ super(message);
+ }
+
+ public AuthenticationServiceUnavailableException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
new file mode 100644
index 0000000..90341af
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class DebugDataAccessException extends Exception {
+ public DebugDataAccessException(String message) {
+ super(message);
+ }
+
+ public DebugDataAccessException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java
deleted file mode 100644
index 72b1b76..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/DebugServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class DebugServiceException extends Exception {
- public DebugServiceException(String message) {
- super(message);
- }
-
- public DebugServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java
new file mode 100644
index 0000000..8f9d4c2
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class DeviceInfoReadException extends Exception {
+ public DeviceInfoReadException(String message) {
+ super(message);
+ }
+
+ public DeviceInfoReadException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java
deleted file mode 100644
index 38e6b09..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class DeviceInfoServiceException extends Exception {
- public DeviceInfoServiceException(String message) {
- super(message);
- }
-
- public DeviceInfoServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java
new file mode 100644
index 0000000..88b65cd
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class DeviceRebootException extends Exception {
+ public DeviceRebootException(String message) {
+ super(message);
+ }
+
+ public DeviceRebootException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java
deleted file mode 100644
index 23ff371..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/LoginServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class LoginServiceException extends Exception {
- public LoginServiceException(String message) {
- super(message);
- }
-
- public LoginServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java
new file mode 100644
index 0000000..ebf014f
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class NetworkConfigurationException extends Exception {
+ public NetworkConfigurationException(String message) {
+ super(message);
+ }
+
+ public NetworkConfigurationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java
deleted file mode 100644
index 0616a38..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class NetworkConfigurationServiceException extends Exception {
- public NetworkConfigurationServiceException(String message) {
- super(message);
- }
-
- public NetworkConfigurationServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java
new file mode 100644
index 0000000..0af923a
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class PixelProgramOperationException extends Exception {
+ public PixelProgramOperationException(String message) {
+ super(message);
+ }
+
+ public PixelProgramOperationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java
deleted file mode 100644
index abb04c7..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class PixelProgramServiceException extends Exception {
- public PixelProgramServiceException(String message) {
- super(message);
- }
-
- public PixelProgramServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java
deleted file mode 100644
index 9d47290..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/RebootServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class RebootServiceException extends Exception {
- public RebootServiceException(String message) {
- super(message);
- }
-
- public RebootServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java
new file mode 100644
index 0000000..739450e
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class RuntimeDataOperationException extends Exception {
+ public RuntimeDataOperationException(String message) {
+ super(message);
+ }
+
+ public RuntimeDataOperationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java
deleted file mode 100644
index 9cfd572..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class RuntimeDataServiceException extends Exception {
- public RuntimeDataServiceException(String message) {
- super(message);
- }
-
- public RuntimeDataServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java
new file mode 100644
index 0000000..f6b042c
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java
@@ -0,0 +1,11 @@
+package pl.vtt.wpi.core.application.exception;
+
+public class UserManagementOperationException extends Exception {
+ public UserManagementOperationException(String message) {
+ super(message);
+ }
+
+ public UserManagementOperationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java
deleted file mode 100644
index 39ef1d1..0000000
--- a/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementServiceException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.vtt.wpi.core.application.exception;
-
-public class UserManagementServiceException extends Exception {
- public UserManagementServiceException(String message) {
- super(message);
- }
-
- public UserManagementServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java b/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java
index 9a3b84b..596e320 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/AdminPasswordService.java
@@ -1,8 +1,8 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
-import pl.vtt.wpi.core.application.exception.AdminPasswordServiceException;
+import pl.vtt.wpi.core.application.exception.AdminPasswordResetException;
public interface AdminPasswordService {
- void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordServiceException;
+ void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordResetException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
index 8872309..123ab6c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
@@ -1,7 +1,7 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.domain.model.device.CurrentState;
-import pl.vtt.wpi.core.application.exception.DebugServiceException;
+import pl.vtt.wpi.core.application.exception.DebugDataAccessException;
import java.util.List;
@@ -10,7 +10,7 @@ public interface DebugService {
* Best-effort non-atomic read-and-clear operation.
* Reading logs and clearing logs happen in separate calls and may race with concurrent log writes.
*/
- List pollLogs() throws DebugServiceException;
- List peekLogs() throws DebugServiceException;
- CurrentState getCurrentState() throws DebugServiceException;
+ List pollLogs() throws DebugDataAccessException;
+ List peekLogs() throws DebugDataAccessException;
+ CurrentState getCurrentState() throws DebugDataAccessException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java b/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java
index 818e852..ee82319 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/DeviceInfoService.java
@@ -1,8 +1,8 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
-import pl.vtt.wpi.core.application.exception.DeviceInfoServiceException;
+import pl.vtt.wpi.core.application.exception.DeviceInfoReadException;
public interface DeviceInfoService {
- DeviceInfo read() throws DeviceInfoServiceException;
+ DeviceInfo read() throws DeviceInfoReadException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java b/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java
index a199d1c..1729e97 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/LoginService.java
@@ -2,12 +2,12 @@
import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
-import pl.vtt.wpi.core.application.exception.LoginServiceException;
+import pl.vtt.wpi.core.application.exception.AuthenticationServiceUnavailableException;
import pl.vtt.wpi.core.domain.model.Credentials;
public interface LoginService {
Credentials login(String username, String password)
- throws IncorrectUsernameOrPasswordException, LoginServiceException;
+ throws IncorrectUsernameOrPasswordException, AuthenticationServiceUnavailableException;
default void logout() {
AuthorizationHolder.clear();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java b/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
index e831c3a..ce7daa3 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
@@ -2,9 +2,9 @@
import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
import pl.vtt.wpi.core.domain.model.device.WifiConfig;
-import pl.vtt.wpi.core.application.exception.NetworkConfigurationServiceException;
+import pl.vtt.wpi.core.application.exception.NetworkConfigurationException;
public interface NetworkConfigurationService {
- void config(WifiConfig config) throws NetworkConfigurationServiceException;
- void config(AccessPointConfig config) throws NetworkConfigurationServiceException;
+ void config(WifiConfig config) throws NetworkConfigurationException;
+ void config(AccessPointConfig config) throws NetworkConfigurationException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java b/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java
index 4343330..6f72b7e 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/PixelProgramService.java
@@ -2,24 +2,24 @@
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
-import pl.vtt.wpi.core.application.exception.PixelProgramServiceException;
+import pl.vtt.wpi.core.application.exception.PixelProgramOperationException;
import pl.vtt.wpi.core.domain.model.color.RgbColor;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import java.util.List;
public interface PixelProgramService {
- List getAll() throws PixelProgramServiceException;
- void insert(List pixelPrograms) throws PixelProgramServiceException;
+ List getAll() throws PixelProgramOperationException;
+ void insert(List pixelPrograms) throws PixelProgramOperationException;
void set(List pixelPrograms)
throws DataInconsistencyException;
PixelProgram get(int index)
- throws PixelProgramNotFoundException, PixelProgramServiceException;
+ throws PixelProgramNotFoundException, PixelProgramOperationException;
PixelProgram singleton(List pixelProgram);
- PixelProgram save(List pixelProgram) throws PixelProgramServiceException;
+ PixelProgram save(List pixelProgram) throws PixelProgramOperationException;
PixelProgram update(int index, List pixelProgram)
- throws PixelProgramNotFoundException, PixelProgramServiceException;
+ throws PixelProgramNotFoundException, PixelProgramOperationException;
PixelProgram remove(int index)
- throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramServiceException;
+ throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramOperationException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java b/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java
index ebf31c5..34449ff 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/RebootService.java
@@ -1,7 +1,7 @@
package pl.vtt.wpi.core.application.service;
-import pl.vtt.wpi.core.application.exception.RebootServiceException;
+import pl.vtt.wpi.core.application.exception.DeviceRebootException;
public interface RebootService {
- void reboot() throws RebootServiceException;
+ void reboot() throws DeviceRebootException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java b/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
index b8d993d..0b1f20b 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/RuntimeDataService.java
@@ -1,10 +1,10 @@
package pl.vtt.wpi.core.application.service;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
-import pl.vtt.wpi.core.application.exception.RuntimeDataServiceException;
+import pl.vtt.wpi.core.application.exception.RuntimeDataOperationException;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
public interface RuntimeDataService {
- RuntimeData read() throws RuntimeDataServiceException;
- void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataServiceException;
+ RuntimeData read() throws RuntimeDataOperationException;
+ void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataOperationException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java b/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java
index 918ad3b..10c3d6e 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/UserManagementService.java
@@ -3,17 +3,17 @@
import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
import pl.vtt.wpi.core.application.exception.UserNotExistsException;
-import pl.vtt.wpi.core.application.exception.UserManagementServiceException;
+import pl.vtt.wpi.core.application.exception.UserManagementOperationException;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.model.User;
public interface UserManagementService {
void createUser(User user, PasswordDto passwordDto)
- throws UserAlreadyExistsException, InvalidPasswordException, UserManagementServiceException;
+ throws UserAlreadyExistsException, InvalidPasswordException, UserManagementOperationException;
void changePassword(PasswordDto passwordDto)
- throws InvalidPasswordException, UserManagementServiceException;
+ throws InvalidPasswordException, UserManagementOperationException;
void removeUser(User user)
- throws UserNotExistsException, UserManagementServiceException;
+ throws UserNotExistsException, UserManagementOperationException;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
index 280c1aa..4844210 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
@@ -1,7 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
-import pl.vtt.wpi.core.application.exception.AdminPasswordServiceException;
+import pl.vtt.wpi.core.application.exception.AdminPasswordResetException;
import pl.vtt.wpi.core.application.service.AdminPasswordService;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.port.InputPort;
@@ -16,24 +16,24 @@ public AdminPasswordServiceImpl(InputPort adminPasswo
}
@Override
- public void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordServiceException {
+ public void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordResetException {
if (secureKey == null || secureKey.isBlank()) {
- throw new AdminPasswordServiceException("Secure key cannot be null or blank");
+ throw new AdminPasswordResetException("Secure key cannot be null or blank");
}
if (passwordDto == null || passwordDto.password() == null || passwordDto.passwordConfirmation() == null) {
- throw new AdminPasswordServiceException("Password data cannot be null");
+ throw new AdminPasswordResetException("Password data cannot be null");
}
if (passwordDto.password().isBlank() || passwordDto.passwordConfirmation().isBlank()) {
- throw new AdminPasswordServiceException("Password and password confirmation cannot be blank");
+ throw new AdminPasswordResetException("Password and password confirmation cannot be blank");
}
if (!passwordDto.password().equals(passwordDto.passwordConfirmation())) {
- throw new AdminPasswordServiceException("Password and password confirmation do not match");
+ throw new AdminPasswordResetException("Password and password confirmation do not match");
}
try {
adminPasswordResetInputPort.send(new AdminPasswordResetRequest(secureKey, passwordDto));
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new AdminPasswordServiceException("Cannot reset admin password", cause);
+ throw new AdminPasswordResetException("Cannot reset admin password", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
index 43138eb..a5b5c9c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
@@ -2,7 +2,7 @@
import java.util.List;
import java.util.Objects;
-import pl.vtt.wpi.core.application.exception.DebugServiceException;
+import pl.vtt.wpi.core.application.exception.DebugDataAccessException;
import pl.vtt.wpi.core.application.service.DebugService;
import pl.vtt.wpi.core.domain.model.device.CurrentState;
import pl.vtt.wpi.core.domain.port.InputPort;
@@ -33,34 +33,34 @@ public DebugServiceImpl(OutputPort> logsOutputPort,
*
*/
@Override
- public List pollLogs() throws DebugServiceException {
+ public List pollLogs() throws DebugDataAccessException {
List logs = peekLogs();
try {
logsDeleteInputPort.send(null);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DebugServiceException("Cannot clear logs", cause);
+ throw new DebugDataAccessException("Cannot clear logs", cause);
}
return logs;
}
@Override
- public List peekLogs() throws DebugServiceException {
+ public List peekLogs() throws DebugDataAccessException {
try {
return logsOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DebugServiceException("Cannot read logs", cause);
+ throw new DebugDataAccessException("Cannot read logs", cause);
}
}
@Override
- public CurrentState getCurrentState() throws DebugServiceException {
+ public CurrentState getCurrentState() throws DebugDataAccessException {
try {
return currentStateOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DebugServiceException("Cannot read current state", cause);
+ throw new DebugDataAccessException("Cannot read current state", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
index b2db8b4..ae1d23b 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImpl.java
@@ -1,7 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
-import pl.vtt.wpi.core.application.exception.DeviceInfoServiceException;
+import pl.vtt.wpi.core.application.exception.DeviceInfoReadException;
import pl.vtt.wpi.core.application.service.DeviceInfoService;
import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
import pl.vtt.wpi.core.domain.port.OutputPort;
@@ -16,12 +16,12 @@ public DeviceInfoServiceImpl(OutputPort deviceInfoOutputPort) {
}
@Override
- public DeviceInfo read() throws DeviceInfoServiceException {
+ public DeviceInfo read() throws DeviceInfoReadException {
try {
return deviceInfoOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DeviceInfoServiceException("Cannot read device info", cause);
+ throw new DeviceInfoReadException("Cannot read device info", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
index ed6b901..9467a29 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
@@ -2,7 +2,7 @@
import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
-import pl.vtt.wpi.core.application.exception.LoginServiceException;
+import pl.vtt.wpi.core.application.exception.AuthenticationServiceUnavailableException;
import pl.vtt.wpi.core.application.service.LoginService;
import pl.vtt.wpi.core.domain.port.OutputPort;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
@@ -17,7 +17,7 @@ public LoginServiceImpl(OutputPort authPort) {
@Override
public Credentials login(String username, String password)
- throws IncorrectUsernameOrPasswordException, LoginServiceException {
+ throws IncorrectUsernameOrPasswordException, AuthenticationServiceUnavailableException {
if (username == null || username.isBlank() || password == null || password.isBlank()) {
throw new IncorrectUsernameOrPasswordException();
}
@@ -26,14 +26,14 @@ public Credentials login(String username, String password)
Credentials credentials = getCredentials();
AuthorizationHolder.authorize(credentials);
return credentials;
- } catch (IncorrectUsernameOrPasswordException | LoginServiceException e) {
+ } catch (IncorrectUsernameOrPasswordException | AuthenticationServiceUnavailableException e) {
AuthorizationHolder.clear();
throw e;
}
}
private Credentials getCredentials()
- throws IncorrectUsernameOrPasswordException, LoginServiceException {
+ throws IncorrectUsernameOrPasswordException, AuthenticationServiceUnavailableException {
final Credentials responseBody;
try {
responseBody = authPort.load();
@@ -42,7 +42,7 @@ private Credentials getCredentials()
throw incorrect;
}
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new LoginServiceException("Login failed", cause);
+ throw new AuthenticationServiceUnavailableException("Login failed", cause);
}
if (responseBody == null) {
throw new IncorrectUsernameOrPasswordException();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
index 8f0b6d4..b8317bb 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/NetworkConfigurationServiceImpl.java
@@ -1,7 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
-import pl.vtt.wpi.core.application.exception.NetworkConfigurationServiceException;
+import pl.vtt.wpi.core.application.exception.NetworkConfigurationException;
import pl.vtt.wpi.core.application.service.NetworkConfigurationService;
import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
import pl.vtt.wpi.core.domain.model.device.WifiConfig;
@@ -17,27 +17,27 @@ public NetworkConfigurationServiceImpl(InputPort wifiConfigInputPort
}
@Override
- public void config(WifiConfig config) throws NetworkConfigurationServiceException {
+ public void config(WifiConfig config) throws NetworkConfigurationException {
send(config);
}
@Override
- public void config(AccessPointConfig config) throws NetworkConfigurationServiceException {
+ public void config(AccessPointConfig config) throws NetworkConfigurationException {
if (config == null) {
- throw new NetworkConfigurationServiceException("Access point config cannot be null");
+ throw new NetworkConfigurationException("Access point config cannot be null");
}
send(new WifiConfig(config.ssid(), config.password()));
}
- private void send(WifiConfig config) throws NetworkConfigurationServiceException {
+ private void send(WifiConfig config) throws NetworkConfigurationException {
if (config == null) {
- throw new NetworkConfigurationServiceException("WiFi config cannot be null");
+ throw new NetworkConfigurationException("WiFi config cannot be null");
}
try {
wifiConfigInputPort.send(config);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new NetworkConfigurationServiceException("Cannot update network configuration", cause);
+ throw new NetworkConfigurationException("Cannot update network configuration", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
index fddd5e2..e0738b8 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -8,7 +8,7 @@
import java.util.concurrent.locks.ReentrantLock;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
-import pl.vtt.wpi.core.application.exception.PixelProgramServiceException;
+import pl.vtt.wpi.core.application.exception.PixelProgramOperationException;
import pl.vtt.wpi.core.application.service.PixelProgramService;
import pl.vtt.wpi.core.domain.model.color.RgbColor;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
@@ -38,12 +38,12 @@ public PixelProgramServiceImpl(OutputPort> pixelProgramsOutpu
}
@Override
- public List getAll() throws PixelProgramServiceException {
+ public List getAll() throws PixelProgramOperationException {
return new ArrayList<>(loadPrograms());
}
@Override
- public void insert(List pixelPrograms) throws PixelProgramServiceException {
+ public void insert(List pixelPrograms) throws PixelProgramOperationException {
if (pixelPrograms == null || pixelPrograms.isEmpty()) {
return;
}
@@ -53,7 +53,7 @@ public void insert(List pixelPrograms) throws PixelProgramServiceE
current.addAll(pixelPrograms);
set(current);
} catch (DataInconsistencyException e) {
- throw new PixelProgramServiceException("Cannot insert pixel programs", e);
+ throw new PixelProgramOperationException("Cannot insert pixel programs", e);
} finally {
mutationLock.unlock();
}
@@ -81,7 +81,7 @@ public void set(List pixelPrograms) throws DataInconsistencyExcept
}
@Override
- public PixelProgram get(int index) throws PixelProgramNotFoundException, PixelProgramServiceException {
+ public PixelProgram get(int index) throws PixelProgramNotFoundException, PixelProgramOperationException {
List programs = loadPrograms();
return programs.get(findListIndex(programs, index));
}
@@ -92,7 +92,7 @@ public PixelProgram singleton(List pixelProgram) {
}
@Override
- public PixelProgram save(List pixelProgram) throws PixelProgramServiceException {
+ public PixelProgram save(List pixelProgram) throws PixelProgramOperationException {
mutationLock.lock();
try {
List programs = new ArrayList<>(getAll());
@@ -107,7 +107,7 @@ public PixelProgram save(List pixelProgram) throws PixelProgramService
set(programs);
return saved;
} catch (DataInconsistencyException e) {
- throw new PixelProgramServiceException("Cannot save pixel program", e);
+ throw new PixelProgramOperationException("Cannot save pixel program", e);
} finally {
mutationLock.unlock();
}
@@ -115,7 +115,7 @@ public PixelProgram save(List pixelProgram) throws PixelProgramService
@Override
public PixelProgram update(int index, List pixelProgram)
- throws PixelProgramNotFoundException, PixelProgramServiceException {
+ throws PixelProgramNotFoundException, PixelProgramOperationException {
mutationLock.lock();
try {
List programs = getAll();
@@ -126,7 +126,7 @@ public PixelProgram update(int index, List pixelProgram)
set(programs);
return updated;
} catch (DataInconsistencyException e) {
- throw new PixelProgramServiceException("Cannot update pixel program", e);
+ throw new PixelProgramOperationException("Cannot update pixel program", e);
} finally {
mutationLock.unlock();
}
@@ -134,7 +134,7 @@ public PixelProgram update(int index, List pixelProgram)
@Override
public PixelProgram remove(int index)
- throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramServiceException {
+ throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramOperationException {
mutationLock.lock();
try {
List programs = getAll();
@@ -142,20 +142,20 @@ public PixelProgram remove(int index)
PixelProgram removed = programs.remove(removeIndex);
set(programs);
return removed;
- } catch (PixelProgramServiceException e) {
+ } catch (PixelProgramOperationException e) {
throw e;
} finally {
mutationLock.unlock();
}
}
- private List loadPrograms() throws PixelProgramServiceException {
+ private List loadPrograms() throws PixelProgramOperationException {
try {
List pixelPrograms = pixelProgramsOutputPort.load();
return pixelPrograms == null ? List.of() : pixelPrograms;
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new PixelProgramServiceException("Cannot read pixel programs", cause);
+ throw new PixelProgramOperationException("Cannot read pixel programs", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
index 9ab534f..55c902d 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
@@ -1,7 +1,7 @@
package pl.vtt.wpi.core.application.service.impl;
import java.util.Objects;
-import pl.vtt.wpi.core.application.exception.RebootServiceException;
+import pl.vtt.wpi.core.application.exception.DeviceRebootException;
import pl.vtt.wpi.core.application.service.RebootService;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
@@ -15,12 +15,12 @@ public RebootServiceImpl(InputPort rebootInputPort) {
}
@Override
- public void reboot() throws RebootServiceException {
+ public void reboot() throws DeviceRebootException {
try {
rebootInputPort.send(null);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RebootServiceException("Cannot reboot device", cause);
+ throw new DeviceRebootException("Cannot reboot device", cause);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
index ef6af6c..fe6fe90 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
@@ -4,7 +4,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
-import pl.vtt.wpi.core.application.exception.RuntimeDataServiceException;
+import pl.vtt.wpi.core.application.exception.RuntimeDataOperationException;
import pl.vtt.wpi.core.application.service.RuntimeDataService;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
@@ -30,30 +30,30 @@ public RuntimeDataServiceImpl(OutputPort runtimeDataOutputPort,
}
@Override
- public RuntimeData read() throws RuntimeDataServiceException {
+ public RuntimeData read() throws RuntimeDataOperationException {
try {
return runtimeDataOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeDataServiceException("Cannot read runtime data", cause);
+ throw new RuntimeDataOperationException("Cannot read runtime data", cause);
}
}
@Override
- public void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataServiceException {
+ public void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataOperationException {
if (runtimeData == null) {
- throw new RuntimeDataServiceException("Runtime data cannot be null");
+ throw new RuntimeDataOperationException("Runtime data cannot be null");
}
validatePixelProgramExists(runtimeData);
send(runtimeData);
}
- private void send(RuntimeData runtimeData) throws RuntimeDataServiceException {
+ private void send(RuntimeData runtimeData) throws RuntimeDataOperationException {
try {
runtimeDataInputPort.send(runtimeData);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new RuntimeDataServiceException("Cannot set runtime data", cause);
+ throw new RuntimeDataOperationException("Cannot set runtime data", cause);
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
index de4a0ac..c2c5f39 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
@@ -4,7 +4,7 @@
import java.util.Objects;
import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
-import pl.vtt.wpi.core.application.exception.UserManagementServiceException;
+import pl.vtt.wpi.core.application.exception.UserManagementOperationException;
import pl.vtt.wpi.core.application.exception.UserNotExistsException;
import pl.vtt.wpi.core.application.service.UserManagementService;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
@@ -36,10 +36,10 @@ public UserManagementServiceImpl(OutputPort> usersOutputPort,
@Override
public void createUser(User user, PasswordDto passwordDto)
- throws UserAlreadyExistsException, InvalidPasswordException, UserManagementServiceException {
+ throws UserAlreadyExistsException, InvalidPasswordException, UserManagementOperationException {
validatePassword(passwordDto);
if (user == null) {
- throw new UserManagementServiceException("User cannot be null");
+ throw new UserManagementOperationException("User cannot be null");
}
boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
if (exists) {
@@ -49,27 +49,27 @@ public void createUser(User user, PasswordDto passwordDto)
userCreateRequestInputPort.send(new UserCreateRequest(user, passwordDto));
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new UserManagementServiceException("Cannot create user", cause);
+ throw new UserManagementOperationException("Cannot create user", cause);
}
}
@Override
public void changePassword(PasswordDto passwordDto)
- throws InvalidPasswordException, UserManagementServiceException {
+ throws InvalidPasswordException, UserManagementOperationException {
validatePassword(passwordDto);
try {
changePasswordInputPort.send(passwordDto);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new UserManagementServiceException("Cannot change password", cause);
+ throw new UserManagementOperationException("Cannot change password", cause);
}
}
@Override
public void removeUser(User user)
- throws UserNotExistsException, UserManagementServiceException {
+ throws UserNotExistsException, UserManagementOperationException {
if (user == null) {
- throw new UserManagementServiceException("User cannot be null");
+ throw new UserManagementOperationException("User cannot be null");
}
boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
if (!exists) {
@@ -79,17 +79,17 @@ public void removeUser(User user)
removeUserInputPort.send(user);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new UserManagementServiceException("Cannot remove user", cause);
+ throw new UserManagementOperationException("Cannot remove user", cause);
}
}
- private List loadUsers() throws UserManagementServiceException {
+ private List loadUsers() throws UserManagementOperationException {
try {
List users = usersOutputPort.load();
return users == null ? List.of() : users;
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new UserManagementServiceException("Cannot load users", cause);
+ throw new UserManagementOperationException("Cannot load users", cause);
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
index d9e2b33..0f67ab4 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
@@ -2,7 +2,7 @@
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
-import pl.vtt.wpi.core.application.exception.AdminPasswordServiceException;
+import pl.vtt.wpi.core.application.exception.AdminPasswordResetException;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.port.InputPort;
@@ -25,10 +25,10 @@ void resetPassword_validData_sendsRequest() throws Exception {
}
@Test
- void resetPassword_blankPassword_throwsAdminPasswordServiceException() {
+ void resetPassword_blankPassword_throwsAdminPasswordResetException() {
AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(_ -> {});
- assertThrows(AdminPasswordServiceException.class,
+ assertThrows(AdminPasswordResetException.class,
() -> service.resetPassword("secure-key", new PasswordDto(" ", " ")));
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
index 156095c..ee57478 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImplTest.java
@@ -3,7 +3,7 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
-import pl.vtt.wpi.core.application.exception.DebugServiceException;
+import pl.vtt.wpi.core.application.exception.DebugDataAccessException;
import pl.vtt.wpi.core.domain.model.device.CurrentState;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.OutputPort;
@@ -38,7 +38,7 @@ void peekLogs_outputFailure_wrapsException() {
() -> new CurrentState.Builder().build()
);
- DebugServiceException exception = assertThrows(DebugServiceException.class, service::peekLogs);
+ DebugDataAccessException exception = assertThrows(DebugDataAccessException.class, service::peekLogs);
assertEquals("Cannot read logs", exception.getMessage());
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
index 2667843..bff7581 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/DeviceInfoServiceImplTest.java
@@ -2,7 +2,7 @@
import java.util.UUID;
import org.junit.jupiter.api.Test;
-import pl.vtt.wpi.core.application.exception.DeviceInfoServiceException;
+import pl.vtt.wpi.core.application.exception.DeviceInfoReadException;
import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
@@ -25,7 +25,7 @@ void read_portFailure_wrapsException() {
() -> { throw new OutputPortException("x", new IllegalArgumentException("boom")); }
);
- DeviceInfoServiceException exception = assertThrows(DeviceInfoServiceException.class, service::read);
+ DeviceInfoReadException exception = assertThrows(DeviceInfoReadException.class, service::read);
assertEquals("Cannot read device info", exception.getMessage());
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
index b561738..907e9ef 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
@@ -7,7 +7,7 @@
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
-import pl.vtt.wpi.core.application.exception.LoginServiceException;
+import pl.vtt.wpi.core.application.exception.AuthenticationServiceUnavailableException;
import pl.vtt.wpi.core.domain.port.OutputPort;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
import pl.vtt.wpi.core.domain.model.Credentials;
@@ -38,7 +38,7 @@ void casual_login_ok() {
.encodeToString((username + ":" + token).getBytes(StandardCharsets.UTF_8));
assertEquals(expectedType, AuthorizationHolder.get().type());
assertEquals(expectedCredentials, AuthorizationHolder.get().credentials());
- } catch (IncorrectUsernameOrPasswordException | LoginServiceException e) {
+ } catch (IncorrectUsernameOrPasswordException | AuthenticationServiceUnavailableException e) {
fail(e);
}
}
@@ -66,7 +66,7 @@ void runtime_exception_clears_authorization() {
};
LoginServiceImpl instance = new LoginServiceImpl(port);
- LoginServiceException exception = assertThrows(LoginServiceException.class,
+ AuthenticationServiceUnavailableException exception = assertThrows(AuthenticationServiceUnavailableException.class,
() -> instance.login(username, password));
assertEquals("Login failed", exception.getMessage());
assertNotNull(exception.getCause());
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
index 845953b..8344e3f 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImplTest.java
@@ -2,7 +2,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
-import pl.vtt.wpi.core.application.exception.RebootServiceException;
+import pl.vtt.wpi.core.application.exception.DeviceRebootException;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -27,7 +27,7 @@ void reboot_portFailure_wrapsException() {
_ -> { throw new InputPortException("x", new IllegalStateException("boom")); }
);
- RebootServiceException exception = assertThrows(RebootServiceException.class, service::reboot);
+ DeviceRebootException exception = assertThrows(DeviceRebootException.class, service::reboot);
assertEquals("Cannot reboot device", exception.getMessage());
}
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
index 742eda7..2a5beae 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
@@ -5,7 +5,7 @@
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
-import pl.vtt.wpi.core.application.exception.RuntimeDataServiceException;
+import pl.vtt.wpi.core.application.exception.RuntimeDataOperationException;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
@@ -16,10 +16,10 @@
class RuntimeDataServiceImplTest {
@Test
- void set_nullRuntimeData_throwsRuntimeDataServiceException() {
+ void set_nullRuntimeData_throwsRuntimeDataOperationException() {
RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(() -> null, () -> List.of(), _ -> {});
- RuntimeDataServiceException exception = assertThrows(RuntimeDataServiceException.class,
+ RuntimeDataOperationException exception = assertThrows(RuntimeDataOperationException.class,
() -> service.set(null));
assertEquals("Runtime data cannot be null", exception.getMessage());
From 2a5adcd64ffeaab946f80a3dfec6c3209a85d24b Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Mon, 27 Apr 2026 13:37:14 +0200
Subject: [PATCH 08/10] Apply review fixes for exception hierarchy and service
robustness
---
.../AdminPasswordResetException.java | 4 ++-
.../ApplicationServiceException.java | 13 ++++++++
...enticationServiceUnavailableException.java | 4 ++-
.../exception/DebugDataAccessException.java | 7 +++-
.../exception/DeviceInfoReadException.java | 4 ++-
.../exception/DeviceRebootException.java | 4 ++-
.../NetworkConfigurationException.java | 4 ++-
.../PixelProgramOperationException.java | 4 ++-
.../RuntimeDataOperationException.java | 4 ++-
.../UserManagementOperationException.java | 4 ++-
.../service/NetworkConfigurationService.java | 2 +-
.../impl/AdminPasswordServiceImpl.java | 4 +--
.../service/impl/LoginServiceImpl.java | 4 ++-
.../service/impl/RebootServiceImpl.java | 3 +-
.../service/impl/RuntimeDataServiceImpl.java | 7 ++--
.../impl/UserManagementServiceImpl.java | 27 ++++++++++-----
.../domain/dto/AdminPasswordResetRequest.java | 4 +++
.../impl/AdminPasswordServiceImplTest.java | 33 +++++++++++++++++--
.../service/impl/LoginServiceImplTest.java | 4 +--
.../impl/RuntimeDataServiceImplTest.java | 24 ++++++++++++++
20 files changed, 132 insertions(+), 32 deletions(-)
create mode 100644 src/main/java/pl/vtt/wpi/core/application/exception/ApplicationServiceException.java
create mode 100644 src/main/java/pl/vtt/wpi/core/domain/dto/AdminPasswordResetRequest.java
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java
index b34ba50..953b990 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/AdminPasswordResetException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class AdminPasswordResetException extends Exception {
+public class AdminPasswordResetException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public AdminPasswordResetException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/ApplicationServiceException.java b/src/main/java/pl/vtt/wpi/core/application/exception/ApplicationServiceException.java
new file mode 100644
index 0000000..c113a74
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/ApplicationServiceException.java
@@ -0,0 +1,13 @@
+package pl.vtt.wpi.core.application.exception;
+
+public abstract class ApplicationServiceException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ protected ApplicationServiceException(String message) {
+ super(message);
+ }
+
+ protected ApplicationServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java b/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java
index 85981c0..e5a2f4c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/AuthenticationServiceUnavailableException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class AuthenticationServiceUnavailableException extends Exception {
+public class AuthenticationServiceUnavailableException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public AuthenticationServiceUnavailableException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
index 90341af..a081d96 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
@@ -1,6 +1,11 @@
package pl.vtt.wpi.core.application.exception;
-public class DebugDataAccessException extends Exception {
+/**
+ * Thrown when debug operations cannot read or clear data due to I/O/port failures.
+ */
+public class DebugDataAccessException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public DebugDataAccessException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java
index 8f9d4c2..8ba7378 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceInfoReadException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class DeviceInfoReadException extends Exception {
+public class DeviceInfoReadException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public DeviceInfoReadException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java
index 88b65cd..e7b0462 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DeviceRebootException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class DeviceRebootException extends Exception {
+public class DeviceRebootException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public DeviceRebootException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java
index ebf014f..8c43598 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/NetworkConfigurationException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class NetworkConfigurationException extends Exception {
+public class NetworkConfigurationException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public NetworkConfigurationException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java
index 0af923a..7085a3f 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/PixelProgramOperationException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class PixelProgramOperationException extends Exception {
+public class PixelProgramOperationException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public PixelProgramOperationException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java
index 739450e..129750c 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/RuntimeDataOperationException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class RuntimeDataOperationException extends Exception {
+public class RuntimeDataOperationException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public RuntimeDataOperationException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java
index f6b042c..d600467 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/UserManagementOperationException.java
@@ -1,6 +1,8 @@
package pl.vtt.wpi.core.application.exception;
-public class UserManagementOperationException extends Exception {
+public class UserManagementOperationException extends ApplicationServiceException {
+ private static final long serialVersionUID = 1L;
+
public UserManagementOperationException(String message) {
super(message);
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java b/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
index ce7daa3..e2a71d3 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/NetworkConfigurationService.java
@@ -1,8 +1,8 @@
package pl.vtt.wpi.core.application.service;
+import pl.vtt.wpi.core.application.exception.NetworkConfigurationException;
import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
import pl.vtt.wpi.core.domain.model.device.WifiConfig;
-import pl.vtt.wpi.core.application.exception.NetworkConfigurationException;
public interface NetworkConfigurationService {
void config(WifiConfig config) throws NetworkConfigurationException;
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
index 4844210..2c742c3 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImpl.java
@@ -3,6 +3,7 @@
import java.util.Objects;
import pl.vtt.wpi.core.application.exception.AdminPasswordResetException;
import pl.vtt.wpi.core.application.service.AdminPasswordService;
+import pl.vtt.wpi.core.domain.dto.AdminPasswordResetRequest;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.port.InputPort;
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
@@ -36,7 +37,4 @@ public void resetPassword(String secureKey, PasswordDto passwordDto) throws Admi
throw new AdminPasswordResetException("Cannot reset admin password", cause);
}
}
-
- public record AdminPasswordResetRequest(String secureKey, PasswordDto passwordDto) {
- }
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
index 9467a29..240ec12 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImpl.java
@@ -26,7 +26,9 @@ public Credentials login(String username, String password)
Credentials credentials = getCredentials();
AuthorizationHolder.authorize(credentials);
return credentials;
- } catch (IncorrectUsernameOrPasswordException | AuthenticationServiceUnavailableException e) {
+ } catch (IncorrectUsernameOrPasswordException
+ | AuthenticationServiceUnavailableException
+ | RuntimeException e) {
AuthorizationHolder.clear();
throw e;
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
index 55c902d..f2ded5e 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RebootServiceImpl.java
@@ -19,8 +19,7 @@ public void reboot() throws DeviceRebootException {
try {
rebootInputPort.send(null);
} catch (InputPortException e) {
- Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DeviceRebootException("Cannot reboot device", cause);
+ throw new DeviceRebootException("Cannot reboot device", e);
}
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
index fe6fe90..b969aba 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImpl.java
@@ -57,7 +57,8 @@ private void send(RuntimeData runtimeData) throws RuntimeDataOperationException
}
}
- private void validatePixelProgramExists(RuntimeData runtimeData) throws DataInconsistencyException {
+ private void validatePixelProgramExists(RuntimeData runtimeData)
+ throws DataInconsistencyException, RuntimeDataOperationException {
if (runtimeData.pixelProgram() == null) {
return;
}
@@ -66,12 +67,12 @@ private void validatePixelProgramExists(RuntimeData runtimeData) throws DataInco
pixelPrograms = pixelProgramsOutputPort.load();
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
- throw new DataInconsistencyException("Cannot validate runtime data", cause);
+ throw new RuntimeDataOperationException("Cannot validate runtime data", cause);
}
boolean exists = pixelPrograms != null
&& pixelPrograms.stream().anyMatch(program ->
- program != null && program.index() == runtimeData.pixelProgram());
+ program != null && Objects.equals(program.index(), runtimeData.pixelProgram()));
if (!exists) {
String message = "Pixel program %d does not exist".formatted(runtimeData.pixelProgram());
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
index c2c5f39..a60e5a9 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
@@ -2,6 +2,8 @@
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
import pl.vtt.wpi.core.application.exception.UserManagementOperationException;
@@ -20,6 +22,7 @@ public class UserManagementServiceImpl implements UserManagementService {
private final InputPort userCreateRequestInputPort;
private final InputPort changePasswordInputPort;
private final InputPort removeUserInputPort;
+ private final Lock userLock = new ReentrantLock();
public UserManagementServiceImpl(OutputPort> usersOutputPort,
InputPort userCreateRequestInputPort,
@@ -37,19 +40,22 @@ public UserManagementServiceImpl(OutputPort> usersOutputPort,
@Override
public void createUser(User user, PasswordDto passwordDto)
throws UserAlreadyExistsException, InvalidPasswordException, UserManagementOperationException {
- validatePassword(passwordDto);
if (user == null) {
throw new UserManagementOperationException("User cannot be null");
}
- boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
- if (exists) {
- throw new UserAlreadyExistsException();
- }
+ validatePassword(passwordDto);
+ userLock.lock();
try {
+ boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
+ if (exists) {
+ throw new UserAlreadyExistsException();
+ }
userCreateRequestInputPort.send(new UserCreateRequest(user, passwordDto));
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
throw new UserManagementOperationException("Cannot create user", cause);
+ } finally {
+ userLock.unlock();
}
}
@@ -71,15 +77,18 @@ public void removeUser(User user)
if (user == null) {
throw new UserManagementOperationException("User cannot be null");
}
- boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
- if (!exists) {
- throw new UserNotExistsException();
- }
+ userLock.lock();
try {
+ boolean exists = loadUsers().stream().anyMatch(existingUser -> existingUser.username().equals(user.username()));
+ if (!exists) {
+ throw new UserNotExistsException();
+ }
removeUserInputPort.send(user);
} catch (InputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
throw new UserManagementOperationException("Cannot remove user", cause);
+ } finally {
+ userLock.unlock();
}
}
diff --git a/src/main/java/pl/vtt/wpi/core/domain/dto/AdminPasswordResetRequest.java b/src/main/java/pl/vtt/wpi/core/domain/dto/AdminPasswordResetRequest.java
new file mode 100644
index 0000000..a4aef71
--- /dev/null
+++ b/src/main/java/pl/vtt/wpi/core/domain/dto/AdminPasswordResetRequest.java
@@ -0,0 +1,4 @@
+package pl.vtt.wpi.core.domain.dto;
+
+public record AdminPasswordResetRequest(String secureKey, PasswordDto passwordDto) {
+}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
index 0f67ab4..d35dd8e 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/AdminPasswordServiceImplTest.java
@@ -3,8 +3,10 @@
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.exception.AdminPasswordResetException;
+import pl.vtt.wpi.core.domain.dto.AdminPasswordResetRequest;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.port.InputPort;
+import pl.vtt.wpi.core.domain.port.exception.InputPortException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -13,8 +15,8 @@ class AdminPasswordServiceImplTest {
@Test
void resetPassword_validData_sendsRequest() throws Exception {
- AtomicReference sent = new AtomicReference<>();
- InputPort inputPort = sent::set;
+ AtomicReference sent = new AtomicReference<>();
+ InputPort inputPort = sent::set;
AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(inputPort);
PasswordDto passwordDto = new PasswordDto("secret", "secret");
@@ -31,4 +33,31 @@ void resetPassword_blankPassword_throwsAdminPasswordResetException() {
assertThrows(AdminPasswordResetException.class,
() -> service.resetPassword("secure-key", new PasswordDto(" ", " ")));
}
+
+ @Test
+ void resetPassword_inputPortExceptionWithoutCause_isTranslated() {
+ AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(
+ _ -> { throw new InputPortException("port failure"); }
+ );
+
+ AdminPasswordResetException exception = assertThrows(AdminPasswordResetException.class,
+ () -> service.resetPassword("secure-key", new PasswordDto("secret", "secret")));
+
+ assertEquals("Cannot reset admin password", exception.getMessage());
+ assertEquals("port failure", exception.getCause().getMessage());
+ }
+
+ @Test
+ void resetPassword_inputPortExceptionWithCause_isTranslated() {
+ RuntimeException cause = new RuntimeException("cause");
+ AdminPasswordServiceImpl service = new AdminPasswordServiceImpl(
+ _ -> { throw new InputPortException("port failure", cause); }
+ );
+
+ AdminPasswordResetException exception = assertThrows(AdminPasswordResetException.class,
+ () -> service.resetPassword("secure-key", new PasswordDto("secret", "secret")));
+
+ assertEquals("Cannot reset admin password", exception.getMessage());
+ assertEquals("cause", exception.getCause().getMessage());
+ }
}
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
index 907e9ef..3664031 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/LoginServiceImplTest.java
@@ -57,8 +57,8 @@ void wrong_username_or_password_login_exception() {
}
@Test
- @DisplayName("Tests cleanup when auth port throws runtime exception")
- void runtime_exception_clears_authorization() {
+ @DisplayName("Tests cleanup when OutputPortException causes AuthenticationServiceUnavailableException")
+ void output_port_exception_clears_authorization() {
String username = "test";
String password = "test123";
OutputPort port = () -> {
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
index 2a5beae..1c4c417 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/RuntimeDataServiceImplTest.java
@@ -8,6 +8,7 @@
import pl.vtt.wpi.core.application.exception.RuntimeDataOperationException;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;
+import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -15,6 +16,29 @@
class RuntimeDataServiceImplTest {
+ @Test
+ void read_success_returnsRuntimeData() throws Exception {
+ RuntimeData runtimeData = new RuntimeData.Builder().build();
+ RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(() -> runtimeData, () -> List.of(), _ -> {});
+
+ RuntimeData result = service.read();
+
+ assertEquals(runtimeData, result);
+ }
+
+ @Test
+ void read_outputPortFailure_throwsRuntimeDataOperationException() {
+ RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(
+ () -> { throw new OutputPortException("read failed"); },
+ () -> List.of(),
+ _ -> {}
+ );
+
+ RuntimeDataOperationException exception = assertThrows(RuntimeDataOperationException.class, service::read);
+
+ assertInstanceOf(OutputPortException.class, exception.getCause());
+ }
+
@Test
void set_nullRuntimeData_throwsRuntimeDataOperationException() {
RuntimeDataServiceImpl service = new RuntimeDataServiceImpl(() -> null, () -> List.of(), _ -> {});
From ad17cba7c275fcfa7d826b549b44c395bf74fbc0 Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Mon, 27 Apr 2026 13:54:19 +0200
Subject: [PATCH 09/10] Remove Javadocs from evolving service code
---
.../application/exception/DebugDataAccessException.java | 3 ---
.../pl/vtt/wpi/core/application/service/DebugService.java | 4 ----
.../core/application/service/impl/DebugServiceImpl.java | 7 -------
.../application/service/impl/PixelProgramServiceImpl.java | 7 -------
4 files changed, 21 deletions(-)
diff --git a/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java b/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
index a081d96..8dde42d 100644
--- a/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
+++ b/src/main/java/pl/vtt/wpi/core/application/exception/DebugDataAccessException.java
@@ -1,8 +1,5 @@
package pl.vtt.wpi.core.application.exception;
-/**
- * Thrown when debug operations cannot read or clear data due to I/O/port failures.
- */
public class DebugDataAccessException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
index 123ab6c..9081857 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/DebugService.java
@@ -6,10 +6,6 @@
import java.util.List;
public interface DebugService {
- /**
- * Best-effort non-atomic read-and-clear operation.
- * Reading logs and clearing logs happen in separate calls and may race with concurrent log writes.
- */
List pollLogs() throws DebugDataAccessException;
List peekLogs() throws DebugDataAccessException;
CurrentState getCurrentState() throws DebugDataAccessException;
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
index a5b5c9c..4b1aed1 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/DebugServiceImpl.java
@@ -25,13 +25,6 @@ public DebugServiceImpl(OutputPort> logsOutputPort,
"currentStateOutputPort cannot be null");
}
- /**
- * Best-effort non-atomic read-and-clear operation.
- *
- * This method performs {@link #peekLogs()} and then issues a separate delete call.
- * Log entries appended between these calls may be deleted without appearing in the returned list.
- *
- */
@Override
public List pollLogs() throws DebugDataAccessException {
List logs = peekLogs();
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
index e0738b8..2d150d3 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -17,13 +17,6 @@
import pl.vtt.wpi.core.domain.port.exception.InputPortException;
import pl.vtt.wpi.core.domain.port.exception.OutputPortException;
-/**
- * Default {@link PixelProgramService} implementation.
- *
- * Mutating operations are synchronized per service instance with a lock to avoid
- * read-modify-write races between {@code load()} and {@code send()} calls.
- *
- */
public class PixelProgramServiceImpl implements PixelProgramService {
private final OutputPort> pixelProgramsOutputPort;
private final InputPort> pixelProgramsInputPort;
From 7196ee72729975595e725d5d8c0541a45ba9940b Mon Sep 17 00:00:00 2001
From: Kamil Jan Mularski
Date: Thu, 7 May 2026 19:50:51 +0200
Subject: [PATCH 10/10] Handle corrupted null data in service implementations
---
.../service/impl/PixelProgramServiceImpl.java | 1 +
.../impl/UserManagementServiceImpl.java | 8 +++-
.../impl/PixelProgramServiceImplTest.java | 18 ++++++++-
.../impl/UserManagementServiceImplTest.java | 38 +++++++++++++++++++
4 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
index 2d150d3..1558dba 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImpl.java
@@ -90,6 +90,7 @@ public PixelProgram save(List pixelProgram) throws PixelProgramOperati
try {
List programs = new ArrayList<>(getAll());
int nextIndex = programs.stream()
+ .filter(Objects::nonNull)
.map(PixelProgram::index)
.max(Comparator.naturalOrder())
.map(i -> i + 1)
diff --git a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
index a60e5a9..5ffc1de 100644
--- a/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
+++ b/src/main/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImpl.java
@@ -95,7 +95,13 @@ public void removeUser(User user)
private List loadUsers() throws UserManagementOperationException {
try {
List users = usersOutputPort.load();
- return users == null ? List.of() : users;
+ if (users == null) {
+ return List.of();
+ }
+ if (users.stream().anyMatch(Objects::isNull)) {
+ throw new UserManagementOperationException("Users cannot contain null elements");
+ }
+ return users;
} catch (OutputPortException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
throw new UserManagementOperationException("Cannot load users", cause);
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
index a0266c1..e09f8fb 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/PixelProgramServiceImplTest.java
@@ -1,10 +1,13 @@
package pl.vtt.wpi.core.application.service.impl;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
+import pl.vtt.wpi.core.application.exception.PixelProgramOperationException;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -17,7 +20,7 @@ void set_nullElement_throwsDataInconsistencyException() {
PixelProgramServiceImpl service = new PixelProgramServiceImpl(() -> List.of(), _ -> {});
DataInconsistencyException exception = assertThrows(DataInconsistencyException.class,
- () -> service.set(new java.util.ArrayList<>(java.util.Collections.singletonList(null))));
+ () -> service.set(new ArrayList<>(Collections.singletonList(null))));
assertEquals("Pixel programs cannot contain null elements", exception.getMessage());
}
@@ -36,6 +39,19 @@ void save_addsProgramWithNextIndexAndSendsAll() throws Exception {
assertEquals(2, sent.get().size());
}
+ @Test
+ void save_loadedNullProgram_throwsPixelProgramOperationException() {
+ PixelProgramServiceImpl service = new PixelProgramServiceImpl(
+ () -> new ArrayList<>(Collections.singletonList(null)),
+ _ -> {}
+ );
+
+ PixelProgramOperationException exception = assertThrows(PixelProgramOperationException.class,
+ () -> service.save(List.of()));
+
+ assertEquals("Cannot save pixel program", exception.getMessage());
+ }
+
@Test
void get_missingProgram_throwsPixelProgramNotFoundException() {
PixelProgramServiceImpl service = new PixelProgramServiceImpl(() -> List.of(), _ -> {});
diff --git a/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java b/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
index 9688cc0..f7cd50e 100644
--- a/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
+++ b/src/test/java/pl/vtt/wpi/core/application/service/impl/UserManagementServiceImplTest.java
@@ -1,11 +1,14 @@
package pl.vtt.wpi.core.application.service.impl;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import pl.vtt.wpi.core.application.exception.InvalidPasswordException;
import pl.vtt.wpi.core.application.exception.UserAlreadyExistsException;
+import pl.vtt.wpi.core.application.exception.UserManagementOperationException;
import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.domain.dto.UserCreateRequest;
import pl.vtt.wpi.core.domain.model.User;
@@ -49,6 +52,41 @@ void createUser_existingUser_throwsUserAlreadyExistsException() throws Exception
assertThrows(UserAlreadyExistsException.class, () -> service.createUser(user, passwordDto));
}
+ @Test
+ void createUser_loadedNullUser_throwsUserManagementOperationException() {
+ User user = new User("john", EnumSet.of(UserGroup.ADMIN));
+ PasswordDto passwordDto = new PasswordDto("secret", "secret");
+
+ UserManagementServiceImpl service = new UserManagementServiceImpl(
+ () -> new ArrayList<>(Collections.singletonList(null)),
+ _ -> {},
+ _ -> {},
+ _ -> {}
+ );
+
+ UserManagementOperationException exception = assertThrows(UserManagementOperationException.class,
+ () -> service.createUser(user, passwordDto));
+
+ assertEquals("Users cannot contain null elements", exception.getMessage());
+ }
+
+ @Test
+ void removeUser_loadedNullUser_throwsUserManagementOperationException() {
+ User user = new User("john", EnumSet.of(UserGroup.ADMIN));
+
+ UserManagementServiceImpl service = new UserManagementServiceImpl(
+ () -> new ArrayList<>(Collections.singletonList(null)),
+ _ -> {},
+ _ -> {},
+ _ -> {}
+ );
+
+ UserManagementOperationException exception = assertThrows(UserManagementOperationException.class,
+ () -> service.removeUser(user));
+
+ assertEquals("Users cannot contain null elements", exception.getMessage());
+ }
+
@Test
void changePassword_blankPassword_throwsInvalidPasswordException() {
UserManagementServiceImpl service = new UserManagementServiceImpl(