test: bootstrap wcs demo domain
This commit is contained in:
53
javaTest/pom.xml
Normal file
53
javaTest/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ft546.wcs</groupId>
|
||||||
|
<artifactId>wcs-dispatch-traffic-demo</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<name>FT546 WCS Dispatch and Traffic Demo</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<junit.version>5.10.2</junit.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<release>8</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.2.5</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>com.ft546.wcs.demo.DemoApplication</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package com.ft546.wcs.demo.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class DispatchQueueEntry {
|
||||||
|
public enum QueueType {
|
||||||
|
BUSINESS,
|
||||||
|
AVOIDANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String queueId;
|
||||||
|
private final String taskId;
|
||||||
|
private final String vehicleId;
|
||||||
|
private final QueueType queueType;
|
||||||
|
private final String startSite;
|
||||||
|
private final String endSite;
|
||||||
|
private final int priority;
|
||||||
|
private int issued;
|
||||||
|
private int executionStatus;
|
||||||
|
private List<String> path;
|
||||||
|
|
||||||
|
private DispatchQueueEntry(
|
||||||
|
String queueId,
|
||||||
|
String taskId,
|
||||||
|
String vehicleId,
|
||||||
|
QueueType queueType,
|
||||||
|
String startSite,
|
||||||
|
String endSite,
|
||||||
|
int priority
|
||||||
|
) {
|
||||||
|
this.queueId = queueId;
|
||||||
|
this.taskId = taskId;
|
||||||
|
this.vehicleId = vehicleId;
|
||||||
|
this.queueType = queueType;
|
||||||
|
this.startSite = startSite;
|
||||||
|
this.endSite = endSite;
|
||||||
|
this.priority = priority;
|
||||||
|
this.issued = 0;
|
||||||
|
this.executionStatus = 1;
|
||||||
|
this.path = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DispatchQueueEntry business(DispatchTask task, String vehicleId) {
|
||||||
|
if (task == null) {
|
||||||
|
throw new IllegalArgumentException("task must not be null");
|
||||||
|
}
|
||||||
|
if (vehicleId == null || vehicleId.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("vehicle id must not be blank");
|
||||||
|
}
|
||||||
|
return new DispatchQueueEntry(
|
||||||
|
task.getTaskId(),
|
||||||
|
task.getTaskId(),
|
||||||
|
vehicleId,
|
||||||
|
QueueType.BUSINESS,
|
||||||
|
task.getSourceSite(),
|
||||||
|
task.getTargetSite(),
|
||||||
|
task.getPriority()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markIssued(List<String> issuedPath) {
|
||||||
|
if (issued != 0) {
|
||||||
|
throw new IllegalStateException("queue has already been issued");
|
||||||
|
}
|
||||||
|
if (issuedPath == null || issuedPath.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("issued path must not be empty");
|
||||||
|
}
|
||||||
|
this.path = Collections.unmodifiableList(new ArrayList<String>(issuedPath));
|
||||||
|
this.issued = 1;
|
||||||
|
this.executionStatus = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markCompleted() {
|
||||||
|
if (issued == 0) {
|
||||||
|
throw new IllegalStateException("unissued queue cannot complete");
|
||||||
|
}
|
||||||
|
this.executionStatus = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueueId() {
|
||||||
|
return queueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTaskId() {
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVehicleId() {
|
||||||
|
return vehicleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueueType getQueueType() {
|
||||||
|
return queueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStartSite() {
|
||||||
|
return startSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEndSite() {
|
||||||
|
return endSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIssued() {
|
||||||
|
return issued;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExecutionStatus() {
|
||||||
|
return executionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIssued() {
|
||||||
|
return issued == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.ft546.wcs.demo.model;
|
||||||
|
|
||||||
|
public final class DispatchTask {
|
||||||
|
public enum TaskType {
|
||||||
|
INBOUND,
|
||||||
|
OUTBOUND,
|
||||||
|
RELOCATION
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String taskId;
|
||||||
|
private final TaskType type;
|
||||||
|
private final String sourceSite;
|
||||||
|
private final String targetSite;
|
||||||
|
private final int floor;
|
||||||
|
private final int priority;
|
||||||
|
|
||||||
|
public DispatchTask(
|
||||||
|
String taskId,
|
||||||
|
TaskType type,
|
||||||
|
String sourceSite,
|
||||||
|
String targetSite,
|
||||||
|
int floor,
|
||||||
|
int priority
|
||||||
|
) {
|
||||||
|
this.taskId = requireText(taskId, "task id");
|
||||||
|
if (type == null) {
|
||||||
|
throw new IllegalArgumentException("task type must not be null");
|
||||||
|
}
|
||||||
|
this.type = type;
|
||||||
|
this.sourceSite = requireText(sourceSite, "source site");
|
||||||
|
this.targetSite = requireText(targetSite, "target site");
|
||||||
|
if (floor <= 0) {
|
||||||
|
throw new IllegalArgumentException("floor must be positive");
|
||||||
|
}
|
||||||
|
this.floor = floor;
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTaskId() {
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceSite() {
|
||||||
|
return sourceSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetSite() {
|
||||||
|
return targetSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFloor() {
|
||||||
|
return floor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(String value, String name) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(name + " must not be blank");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
javaTest/src/main/java/com/ft546/wcs/demo/model/Site.java
Normal file
66
javaTest/src/main/java/com/ft546/wcs/demo/model/Site.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package com.ft546.wcs.demo.model;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class Site {
|
||||||
|
private final String id;
|
||||||
|
private final int floor;
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
private final String channel;
|
||||||
|
|
||||||
|
public Site(String id, int floor, int x, int y, String channel) {
|
||||||
|
this.id = requireText(id, "site id");
|
||||||
|
if (floor <= 0) {
|
||||||
|
throw new IllegalArgumentException("floor must be positive");
|
||||||
|
}
|
||||||
|
this.floor = floor;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.channel = requireText(channel, "channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFloor() {
|
||||||
|
return floor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(String value, String name) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(name + " must not be blank");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(other instanceof Site)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Site site = (Site) other;
|
||||||
|
return id.equals(site.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.ft546.wcs.demo.model;
|
||||||
|
|
||||||
|
public final class VehicleSnapshot {
|
||||||
|
private final String vehicleId;
|
||||||
|
private final int floor;
|
||||||
|
private final String currentSite;
|
||||||
|
private final int workMode;
|
||||||
|
private final int status;
|
||||||
|
private final int battery;
|
||||||
|
private final boolean connected;
|
||||||
|
private final boolean allowSend;
|
||||||
|
private final boolean cargo;
|
||||||
|
|
||||||
|
public VehicleSnapshot(
|
||||||
|
String vehicleId,
|
||||||
|
int floor,
|
||||||
|
String currentSite,
|
||||||
|
int workMode,
|
||||||
|
int status,
|
||||||
|
int battery,
|
||||||
|
boolean connected,
|
||||||
|
boolean allowSend,
|
||||||
|
boolean cargo
|
||||||
|
) {
|
||||||
|
this.vehicleId = requireText(vehicleId, "vehicle id");
|
||||||
|
if (floor <= 0) {
|
||||||
|
throw new IllegalArgumentException("floor must be positive");
|
||||||
|
}
|
||||||
|
this.floor = floor;
|
||||||
|
this.currentSite = requireText(currentSite, "current site");
|
||||||
|
this.workMode = workMode;
|
||||||
|
this.status = status;
|
||||||
|
if (battery < 0 || battery > 100) {
|
||||||
|
throw new IllegalArgumentException("battery must be between 0 and 100");
|
||||||
|
}
|
||||||
|
this.battery = battery;
|
||||||
|
this.connected = connected;
|
||||||
|
this.allowSend = allowSend;
|
||||||
|
this.cargo = cargo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVehicleId() {
|
||||||
|
return vehicleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFloor() {
|
||||||
|
return floor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentSite() {
|
||||||
|
return currentSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWorkMode() {
|
||||||
|
return workMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBattery() {
|
||||||
|
return battery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowSend() {
|
||||||
|
return allowSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCargo() {
|
||||||
|
return cargo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VehicleSnapshot at(String siteId, int newFloor) {
|
||||||
|
return new VehicleSnapshot(
|
||||||
|
vehicleId,
|
||||||
|
newFloor,
|
||||||
|
siteId,
|
||||||
|
workMode,
|
||||||
|
status,
|
||||||
|
battery,
|
||||||
|
connected,
|
||||||
|
allowSend,
|
||||||
|
cargo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(String value, String name) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(name + " must not be blank");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.ft546.wcs.demo;
|
||||||
|
|
||||||
|
import com.ft546.wcs.demo.model.DispatchQueueEntry;
|
||||||
|
import com.ft546.wcs.demo.model.DispatchTask;
|
||||||
|
import com.ft546.wcs.demo.model.Site;
|
||||||
|
import com.ft546.wcs.demo.model.VehicleSnapshot;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 QueueLifecycleTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queueUsesLegacyLifecycleCodes() {
|
||||||
|
DispatchTask task = new DispatchTask(
|
||||||
|
"T-1",
|
||||||
|
DispatchTask.TaskType.OUTBOUND,
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
DispatchQueueEntry queue = DispatchQueueEntry.business(task, "1");
|
||||||
|
|
||||||
|
assertEquals(0, queue.getIssued());
|
||||||
|
assertEquals(1, queue.getExecutionStatus());
|
||||||
|
|
||||||
|
queue.markIssued(Arrays.asList("A", "B"));
|
||||||
|
|
||||||
|
assertEquals(1, queue.getIssued());
|
||||||
|
assertEquals(3, queue.getExecutionStatus());
|
||||||
|
|
||||||
|
queue.markCompleted();
|
||||||
|
|
||||||
|
assertEquals(4, queue.getExecutionStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queueCopiesPathAndRejectsCompletionBeforeIssue() {
|
||||||
|
DispatchTask task = new DispatchTask(
|
||||||
|
"T-2",
|
||||||
|
DispatchTask.TaskType.INBOUND,
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
DispatchQueueEntry queue = DispatchQueueEntry.business(task, "1");
|
||||||
|
List<String> path = new java.util.ArrayList<String>(Arrays.asList("A", "B"));
|
||||||
|
|
||||||
|
assertThrows(IllegalStateException.class, queue::markCompleted);
|
||||||
|
|
||||||
|
queue.markIssued(path);
|
||||||
|
path.add("C");
|
||||||
|
|
||||||
|
assertEquals(Arrays.asList("A", "B"), queue.getPath());
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> queue.getPath().add("D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void domainInputsRejectInvalidValues() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new Site("", 1, 0, 0, "C1"));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new VehicleSnapshot("1", 1, "A", 5, 1, 101, true, true, false));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new DispatchTask("", DispatchTask.TaskType.OUTBOUND, "A", "B", 1, 2));
|
||||||
|
|
||||||
|
VehicleSnapshot snapshot = new VehicleSnapshot(
|
||||||
|
"1", 1, "A", 5, 1, 80, true, true, false
|
||||||
|
);
|
||||||
|
assertTrue(snapshot.isConnected());
|
||||||
|
assertEquals("1", snapshot.getVehicleId());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user