diff --git a/notification/README.md b/notification/README.md new file mode 100644 index 000000000..619a4ad7a --- /dev/null +++ b/notification/README.md @@ -0,0 +1,251 @@ +title: Notification +category: Behavioural +language: en +tags: +- Decoupling +- Presentation +- Domain +--- + +## Intent + +To capture information about errors and other information that occurs in the domain layer +which acts as the internal logic and validation tool. The notification then communicates this information +back to the presentation for handling and displaying to the user what errors have occurred and why. + +## Explanation + +Real world example + +> You need to register a worker for your company. The information submitted needs to be valid +> before the worker can be added to the database, and if there are any errors you need to know about them. + +In plain words + +> A notification is simply a way of collecting information about errors and communicating it to the user +> so that they are aware of them + +**Programatic example** +Building off the example of registering a worker for a company, we can now explore a coded example for a full picture +of how this pattern looks when coded up. For full code please visit the github repository. + +To begin with, the user submits information to a form through the presentation layer of our software. The information +given to register a worker is the worker's name, occupation, and date of birth. The program will then make sure none of +these fields are blank (validation) and that the worker is over 18 years old. If there are any errors, +the program will inform the user. + +The code for the form is given below. This form acts as our presentation layer, taking input from the user and printing +output using a LOGGER (in this case). The form then gives this information to the service layer RegisterWorkerService +through a data transfer object (DTO). + +The service handles information validation and the presentation can then check the notification stored within the DTO for +any errors and display them to the user if necessary. Otherwise, it will inform the user the submission was processed +successfully. + +form: +```java +/** + * The form submitted by the user, part of the presentation layer, + * linked to the domain layer through a data transfer object and + * linked to the service layer directly. + */ +@Slf4j +public class RegisterWorkerForm { + String name; + String occupation; + LocalDate dateOfBirth; + RegisterWorkerDto worker; + /** + * Service super type which the form uses as part of its service layer. + */ + RegisterWorkerService service = new RegisterWorkerService(); + + /** + * Creates the form. + * + * @param name name of worker + * @param occupation occupation of the worker + * @param dateOfBirth date of birth of the worker + */ + public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { + this.name = name; + this.occupation = occupation; + this.dateOfBirth = dateOfBirth; + } + + /** + * Attempts to submit the form for registering a worker. + */ + public void submit() { + //Save worker information (like name, occupation, dob) to our transfer object to be communicated between layers + saveToWorker(); + //call the service layer to register our worker + service.registerWorker(worker); + + //check for any errors + if (worker.getNotification().hasErrors()) { + indicateErrors(); //displays errors to users + LOGGER.info("Not registered, see errors"); + } else { + LOGGER.info("Registration Succeeded"); + } + } + + ... +} +``` + +The data transfer object (DTO) created stores the information submitted (name, occupation, date of birth), as well as +information on the notification after this data has been validated (stored in the DTO class it extends). +This acts as the link between the service layer and our domain layer which runs the internal logic. +It also holds information on the error types created. + +DTO: +```java +/** + * Data transfer object which stores information about the worker. This is carried between + * objects and layers to reduce the number of method calls made. + */ +@Getter +@Setter +public class RegisterWorkerDto extends DataTransferObject { + private String name; + private String occupation; + private LocalDate dateOfBirth; + + /** + * Error for when name field is blank or missing. + */ + public static final NotificationError MISSING_NAME = + new NotificationError(1, "Name is missing"); + + /** + * Error for when occupation field is blank or missing. + */ + public static final NotificationError MISSING_OCCUPATION = + new NotificationError(2, "Occupation is missing"); + + /** + * Error for when date of birth field is blank or missing. + */ + public static final NotificationError MISSING_DOB = + new NotificationError(3, "Date of birth is missing"); + + /** + * Error for when date of birth is less than 18 years ago. + */ + public static final NotificationError DOB_TOO_SOON = + new NotificationError(4, "Worker registered must be over 18"); + + + protected RegisterWorkerDto() { + super(); + } + + ... +} +``` + +These errors are stored within a simple wrapper class called NotificationError. + +Our service layer (RegisterWorkerService) represents the framework of our service layer. Currently, it will +run the commands necessary to validate our Java object without handling any of the internal logic itself, +passing on the work, along with our DTO, to the domain layer. + +This validation itself is done in RegisterWorker which works within our domain layer. ServerCommand acts as +a SuperType here for the domain and holds any DTOs needed. If it passes validation, our worker is then added into +the database as submission was successful! + +validation: +```java +/** + * Handles internal logic and validation for worker registration. + * Part of the domain layer which collects information and sends it back to the presentation. + */ +@Slf4j +public class RegisterWorker extends ServerCommand { + protected RegisterWorker(RegisterWorkerDto worker) { + super(worker); + } + + /** + * Validates the data provided and adds it to the database in the backend. + */ + public void run() { + //make sure the information submitted is valid + validate(); + if (!super.getNotification().hasErrors()) { + //Add worker to system in backend (not implemented here) + LOGGER.info("Register worker in backend system"); + } + } + + /** + * Validates our data. Checks for any errors and if found, stores them in our notification. + */ + private void validate() { + var ourData = ((RegisterWorkerDto) this.data); + //check if any of submitted data is not given + failIfNullOrBlank(ourData.getName(), RegisterWorkerDto.MISSING_NAME); + failIfNullOrBlank(ourData.getOccupation(), RegisterWorkerDto.MISSING_OCCUPATION); + failIfNullOrBlank(ourData.getDateOfBirth().toString(), RegisterWorkerDto.MISSING_DOB); + //only if DOB is not blank, then check if worker is over 18 to register. + if (!super.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)) { + var dateOfBirth = ourData.getDateOfBirth(); + var current = now().minusYears(18); + fail(dateOfBirth.compareTo(current) > 0, RegisterWorkerDto.DOB_TOO_SOON); + } + } + + ... +} +``` + +After all of this explanation, we can then simulate the following inputs into the form and submit them: + +input: +```java + /** + * Variables to be submitted in the form. + */ + private static final String NAME = ""; + private static final String OCCUPATION = ""; + private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13); + + RegisterWorkerForm form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH); + form.submit(); +``` + +The form then processes the submission and returns these error messages to the user, showing our notification worked. + +output: +```java +18:10:00.075 [main] INFO com.iluwater.RegisterWorkerForm - Error 1: Name is missing: "" +18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 2: Occupation is missing: "" +18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13" +18:10:00.080 [main] INFO com.iluwater.RegisterWorkerForm - Not registered, see errors +``` + +## Class diagram + +![alt text](./etc/notification.urm.png "Notification") + +## Applicability + +Use the notification pattern when: + +* You wish to communicate information about errors between the domain layer and the presentation layer. This is most applicable when a seperated presentation pattern is being used as this does not allow for direct communication between the domain and presentation. + +## Related patterns + +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/) +* [Data Transfer Object](https://java-design-patterns.com/patterns/data-transfer-object/) +* [Domain Model](https://java-design-patterns.com/patterns/domain-model/) +* [Remote Facade](https://martinfowler.com/eaaCatalog/remoteFacade.html) +* [Autonomous View](https://martinfowler.com/eaaDev/AutonomousView.html) +* [Layer Supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html) +* [Separated Presentation](https://java-design-patterns.com/patterns/data-transfer-object/) + +## Credits + +* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html) \ No newline at end of file diff --git a/notification/etc/notification.urm.png b/notification/etc/notification.urm.png new file mode 100644 index 000000000..dd4a9b7d5 Binary files /dev/null and b/notification/etc/notification.urm.png differ diff --git a/notification/etc/notification.urm.puml b/notification/etc/notification.urm.puml new file mode 100644 index 000000000..ca4fe2776 --- /dev/null +++ b/notification/etc/notification.urm.puml @@ -0,0 +1,89 @@ +@startuml +package com.iluwatar { + class App { + - DATE_OF_BIRTH : LocalDate {static} + - NAME : String {static} + - OCCUPATION : String {static} + - App() + + main(args : String[]) {static} + } + class DataTransferObject { + # notification : Notification + + DataTransferObject() + + getNotification() : Notification + } + class Notification { + - errors : List + + Notification() + + addError(error : NotificationError) + + getErrors() : List + + hasErrors() : boolean + } + class NotificationError { + - errorId : int + - errorMessage : String + + NotificationError(errorId : int, errorMessage : String) + + getErrorId() : int + + getErrorMessage() : String + + toString() : String + } + class RegisterWorker { + - LOGGER : Logger {static} + # RegisterWorker(worker : RegisterWorkerDto) + # fail(condition : boolean, error : NotificationError) + # failIfNullOrBlank(s : String, error : NotificationError) + # isNullOrBlank(s : String) : boolean + + run() + - validate() + } + class RegisterWorkerDto { + + DOB_TOO_SOON : NotificationError {static} + + MISSING_DOB : NotificationError {static} + + MISSING_NAME : NotificationError {static} + + MISSING_OCCUPATION : NotificationError {static} + - dateOfBirth : LocalDate + - name : String + - occupation : String + # RegisterWorkerDto() + + getDateOfBirth() : LocalDate + + getName() : String + + getOccupation() : String + + setDateOfBirth(dateOfBirth : LocalDate) + + setName(name : String) + + setOccupation(occupation : String) + + setupWorkerDto(name : String, occupation : String, dateOfBirth : LocalDate) + } + class RegisterWorkerForm { + - LOGGER : Logger {static} + ~ dateOfBirth : LocalDate + ~ name : String + ~ occupation : String + ~ service : RegisterWorkerService + ~ worker : RegisterWorkerDto + + RegisterWorkerForm(name : String, occupation : String, dateOfBirth : LocalDate) + - checkError(error : NotificationError, info : String) + - indicateErrors() + - saveToWorker() + ~ showError(info : String, message : String) + + submit() + } + class RegisterWorkerService { + + RegisterWorkerService() + + registerWorker(registration : RegisterWorkerDto) + } + class ServerCommand { + # data : DataTransferObject + + ServerCommand(data : DataTransferObject) + + getNotification() : Notification + } +} +Notification --> "-errors" NotificationError +DataTransferObject --> "-notification" Notification +RegisterWorkerForm --> "-service" RegisterWorkerService +RegisterWorkerForm --> "-worker" RegisterWorkerDto +ServerCommand --> "-data" DataTransferObject +RegisterWorkerDto --> "-MISSING_DOB" NotificationError +RegisterWorkerDto --> "-MISSING_NAME" NotificationError +RegisterWorker --|> ServerCommand +RegisterWorkerDto --|> DataTransferObject +@enduml \ No newline at end of file diff --git a/notification/pom.xml b/notification/pom.xml new file mode 100644 index 000000000..9755f96f3 --- /dev/null +++ b/notification/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + + org.junit.jupiter + junit-jupiter-params + test + + + + notification + \ No newline at end of file diff --git a/notification/src/main/java/com/iluwatar/App.java b/notification/src/main/java/com/iluwatar/App.java new file mode 100644 index 000000000..70e51236c --- /dev/null +++ b/notification/src/main/java/com/iluwatar/App.java @@ -0,0 +1,26 @@ +package com.iluwatar; + +import java.time.LocalDate; + +/** + * The notification pattern captures information passed between layers, validates the information, and returns + * any errors to the presentation layer if needed. + * + *

