| | |
| | | Clock clock = mock(Clock.class); |
| | | when(clock.instant()).thenReturn(CAPTURED_AT); |
| | | var service = new TestDataReceptionServiceImpl(fixture.mapper, fixture.tests, fixture.simulator, clock); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(true, |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(true, |
| | | List.of(collector("S-01", 10L, "temperature", "25.1", "℃")))); |
| | | when(fixture.mapper.insertRealtimeRecord(any())).thenReturn(1); |
| | | service.captureReceivedData(); |
| | | |
| | | when(clock.instant()).thenReturn(CAPTURED_AT.plusSeconds(5)); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(true, |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(true, |
| | | List.of(collector("S-01", 11L, "temperature", "38", "℃")))); |
| | | service.captureReceivedData(); |
| | | |
| | |
| | | test.getBatteries().get(0).setCollectorChannels(List.of( |
| | | mapping("PRIMARY_TEMPERATURE", "S-01"), mapping("SECONDARY_TEMPERATURE_2", "S-03"), |
| | | mapping("SECONDARY_TEMPERATURE_3", "S-05"))); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(true, List.of( |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(true, List.of( |
| | | collector("S-01", 10L, "temperature", "38", "℃"), |
| | | collector("S-03", 10L, "temperature", "25.3", "℃"), |
| | | collector("S-05", 10L, "temperature", "25.5", "℃")))); |
| | |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.latestCabinet(any(ChannelIdentity.class))).thenReturn(cabinetRecord()); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(true, List.of( |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(true, List.of( |
| | | collector("S-01", 10L, "TEMPERATURE", "25.100", "℃"), |
| | | collector("S-02", 10L, "POSITIVE_POTENTIAL", "1.620", "V"), |
| | | collector("S-03", 10L, "NEGATIVE_POTENTIAL", "1.610", "V"), |
| | |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.latestCabinet(any(ChannelIdentity.class))).thenThrow(new IllegalArgumentException("offline")); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(true, |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(true, |
| | | List.of(collector("S-01", 11L, "TEMPERATURE", "26.2", "℃")))); |
| | | when(fixture.mapper.insertRealtimeRecord(any())).thenReturn(1); |
| | | |
| | |
| | | Instant started = Instant.parse("2026-09-02T04:00:10Z"); |
| | | Instant ended = Instant.parse("2026-09-02T04:00:50Z"); |
| | | when(fixture.simulator.stepRecords(any(ChannelIdentity.class))).thenReturn(List.of(new StepRecord( |
| | | "STEP-1", 1, 2, "恒流充电", "cc", started, ended, 3.4, 2.5, 8.4, ended.toString(), "UTC", ended, |
| | | "STEP-1", 1, 2, "恒流充电", "cc", started, ended, 3.4, 2.6, 2.5, 8.4, ended.toString(), "UTC", ended, |
| | | java.util.Map.of("stepDuration", new BigDecimal("40.125"), "startVoltage", new BigDecimal("3.1"), |
| | | "chargeMedianVoltage", new BigDecimal("3.25"), "dischargeMedianVoltage", BigDecimal.ZERO)))); |
| | | when(fixture.simulator.cycleRecords(any(ChannelIdentity.class))).thenReturn(List.of(new CycleRecord( |
| | | "CYCLE-1-1-1", 1, started, ended, 2.5, 2.4, 8.4, 8.0, 96, ended.toString(), "UTC", ended))); |
| | | "CYCLE-1-1-1", 1, started, ended, 2.5, 2.4, 8.4, 8.0, 96d, ended.toString(), "UTC", ended, |
| | | CycleRecord.SOURCE_DEVICE))); |
| | | when(fixture.mapper.insertStepRecord(any())).thenReturn(1); |
| | | when(fixture.mapper.insertCycleRecord(any())).thenReturn(1); |
| | | |
| | |
| | | assertEquals(BigDecimal.ZERO, metrics.getBigDecimal("dischargeMedianVoltage")); |
| | | assertEquals("CYCLE-1-1-1", cycle.getValue().getSourceRecordId()); |
| | | assertEquals("record:CYCLE-1-1-1", cycle.getValue().getDedupeKey()); |
| | | // 设备或文件直接提供的循环统计保留原有来源类别。 |
| | | assertEquals(CycleRecord.SOURCE_DEVICE, cycle.getValue().getSourceKind()); |
| | | // 工步结束时的设备读数:电压与电流一起落库。 |
| | | assertEquals(new BigDecimal("3.4"), step.getValue().getVoltageV()); |
| | | assertEquals(new BigDecimal("2.6"), step.getValue().getCurrentA()); |
| | | assertEquals(Date.from(ended), step.getValue().getServerReceivedAtUtc()); |
| | | assertEquals(Date.from(ended), cycle.getValue().getServerReceivedAtUtc()); |
| | | } |
| | | |
| | | /** 接收调度每秒运行,但来源按自己的写库节奏产出工步:不按来源节奏限流会每秒重查厂商库。 */ |
| | | @Test void readsTheStepLayerOncePerSourceCadenceInsteadOfEverySchedulerTick() |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.stepRecords(any(ChannelIdentity.class))).thenReturn(List.of()); |
| | | when(fixture.simulator.cycleRecords(any(ChannelIdentity.class))).thenReturn(List.of()); |
| | | |
| | | TestDataReceptionServiceImpl service = fixture.service(); |
| | | service.captureReceivedData(); |
| | | service.captureReceivedData(); |
| | | service.captureReceivedData(); |
| | | |
| | | verify(fixture.simulator, times(1)).stepRecords(any(ChannelIdentity.class)); |
| | | verify(fixture.simulator, times(1)).cycleRecords(any(ChannelIdentity.class)); |
| | | } |
| | | |
| | | @Test |
| | |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.latestCabinet(any(ChannelIdentity.class))).thenReturn(cabinetRecord()); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(false, List.of())); |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(false, List.of())); |
| | | when(fixture.mapper.insertRealtimeRecord(any())).thenReturn(1); |
| | | |
| | | fixture.service().captureReceivedData(); |
| | |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.latestCabinet(any(ChannelIdentity.class))).thenReturn(cabinetRecord()); |
| | | when(fixture.simulator.latestCollector("collector-9")).thenReturn(collectorSnapshot(false, List.of())); |
| | | when(fixture.simulator.latestCollector(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.eq("collector-9"))).thenReturn(collectorSnapshot(false, List.of())); |
| | | when(fixture.mapper.selectRealtimeRecordContentHash(eq(1L), eq(4L), any(Date.class))) |
| | | .thenReturn("different-content"); |
| | | |
| | |
| | | fixture.tests.selectTestById(1L).setReceptionBoundaryAtUtc(Date.from(CAPTURED_AT)); |
| | | Instant pausedAt = CAPTURED_AT.minusSeconds(10); |
| | | when(fixture.simulator.stepRecords(any())).thenReturn(List.of(new StepRecord("paused", 1, 1, "工步", "cc", |
| | | pausedAt.minusSeconds(20), pausedAt, 3, 1, 2, pausedAt.toString(), "UTC", pausedAt, java.util.Map.of()))); |
| | | pausedAt.minusSeconds(20), pausedAt, 3d, 1.5d, 1d, 2d, pausedAt.toString(), "UTC", pausedAt, |
| | | java.util.Map.of()))); |
| | | when(fixture.simulator.cycleRecords(any())).thenReturn(List.of(new CycleRecord("paused", 1, |
| | | pausedAt.minusSeconds(20), pausedAt, 1, 1, 2, 2, 100, pausedAt.toString(), "UTC", pausedAt))); |
| | | pausedAt.minusSeconds(20), pausedAt, 1, 1, 2, 2, 100d, pausedAt.toString(), "UTC", pausedAt, |
| | | CycleRecord.SOURCE_DEVICE))); |
| | | fixture.service().captureReceivedData(); |
| | | verify(fixture.mapper, never()).insertStepRecord(any()); |
| | | verify(fixture.mapper, never()).insertCycleRecord(any()); |
| | | } |
| | | |
| | | /** 循环统计的来源类别随记录落库:聚合值必须能与设备或文件提供的值区分。 */ |
| | | @Test void persistsCycleSourceKindAndKeepsMissingMetricsEmpty() |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.cycleRecords(any(ChannelIdentity.class))).thenReturn(List.of( |
| | | new CycleRecord("neware:442:cycle:3", 3, CAPTURED_AT.minusSeconds(600), CAPTURED_AT, 2.375, 1.383, |
| | | 4.059, 1.715, null, CAPTURED_AT.toString(), "Asia/Hong_Kong", CAPTURED_AT, |
| | | CycleRecord.SOURCE_AGGREGATED))); |
| | | when(fixture.simulator.stepRecords(any(ChannelIdentity.class))).thenReturn(List.of(new StepRecord( |
| | | "neware:442:step:3:7", 3, 7, null, "7", CAPTURED_AT.minusSeconds(60), CAPTURED_AT, 1.3, 0.4, null, null, |
| | | CAPTURED_AT.toString(), "Asia/Hong_Kong", CAPTURED_AT, |
| | | java.util.Map.of("chargeCapacity", new BigDecimal("13.888888888889"))))); |
| | | when(fixture.mapper.insertCycleRecord(any())).thenReturn(1); |
| | | when(fixture.mapper.insertStepRecord(any())).thenReturn(1); |
| | | |
| | | fixture.service().captureReceivedData(); |
| | | |
| | | ArgumentCaptor<TTestCycleRecord> cycle = ArgumentCaptor.forClass(TTestCycleRecord.class); |
| | | verify(fixture.mapper).insertCycleRecord(cycle.capture()); |
| | | assertEquals(CycleRecord.SOURCE_AGGREGATED, cycle.getValue().getSourceKind()); |
| | | // 没有可用充电容量就没有效率:空值不是零。 |
| | | assertNull(cycle.getValue().getEfficiency()); |
| | | assertEquals(new BigDecimal("2.375"), cycle.getValue().getChargeCapacityAh()); |
| | | |
| | | ArgumentCaptor<TTestStepRecord> step = ArgumentCaptor.forClass(TTestStepRecord.class); |
| | | verify(fixture.mapper).insertStepRecord(step.capture()); |
| | | // 方向未确认的工步没有单项容量与能量:落库保持空值,四组明细仍在扩展里。 |
| | | assertNull(step.getValue().getCapacityAh()); |
| | | assertNull(step.getValue().getEnergyWh()); |
| | | assertTrue(step.getValue().getExtensionMeasurementsJson().contains("chargeCapacity")); |
| | | } |
| | | |
| | | @Test void deletedAggregateDoesNotReturnOnRepeatedObservation() |
| | | { |
| | | Fixture fixture = fixture(); |
| | | when(fixture.simulator.stepRecords(any())).thenReturn(List.of(new StepRecord("deleted", 1, 1, "工步", "cc", |
| | | CAPTURED_AT.minusSeconds(10), CAPTURED_AT, 3, 1, 2, CAPTURED_AT.toString(), "UTC", CAPTURED_AT, java.util.Map.of()))); |
| | | CAPTURED_AT.minusSeconds(10), CAPTURED_AT, 3d, 1.5d, 1d, 2d, CAPTURED_AT.toString(), "UTC", CAPTURED_AT, |
| | | java.util.Map.of()))); |
| | | when(fixture.mapper.wasAggregateDeleted(1L, "STEP", 4L, "dev-sim:22:1:1:1", "record:deleted")).thenReturn(true); |
| | | fixture.service().captureReceivedData(); |
| | | verify(fixture.mapper, never()).insertStepRecord(any()); |