Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public void logChange(Ticket ticket,
event.setTicket(ticket);
event.setUser(actor);
event.setField(field);
event.setAction(action);
event.setOldValue(oldValue);
event.setNewValue(newValue);

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/fmazmz/casemanager/ticket/model/Ticket.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.fmazmz.casemanager.ticket.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
Expand All @@ -26,10 +27,16 @@ public class Ticket {
@Column(unique = true, nullable = false)
private String number;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TicketType type;

@Column(nullable = false)
@Size(min = 5, max = 100)
private String title;

@Column(columnDefinition = "TEXT")
@Size(min = 5, max = 5000)
private String description;

@ManyToOne(optional = false)
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/fmazmz/casemanager/ticket/model/TicketType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.fmazmz.casemanager.ticket.model;

import lombok.Getter;

public enum TicketType {
INCIDENT("INC"),
REQUEST("REQ");

@Getter
private final String label;

TicketType(String label) {
this.label = label;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.fmazmz.casemanager.ticket.model.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.fmazmz.casemanager.ticket.model.TicketType;

import java.util.UUID;

public record CreateTicketRequest(
@NotBlank
@Size(min = 5, max = 100, message = "Title must be between 5 and 100 characters")
String title,

@NotBlank
@Size(max = 5000, message = "Description must be between 0 and 255 characters")
String description,

@NotNull
TicketType type,

@NotNull
UUID requesterId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.fmazmz.casemanager.ticket.orchestration.typehandlers;

import org.fmazmz.casemanager.ticket.model.Priority;
import org.fmazmz.casemanager.ticket.model.Ticket;
import org.fmazmz.casemanager.ticket.model.TicketType;
import org.springframework.stereotype.Service;

@Service
public class IncidentHandler implements TypeHandler {
@Override
public TicketType supports() {
return TicketType.INCIDENT;
}

@Override
public void applyDefaults(Ticket ticket) {
if (ticket.getPriority() == null) ticket.setPriority(Priority.P3);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.fmazmz.casemanager.ticket.orchestration.typehandlers;

import org.fmazmz.casemanager.ticket.model.Priority;
import org.fmazmz.casemanager.ticket.model.Ticket;
import org.fmazmz.casemanager.ticket.model.TicketType;
import org.springframework.stereotype.Service;

@Service
public class RequestHandler implements TypeHandler {
@Override
public TicketType supports() {
return TicketType.REQUEST;
}

@Override
public void applyDefaults(Ticket ticket) {
if (ticket.getPriority() == null) ticket.setPriority(Priority.P5);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.fmazmz.casemanager.ticket.orchestration.typehandlers;

import org.fmazmz.casemanager.ticket.model.Ticket;
import org.fmazmz.casemanager.ticket.model.TicketType;

public interface TypeHandler {
TicketType supports();

void applyDefaults(Ticket ticket);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.fmazmz.casemanager.ticket.orchestration.typehandlers;

import lombok.extern.slf4j.Slf4j;
import org.fmazmz.casemanager.ticket.model.TicketType;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TypeHandlerFactory {
private final Map<TicketType, TypeHandler> handlers;

public TypeHandlerFactory(List<TypeHandler> handlerList) {
this.handlers = handlerList.stream()
.collect(Collectors.toMap(TypeHandler::supports, h -> h));
}

public TypeHandler resolve(TicketType type) {
log.info("Resolving handler for type {}", type);

TypeHandler handler = handlers.get(type);
if (handler == null) {
log.warn("Could not resolve a handler for TicketType: {}", type);
throw new IllegalStateException("No handler configured for type: " + type);
}
return handler;
}
}
113 changes: 113 additions & 0 deletions src/test/java/org/fmazmz/casemanager/unit/TypeHandlersTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.fmazmz.casemanager.unit;

import org.fmazmz.casemanager.ticket.model.Priority;
import org.fmazmz.casemanager.ticket.model.Ticket;
import org.fmazmz.casemanager.ticket.model.TicketType;
import org.fmazmz.casemanager.ticket.orchestration.typehandlers.IncidentHandler;
import org.fmazmz.casemanager.ticket.orchestration.typehandlers.RequestHandler;
import org.fmazmz.casemanager.ticket.orchestration.typehandlers.TypeHandler;
import org.fmazmz.casemanager.ticket.orchestration.typehandlers.TypeHandlerFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class TypeHandlersTests {

@Nested
class TypeHandlerFactoryTests {
@Mock
private TypeHandler incidentHandler;
@Mock
private TypeHandler requestHandler;
private TypeHandlerFactory handlerFactory;

@BeforeEach
void setUp() {
when(incidentHandler.supports()).thenReturn(TicketType.INCIDENT);
when(requestHandler.supports()).thenReturn(TicketType.REQUEST);

handlerFactory = new TypeHandlerFactory(List.of(incidentHandler, requestHandler));
}

@Test
@DisplayName("returns handler for INCIDENT")
void returnsIncidentHandler() {
TypeHandler resolved = handlerFactory.resolve(TicketType.INCIDENT);
assertSame(incidentHandler, resolved);
}

@Test
@DisplayName("returns handler for REQUEST")
void returnsRequestHandler() {
TypeHandler resolved = handlerFactory.resolve(TicketType.REQUEST);
assertSame(requestHandler, resolved);
}

@Test
@DisplayName("throws when no handler is configured")
void throwsWhenMissingHandler() {
TypeHandlerFactory incompleteFactory =
new TypeHandlerFactory(List.of(incidentHandler));

IllegalStateException ex = assertThrows(
IllegalStateException.class,
() -> incompleteFactory.resolve(TicketType.REQUEST)
);
assertTrue(ex.getMessage().contains("REQUEST"));
}
}

@Nested
class IncidentHandlerTests {
IncidentHandler incidentHandler = new IncidentHandler();

@Test
@DisplayName("returns INCIDENT as the correct supported TicketType")
void returnsCorrectSupportedType() {
assert(incidentHandler.supports()).equals(TicketType.INCIDENT);
}

@Test
@DisplayName("applies P3 as default priority")
void appliesDefaultPriority() {
Ticket ticket = new Ticket();
ticket.setPriority(null);

incidentHandler.applyDefaults(ticket);

assert(ticket.getPriority()).equals(Priority.P3);
}
}

@Nested
class RequestHandlerTests {
RequestHandler requestHandler = new RequestHandler();

@Test
@DisplayName("returns REQUEST as the correct supported TicketType")
void returnsCorrectSupportedType() {
assert(requestHandler.supports()).equals(TicketType.REQUEST);
}

@Test
@DisplayName("applies P5 as default priority")
void appliesDefaultPriority() {
Ticket ticket = new Ticket();
ticket.setPriority(null);

requestHandler.applyDefaults(ticket);

assert(ticket.getPriority()).equals(Priority.P5);
}
}
}