In this code, this pattern is implemented through the example of a form being submitted to register + * a worker. The worker inputs their name, occupation, and date of birth to the RegisterWorkerForm (which acts + * as our presentation layer), and passes it to the RegisterWorker class (our domain layer) which validates it. + * Any errors caught by the domain layer are then passed back to the presentation layer through the + * RegisterWorkerDto.

+ */ +public class App { + + private static final String NAME = ""; + private static final String OCCUPATION = ""; + private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13); + + public static void main(String[] args) { + var form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH); + form.submit(); + } + +} diff --git a/notification/src/main/java/com/iluwatar/DataTransferObject.java b/notification/src/main/java/com/iluwatar/DataTransferObject.java new file mode 100644 index 000000000..23430e570 --- /dev/null +++ b/notification/src/main/java/com/iluwatar/DataTransferObject.java @@ -0,0 +1,16 @@ +package com.iluwatar; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Layer super type for all Data Transfer Objects. + * Also contains code for accessing our notification. + */ +@Getter +@NoArgsConstructor +public class DataTransferObject { + + private final Notification notification = new Notification(); + +} diff --git a/notification/src/main/java/com/iluwatar/Notification.java b/notification/src/main/java/com/iluwatar/Notification.java new file mode 100644 index 000000000..93a40ecc7 --- /dev/null +++ b/notification/src/main/java/com/iluwatar/Notification.java @@ -0,0 +1,26 @@ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * The notification. Used for storing errors and any other methods + * that may be necessary for when we send information back to the + * presentation layer. + */ +@Getter +@NoArgsConstructor +public class Notification { + + private final List errors = new ArrayList<>(); + + public boolean hasErrors() { + return !this.errors.isEmpty(); + } + + public void addError(NotificationError error) { + this.errors.add(error); + } +} diff --git a/notification/src/main/java/com/iluwatar/NotificationError.java b/notification/src/main/java/com/iluwatar/NotificationError.java new file mode 100644 index 000000000..d53d900e5 --- /dev/null +++ b/notification/src/main/java/com/iluwatar/NotificationError.java @@ -0,0 +1,20 @@ +package com.iluwatar; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Error class for storing information on the error. + * Error ID is not necessary, but may be useful for serialisation. + */ +@Getter +@AllArgsConstructor +public class NotificationError { + private int errorId; + private String errorMessage; + + @Override + public String toString() { + return "Error " + errorId + ": " + errorMessage; + } +} diff --git a/notification/src/main/java/com/iluwatar/RegisterWorker.java b/notification/src/main/java/com/iluwatar/RegisterWorker.java new file mode 100644 index 000000000..35b101a95 --- /dev/null +++ b/notification/src/main/java/com/iluwatar/RegisterWorker.java @@ -0,0 +1,79 @@ +package com.iluwatar; + +import java.time.LocalDate; +import java.time.Period; +import lombok.extern.slf4j.Slf4j; + +/** + * Class which handles actual internal logic and validation for worker registration. + * Part of the domain layer which collects information and sends it back to the presentation. + */ +@Slf4j +public class RegisterWorker extends ServerCommand { + static final int LEGAL_AGE = 18; + protected RegisterWorker(RegisterWorkerDto worker) { + super(worker); + } + + /** + * Validates the data provided and adds it to the database in the backend. + */ + public void run() { + + validate(); + if (!super.getNotification().hasErrors()) { + LOGGER.info("Register worker in backend system"); + } + } + + /** + * Validates our data. Checks for any errors and if found, adds to notification. + */ + private void validate() { + var ourData = ((RegisterWorkerDto) this.data); + //check if any of submitted data is not given + // passing for empty value validation + fail(isNullOrBlank(ourData.getName()), RegisterWorkerDto.MISSING_NAME); + fail(isNullOrBlank(ourData.getOccupation()), RegisterWorkerDto.MISSING_OCCUPATION); + fail(isNullOrBlank(ourData.getDateOfBirth()), RegisterWorkerDto.MISSING_DOB); + + if (isNullOrBlank(ourData.getDateOfBirth())) { + // If DOB is null or empty + fail(true, RegisterWorkerDto.MISSING_DOB); + } else { + // Validating age ( should be greater than or equal to 18 ) + Period age = Period.between(ourData.getDateOfBirth(), LocalDate.now()); + fail(age.getYears() < LEGAL_AGE, RegisterWorkerDto.DOB_TOO_SOON); + } + } + + /** + * Validates for null/empty value. + * + * @param obj any object + * @return boolean + */ + protected boolean isNullOrBlank(Object obj) { + if (obj == null) { + return true; + } + + if (obj instanceof String) { + return ((String) obj).trim().isEmpty(); + } + + return false; + } + + /** + * If a condition is met, adds the error to our notification. + * + * @param condition condition to check for. + * @param error error to add if condition met. + */ + protected void fail(boolean condition, NotificationError error) { + if (condition) { + super.getNotification().addError(error); + } + } +} diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java b/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java new file mode 100644 index 000000000..1bf705e62 --- /dev/null +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java @@ -0,0 +1,59 @@ +package com.iluwatar; + +import java.time.LocalDate; +import lombok.Getter; +import lombok.Setter; + +/** + * Data transfer object which stores information about the worker. This is carried between + * objects and layers to reduce the number of method calls made. + */ +@Getter +@Setter +public class RegisterWorkerDto extends DataTransferObject { + private String name; + private String occupation; + private LocalDate dateOfBirth; + + /** + * Error for when name field is blank or missing. + */ + public static final NotificationError MISSING_NAME = + new NotificationError(1, "Name is missing"); + + /** + * Error for when occupation field is blank or missing. + */ + public static final NotificationError MISSING_OCCUPATION = + new NotificationError(2, "Occupation is missing"); + + /** + * Error for when date of birth field is blank or missing. + */ + public static final NotificationError MISSING_DOB = + new NotificationError(3, "Date of birth is missing"); + + /** + * Error for when date of birth is less than 18 years ago. + */ + public static final NotificationError DOB_TOO_SOON = + new NotificationError(4, "Worker registered must be over 18"); + + + protected RegisterWorkerDto() { + super(); + } + + /** + * Simple set up function for capturing our worker information. + * + * @param name Name of the worker + * @param occupation occupation of the worker + * @param dateOfBirth Date of Birth of the worker + */ + public void setupWorkerDto(String name, String occupation, LocalDate dateOfBirth) { + this.name = name; + this.occupation = occupation; + this.dateOfBirth = dateOfBirth; + } +} diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java b/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java new file mode 100644 index 000000000..3d03c967c --- /dev/null +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java @@ -0,0 +1,66 @@ +package com.iluwatar; + +import java.time.LocalDate; +import lombok.extern.slf4j.Slf4j; + +/** + * The form submitted by the user, part of the presentation layer, + * linked to the domain layer through a data transfer object and + * linked to the service layer directly. + */ +@Slf4j +public class RegisterWorkerForm { + String name; + String occupation; + LocalDate dateOfBirth; + RegisterWorkerDto worker; + RegisterWorkerService service = new RegisterWorkerService(); + + /** + * Constructor. + * + * @param name Name of the worker + * @param occupation occupation of the worker + * @param dateOfBirth Date of Birth of the worker + */ + public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { + this.name = name; + this.occupation = occupation; + this.dateOfBirth = dateOfBirth; + } + + /** + * Attempts to submit the form for registering a worker. + */ + public void submit() { + //Transmit information to our transfer object to communicate between layers + saveToWorker(); + //call the service layer to register our worker + service.registerWorker(worker); + + //check for any errors + if (worker.getNotification().hasErrors()) { + indicateErrors(); + LOGGER.info("Not registered, see errors"); + } else { + LOGGER.info("Registration Succeeded"); + } + } + + /** + * Saves worker information to the data transfer object. + */ + private void saveToWorker() { + worker = new RegisterWorkerDto(); + worker.setName(name); + worker.setOccupation(occupation); + worker.setDateOfBirth(dateOfBirth); + } + + /** + * Check for any errors with form submission and show them to the user. + */ + public void indicateErrors() { + worker.getNotification().getErrors().forEach(error -> LOGGER.error(error.toString())); + } +} diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerService.java b/notification/src/main/java/com/iluwatar/RegisterWorkerService.java new file mode 100644 index 000000000..bcdd4e08b --- /dev/null +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerService.java @@ -0,0 +1,18 @@ +package com.iluwatar; + +/** + * Service used to register a worker. + * This represents the basic framework of a service layer which can be built upon. + */ +public class RegisterWorkerService { + /** + * Creates and runs a command object to do the work needed, + * in this case, register a worker in the system. + * + * @param registration worker to be registered if possible + */ + public void registerWorker(RegisterWorkerDto registration) { + var cmd = new RegisterWorker(registration); + cmd.run(); + } +} diff --git a/notification/src/main/java/com/iluwatar/ServerCommand.java b/notification/src/main/java/com/iluwatar/ServerCommand.java new file mode 100644 index 000000000..8c820d3de --- /dev/null +++ b/notification/src/main/java/com/iluwatar/ServerCommand.java @@ -0,0 +1,21 @@ +package com.iluwatar; + +import lombok.AllArgsConstructor; + +/** + * Stores the dto and access the notification within it. + * Acting as a layer supertype in this instance for the domain layer. + */ +@AllArgsConstructor +public class ServerCommand { + protected DataTransferObject data; + + /** + * Basic getter to extract information from our data. + * + * @return the notification stored within the data + */ + public Notification getNotification() { + return data.getNotification(); + } +} diff --git a/notification/src/test/java/com/iluwatar/AppTest.java b/notification/src/test/java/com/iluwatar/AppTest.java new file mode 100644 index 000000000..8c20e97da --- /dev/null +++ b/notification/src/test/java/com/iluwatar/AppTest.java @@ -0,0 +1,14 @@ +package com.iluwatar; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} + diff --git a/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java b/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java new file mode 100644 index 000000000..ffeccce82 --- /dev/null +++ b/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java @@ -0,0 +1,53 @@ +package com.iluwatar; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; + +class RegisterWorkerFormTest { + + private RegisterWorkerForm registerWorkerForm; + + @Test + void submitSuccessfully() { + // Ensure the worker is null initially + registerWorkerForm = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1)); + + assertNull(registerWorkerForm.worker); + + // Submit the form + registerWorkerForm.submit(); + + // Verify that the worker is not null after submission + assertNotNull(registerWorkerForm.worker); + + // Verify that the worker's properties are set correctly + assertEquals("John Doe", registerWorkerForm.worker.getName()); + assertEquals("Engineer", registerWorkerForm.worker.getOccupation()); + assertEquals(LocalDate.of(1990, 1, 1), registerWorkerForm.worker.getDateOfBirth()); + } + + @Test + void submitWithErrors() { + // Set up the worker with a notification containing errors + registerWorkerForm = new RegisterWorkerForm(null, null, null); + + // Submit the form + registerWorkerForm.submit(); + + // Verify that the worker's properties remain unchanged + assertNull(registerWorkerForm.worker.getName()); + assertNull(registerWorkerForm.worker.getOccupation()); + assertNull(registerWorkerForm.worker.getDateOfBirth()); + + // Verify the presence of errors + assertEquals(registerWorkerForm.worker.getNotification().getErrors().size(), 4); + } + +} diff --git a/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java b/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java new file mode 100644 index 000000000..7b258afb6 --- /dev/null +++ b/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java @@ -0,0 +1,90 @@ +package com.iluwatar; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +class RegisterWorkerTest { + + @Test + void runSuccessfully() { + RegisterWorkerDto validWorkerDto = createValidWorkerDto(); + validWorkerDto.setupWorkerDto("name", "occupation", LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(validWorkerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that there are no errors in the notification + assertFalse(registerWorker.getNotification().hasErrors()); + } + + @Test + void runWithMissingName() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto(null, "occupation", LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing name error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_NAME)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + @Test + void runWithMissingOccupation() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto("name", null, LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing occupation error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_OCCUPATION)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + @Test + void runWithMissingDOB() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto("name", "occupation", null); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing DOB error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)); + assertEquals(registerWorker.getNotification().getErrors().size(), 2); + } + + @Test + void runWithUnderageDOB() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setDateOfBirth(LocalDate.now().minusYears(17)); // Under 18 + workerDto.setupWorkerDto("name", "occupation", LocalDate.now().minusYears(17)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the underage DOB error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.DOB_TOO_SOON)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + private RegisterWorkerDto createValidWorkerDto() { + return new RegisterWorkerDto(); + } +} diff --git a/pom.xml b/pom.xml index e45a5ee16..8af7ff7ae 100644 --- a/pom.xml +++ b/pom.xml @@ -210,6 +210,7 @@ crtp log-aggregation health-check + notification single-table-inheritance