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(