yeg
2026-09-28 9c93934b70ccfca081102944a47f3baafad3c218
ruoyi-admin/src/test/java/com/battery/business/service/impl/TestDataReceptionServiceImplTest.java
@@ -185,11 +185,12 @@
        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);
@@ -207,8 +208,29 @@
        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
@@ -251,19 +273,54 @@
        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());