feat(ft546): add WCS adapter configuration profiles
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package com.ruoyi.project.ft546.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class Ft546ConfigurationValidatorTest {
|
||||
|
||||
@Test
|
||||
void defaultsToLegacySoDeploymentDoesNotTakeSchedulingOwnership() {
|
||||
Ft546WcsProperties properties = new Ft546WcsProperties();
|
||||
|
||||
assertEquals(Ft546WcsProperties.RuntimeMode.LEGACY,
|
||||
properties.getRuntimeMode());
|
||||
assertTrue(properties.getDispatchIntervalMs() > 0L);
|
||||
assertTrue(properties.getCommandPollIntervalMs() > 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsLegacyProfileThatClaimsPrepareCommit() {
|
||||
Ft546DeviceProfile profile = legacySrgvProfile();
|
||||
profile.getCapabilities().add("PREPARE_COMMIT_SEGMENT");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new Ft546ConfigurationValidator().validate(profile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateRegisterOwnershipWithinOneDevice() {
|
||||
Ft546DeviceProfile profile = legacySrgvProfile();
|
||||
profile.getRegisters().put("taskType", "4000");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new Ft546ConfigurationValidator().validate(profile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsTwoStageCapabilityWithoutPassingEvidenceForExactProfile() {
|
||||
Ft546DeviceProfile profile = legacySrgvProfile();
|
||||
profile.getCapabilities().add("TWO_STAGE_ATOMIC_TASK");
|
||||
Ft546CapabilityGate gate = gate();
|
||||
|
||||
CapabilityGateResult result = gate.evaluate(profile, null);
|
||||
|
||||
assertFalse(result.isPublishable());
|
||||
assertEquals("TWO_STAGE_EVIDENCE_REQUIRED", result.getReasonCode());
|
||||
assertFalse(result.isEnabled("TWO_STAGE_ATOMIC_TASK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enablesTwoStageOnlyForPassingUnexpiredEvidenceBoundToProfile() {
|
||||
Ft546DeviceProfile profile = legacySrgvProfile();
|
||||
profile.getCapabilities().add("TWO_STAGE_ATOMIC_TASK");
|
||||
Ft546CapabilityEvidence evidence = new Ft546CapabilityEvidence(
|
||||
1L, "CORPUS-SHA256", "RUN-1", "COMMIT-1", true,
|
||||
Instant.parse("2026-08-10T00:00:00Z"),
|
||||
Instant.parse("2026-09-10T00:00:00Z"));
|
||||
|
||||
CapabilityGateResult result = gate().evaluate(profile, evidence);
|
||||
|
||||
assertTrue(result.isPublishable());
|
||||
assertTrue(result.isEnabled("TWO_STAGE_ATOMIC_TASK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyProfileKeepsUnsupportedCapabilitiesDisabled() {
|
||||
CapabilityGateResult result = gate().evaluate(legacySrgvProfile(), null);
|
||||
|
||||
assertFalse(result.isEnabled("COMMAND_RESULT_QUERY"));
|
||||
assertFalse(result.isEnabled("ORDERED_DEVICE_SNAPSHOT"));
|
||||
assertFalse(result.isEnabled("CANDIDATE_BUFFER"));
|
||||
assertFalse(result.isEnabled("PREPARE_COMMIT_SEGMENT"));
|
||||
assertFalse(result.isEnabled("IDEMPOTENT_COMMAND_KEY"));
|
||||
assertFalse(result.isEnabled("BOOT_SESSION_ID"));
|
||||
assertTrue(result.isEnabled("POINT_TO_POINT_COMMAND"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkedInProfileKeepsEndpointsOutAndLegacyAddressesExact()
|
||||
throws Exception {
|
||||
String yaml = readProjectFile("ruoyi-admin/src/main/resources/ft546/"
|
||||
+ "wcs-device-profiles.yml");
|
||||
|
||||
assertFalse(yaml.contains("192.168."));
|
||||
assertFalse(yaml.toLowerCase().contains("password"));
|
||||
assertTrue(yaml.contains("taskNo: \"4000\""));
|
||||
assertTrue(yaml.contains("taskType: \"4002\""));
|
||||
assertTrue(yaml.contains("path: \"4004\""));
|
||||
assertTrue(yaml.contains("trigger: \"4021\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void packagesAdminOwnedAdapterConfigurationMigration() throws Exception {
|
||||
String sql = readProjectFile(
|
||||
"sql/wcs/V004__ft546_project_adapter_config.sql");
|
||||
|
||||
assertTrue(sql.contains("project_device_endpoint"));
|
||||
assertTrue(sql.contains("project_plc_address"));
|
||||
assertTrue(sql.contains("project_protocol_profile"));
|
||||
assertTrue(sql.contains("project_protocol_capability_evidence"));
|
||||
assertTrue(sql.contains("project_wms_mapping"));
|
||||
}
|
||||
|
||||
private static Ft546CapabilityGate gate() {
|
||||
return new Ft546CapabilityGate(() ->
|
||||
Instant.parse("2026-08-11T00:00:00Z"));
|
||||
}
|
||||
|
||||
private static Ft546DeviceProfile legacySrgvProfile() {
|
||||
Ft546DeviceProfile profile = new Ft546DeviceProfile();
|
||||
profile.setDeviceId("1");
|
||||
profile.setDeviceType("SHUTTLE");
|
||||
profile.setAdapterType("FT546_LEGACY_SRGV");
|
||||
profile.setProfileVersion(1L);
|
||||
profile.setProtocolCorpusHash("CORPUS-SHA256");
|
||||
profile.setCapabilities(new LinkedHashSet<String>(
|
||||
Collections.singletonList("POINT_TO_POINT_COMMAND")));
|
||||
LinkedHashMap<String, String> registers = new LinkedHashMap<String, String>();
|
||||
registers.put("taskNo", "4000");
|
||||
registers.put("taskType", "4002");
|
||||
registers.put("path", "4004");
|
||||
registers.put("trigger", "4021");
|
||||
registers.put("status", "4068");
|
||||
registers.put("execution", "4060");
|
||||
profile.setRegisters(registers);
|
||||
return profile;
|
||||
}
|
||||
|
||||
private static String readProjectFile(String relative) throws Exception {
|
||||
Path current = Paths.get(System.getProperty(
|
||||
"maven.multiModuleProjectDirectory", "."))
|
||||
.toAbsolutePath().normalize();
|
||||
while (current != null) {
|
||||
Path file = current.resolve(relative);
|
||||
if (Files.isRegularFile(file)) {
|
||||
return new String(Files.readAllBytes(file),
|
||||
StandardCharsets.UTF_8);
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
throw new java.nio.file.NoSuchFileException(relative);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user