Files
xtrhrj/src/test/java/com/xtrh/console/service/ConsoleCollectionStoreTest.java
T

113 lines
6.0 KiB
Java

package com.xtrh.console.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicLong;
import static com.xtrh.common.util.Jsons.*;
import static org.junit.jupiter.api.Assertions.*;
class ConsoleCollectionStoreTest {
private final AtomicLong time = new AtomicLong(1_000_000);
private final ConsoleCollectionStore store = new ConsoleCollectionStore(new ObjectMapper(), time::get);
@Test void startsEmptyAndBecomesStaleWithoutReplacingLastRealValues() {
assertEquals("WAITING", store.snapshot().path("state").asText());
assertTrue(store.snapshot().path("lastSyncedAt").isNull());
store.sync(input());
assertEquals("CONNECTED", store.snapshot().path("state").asText());
assertEquals(0, firstItem().path("value").asInt());
time.addAndGet(15_001);
assertEquals("STALE", store.snapshot().path("state").asText());
assertEquals("speed", firstItem().path("key").asText());
assertEquals(1_000_000, store.snapshot().path("lastSyncedAt").asLong());
ObjectNode fresh = input();
fresh.put("observedAt", time.get());
store.sync(fresh);
assertEquals("CONNECTED", store.snapshot().path("state").asText());
}
@Test void stripsPrivateFieldsAndFiltersSimulatedAndCredentialItems() {
ObjectNode input = input().put("token", "must-not-leak");
ObjectNode equipment = (ObjectNode) input.path("contexts").get(0).path("equipment").get(0);
equipment.put("mediaKey", "must-not-leak");
ArrayNode items = (ArrayNode) equipment.path("items");
((ObjectNode) items.get(0)).put("keyAlias", "must-not-leak");
items.add(item("fake", 50).put("quality", "SIMULATED"));
items.add(item("fake2", 50).put("sourceName", "本地模拟"));
items.add(item("fake3", 50).put("sourceType", "DEMO"));
items.add(item("fake4", 50).put("simulated", true));
items.add(item("mediaKey", 123));
store.sync(input);
assertEquals(1, store.snapshot().path("contexts").get(0).path("equipment").get(0).path("items").size());
assertFalse(store.snapshot().toString().contains("must-not-leak"));
assertFalse(store.snapshot().toString().contains("mediaKey"));
ObjectNode copy = store.snapshot();
((ArrayNode) copy.path("contexts")).removeAll();
assertEquals(1, store.snapshot().path("contexts").size());
}
@Test void invalidOrOutOfOrderPayloadDoesNotEraseAcceptedSnapshot() {
store.sync(input());
ObjectNode old = input().put("observedAt", time.get() - 1);
assertThrows(IllegalArgumentException.class, () -> store.sync(old));
ObjectNode future = input().put("observedAt", time.get() + 60_001);
assertThrows(IllegalArgumentException.class, () -> store.sync(future));
ObjectNode nested = input();
((ObjectNode) nested.path("contexts").get(0).path("equipment").get(0).path("items").get(0))
.set("value", object().put("secret", "hidden"));
assertThrows(IllegalArgumentException.class, () -> store.sync(nested));
assertThrows(IllegalArgumentException.class, () -> store.sync(input().put("source", "OTHER")));
assertThrows(IllegalArgumentException.class, () -> store.sync(input().put("simulated", true)));
assertEquals("speed", firstItem().path("key").asText());
}
@Test void rejectsExcessiveFieldsCountsAndSizeBeforeReplacingState() {
store.sync(input());
ObjectNode longName = input();
((ObjectNode) longName.path("contexts").get(0)).put("name", "x".repeat(201));
assertThrows(IllegalArgumentException.class, () -> store.sync(longName));
ObjectNode excessive = input();
ArrayNode contexts = (ArrayNode) excessive.path("contexts");
for (int i = 0; i < 32; i++) contexts.add(object().put("id", "context-" + i).set("equipment", array()));
assertThrows(IllegalArgumentException.class, () -> store.sync(excessive));
ObjectNode large = input().put("privateExtra", "x".repeat(1_500_000));
assertThrows(IllegalArgumentException.class, () -> store.sync(large));
assertEquals(1, store.snapshot().path("contexts").size());
}
@Test void nullUnknownQualityAndMissingSignalRemainUnavailableRatherThanBecomingZero() {
ObjectNode input = input();
ObjectNode item = (ObjectNode) input.path("contexts").get(0).path("equipment").get(0).path("items").get(0);
item.putNull("value").putNull("receivedAt").put("quality", "UNRECOGNIZED");
store.sync(input);
assertTrue(firstItem().path("value").isNull());
assertTrue(firstItem().path("receivedAt").isNull());
assertEquals("UNKNOWN", firstItem().path("quality").asText());
}
private ObjectNode input() {
ObjectNode result = object().put("source", "PXHJ").put("observedAt", time.get());
ObjectNode equipment = object().put("id", "equipment-a").put("participantCode", "p-1")
.put("name", "真实装备").put("code", "E-01").put("state", "ONLINE").put("lastReceivedAt", time.get());
equipment.set("items", array().add(item("speed", 0)));
ObjectNode context = object().put("id", "trial-1").put("name", "试验").put("roundId", "round-1");
context.set("equipment", array().add(equipment));
result.set("contexts", array().add(context));
return result;
}
private ObjectNode item(String key, int value) {
return object().put("key", key).put("label", "速度").put("type", "NUMBER").put("value", value)
.put("unit", "m/s").put("receivedAt", time.get()).put("sampleTime", time.get())
.put("quality", "GOOD").put("sourceName", "采集模块").put("sourceType", "FIXED_MODULE");
}
private com.fasterxml.jackson.databind.JsonNode firstItem() {
return store.snapshot().path("contexts").get(0).path("equipment").get(0).path("items").get(0);
}
}