Browse Source

test(ai-terminal): add DeviceEventServiceTest — project_id, payload JSON, edge cases

5 tests: persist with project_id isolation, payload JSON serialisation,
null payload handling, empty payload handling, occurredAt default to now.
WangKang 1 week ago
parent
commit
589117e722

+ 109 - 0
emoon-infra/emoon-modules/emoon-ai/emoon-ai-device/src/test/java/com/emoon/ai/device/application/DeviceEventServiceTest.java

@@ -0,0 +1,109 @@
+package com.emoon.ai.device.application;
+
+import cn.hutool.json.JSONUtil;
+import com.emoon.ai.device.domain.AiDeviceEventDO;
+import com.emoon.ai.device.mapper.DeviceEventMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+@DisplayName("DeviceEventService")
+class DeviceEventServiceTest {
+
+    @Mock
+    private DeviceEventMapper eventMapper;
+
+    private DeviceEventService service;
+
+    @BeforeEach
+    void setUp() {
+        service = new DeviceEventService(eventMapper);
+    }
+
+    @Test
+    @DisplayName("ingest persists event with project_id for multi-tenant isolation")
+    void ingestPersistsWithProjectId() {
+        service.ingest("KIOSK-001", "P001", "evt-001", "TOUCH_INTERACTION",
+                Map.of("screen", "home", "action", "tap_register"),
+                LocalDateTime.of(2026, 6, 1, 9, 30), "trace_abc123");
+
+        ArgumentCaptor<AiDeviceEventDO> captor = ArgumentCaptor.forClass(AiDeviceEventDO.class);
+        verify(eventMapper).insert(captor.capture());
+
+        AiDeviceEventDO saved = captor.getValue();
+        assertThat(saved.getDeviceId()).isEqualTo("KIOSK-001");
+        assertThat(saved.getProjectId()).isEqualTo("P001");
+        assertThat(saved.getEventId()).isEqualTo("evt-001");
+        assertThat(saved.getEventType()).isEqualTo("TOUCH_INTERACTION");
+        assertThat(saved.getTraceId()).isEqualTo("trace_abc123");
+        assertThat(saved.getOccurredAt()).isEqualTo(LocalDateTime.of(2026, 6, 1, 9, 30));
+        assertThat(saved.getCreatedAt()).isNotNull();
+    }
+
+    @Test
+    @DisplayName("ingest serialises eventPayload to JSON")
+    void ingestSerialisesPayloadToJson() {
+        Map<String, Object> payload = Map.of("screen", "home", "action", "tap_register");
+
+        service.ingest("DEV-001", "P002", "evt-002", "TOUCH_INTERACTION",
+                payload, null, null);
+
+        ArgumentCaptor<AiDeviceEventDO> captor = ArgumentCaptor.forClass(AiDeviceEventDO.class);
+        verify(eventMapper).insert(captor.capture());
+
+        String payloadJson = captor.getValue().getEventPayload();
+        assertThat(payloadJson).isNotNull();
+        Map<String, Object> parsed = JSONUtil.parseObj(payloadJson);
+        assertThat(parsed).containsEntry("screen", "home").containsEntry("action", "tap_register");
+    }
+
+    @Test
+    @DisplayName("ingest defaults occurredAt to now when null")
+    void ingestDefaultsOccurredAtToNow() {
+        service.ingest("DEV-001", "P003", "evt-003", "CUSTOM",
+                null, null, null);
+
+        ArgumentCaptor<AiDeviceEventDO> captor = ArgumentCaptor.forClass(AiDeviceEventDO.class);
+        verify(eventMapper).insert(captor.capture());
+
+        assertThat(captor.getValue().getOccurredAt()).isNotNull();
+        assertThat(captor.getValue().getOccurredAt()).isBeforeOrEqualTo(LocalDateTime.now());
+    }
+
+    @Test
+    @DisplayName("ingest handles empty payload without NPE")
+    void ingestHandlesEmptyPayload() {
+        service.ingest("DEV-001", "P004", "evt-004", "QR_SCANNED",
+                Map.of(), LocalDateTime.now(), null);
+
+        ArgumentCaptor<AiDeviceEventDO> captor = ArgumentCaptor.forClass(AiDeviceEventDO.class);
+        verify(eventMapper).insert(captor.capture());
+
+        // Empty payload → no JSON stored (null or skipped)
+        assertThat(captor.getValue().getEventPayload()).isNull();
+    }
+
+    @Test
+    @DisplayName("ingest handles null payload without NPE")
+    void ingestHandlesNullPayload() {
+        service.ingest("DEV-001", "P005", "evt-005", "DEVICE_ERROR",
+                null, LocalDateTime.now(), "trace_xyz");
+
+        ArgumentCaptor<AiDeviceEventDO> captor = ArgumentCaptor.forClass(AiDeviceEventDO.class);
+        verify(eventMapper).insert(captor.capture());
+
+        assertThat(captor.getValue().getEventPayload()).isNull();
+    }
+}