From 03d8a2efcd7eec1a7afa18dcaf5c1e3dbc0f6531 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 05 九月 2026 13:39:45 +0800
Subject: [PATCH] 对齐测试删除和时间显示并补齐全链路验收

---
 src/api/testTypes.ts                         |    5 +
 src/views/TestList.vue                       |    4 
 tests/e2e/runtime-without-simulator.spec.ts  |   16 +++++
 src/api/batteryGroups.ts                     |    5 +
 src/api/deviceCollectors.ts                  |    5 +
 src/api/deviceCabinets.ts                    |    5 +
 src/api/batteryGroupFields.ts                |    5 +
 tests/e2e/support/ticket04-stack.ts          |   22 ++++--
 tests/e2e/test-management-acceptance.spec.ts |   86 +++++++++++++++++++++++++++-
 src/api/consoleData.ts                       |   18 ------
 10 files changed, 128 insertions(+), 43 deletions(-)

diff --git a/src/api/batteryGroupFields.ts b/src/api/batteryGroupFields.ts
index a664577..ee3bb75 100644
--- a/src/api/batteryGroupFields.ts
+++ b/src/api/batteryGroupFields.ts
@@ -1,4 +1,5 @@
 import { request, type ApiResponse } from './request'
+import { formatBusinessTime } from '@/utils/businessTime'
 
 export type BatteryGroupFieldStatus = '启用' | '禁用'
 export type BatteryGroupFieldType = '文本' | '数字' | '时间' | '备注'
@@ -8,7 +9,7 @@
   fieldName: string | null
   fieldType: string | null
   createBy: string | null
-  createTime: string | null
+  createTime: number | null
   status: string | null
 }
 
@@ -51,7 +52,7 @@
     name: item.fieldName || '',
     type: mapBatteryGroupFieldType(item.fieldType),
     creator: item.createBy || '-',
-    createdAt: item.createTime || '',
+    createdAt: formatBusinessTime(item.createTime),
     status: item.status === '1' ? '禁用' : '启用',
   }))
 }
diff --git a/src/api/batteryGroups.ts b/src/api/batteryGroups.ts
index b838f99..94c12ff 100644
--- a/src/api/batteryGroups.ts
+++ b/src/api/batteryGroups.ts
@@ -1,4 +1,5 @@
 import { request, type ApiResponse } from './request'
+import { formatBusinessTime } from '@/utils/businessTime'
 import type { BatteryGroupPayload, GroupStatus } from './batteryGroupPayload'
 
 export type { GroupStatus } from './batteryGroupPayload'
@@ -34,7 +35,7 @@
   purpose: string | null
   status: string | null
   createBy: string | null
-  createTime: string | null
+  createTime: number | null
   batteryCount: number | null
   remark?: string | null
   z?: string | null
@@ -114,7 +115,7 @@
     model: item.model || '-',
     purpose: item.purpose || '-',
     batteryCount: Number(item.batteryCount) || 0,
-    createdAt: item.createTime || '-',
+    createdAt: formatBusinessTime(item.createTime),
     status: item.status === '1' ? '禁用' : '启用',
     creator: item.createBy || '-',
     templateFields: (item.templateFields || [])
diff --git a/src/api/consoleData.ts b/src/api/consoleData.ts
index 8dcd2ad..eb94544 100644
--- a/src/api/consoleData.ts
+++ b/src/api/consoleData.ts
@@ -9,17 +9,6 @@
   CYCLE: { recordNo: '记录编号', batteryNo: '电池号', recordedAt: '设备时间', cycleNo: '循环序号', chargeCapacity: '充电容量(Ah)', dischargeCapacity: '放电容量(Ah)', efficiency: '充放电效率(%)', chargeEnergy: '充电能量(Wh)', dischargeEnergy: '放电能量(Wh)' },
 } satisfies Record<ConsoleDataType, Record<string, string>>
 
-export interface ConsoleDataOverride {
-  overrideId: number
-  testId: number
-  dataType: ConsoleDataType
-  stableIdentity: string
-  fieldValues: string | null
-  deleted: boolean
-  actionId: number
-  createdAtUtc: string
-}
-
 export interface ConsoleDataActionPayload {
   dataType: ConsoleDataType
   operationType: ConsoleDataOperation
@@ -32,16 +21,9 @@
     fieldValues: string | null
     deleted: boolean
   }>
-  btsSampleIds?: number[]
-  collectorSampleIds?: number[]
 }
 
-interface OverrideResponse extends ApiResponse { data: ConsoleDataOverride[] }
 interface ActionResponse extends ApiResponse { actionId: number }
-
-export async function getConsoleDataOverrides(testId: number, dataType: ConsoleDataType) {
-  return (await request<OverrideResponse>(`/system/test/${testId}/console-data/${dataType}/overrides`)).data || []
-}
 
 export async function submitConsoleDataAction(testId: number, payload: ConsoleDataActionPayload) {
   return request<ActionResponse>(`/system/test/${testId}/console-data/actions`, {
diff --git a/src/api/deviceCabinets.ts b/src/api/deviceCabinets.ts
index 0497805..b9d1100 100644
--- a/src/api/deviceCabinets.ts
+++ b/src/api/deviceCabinets.ts
@@ -6,6 +6,7 @@
   DeviceCabinetRecord,
 } from '@/stores/deviceCabinets'
 import { request, type ApiResponse } from './request'
+import { formatBusinessTime } from '@/utils/businessTime'
 
 export interface DeviceCabinetListParams {
   pageNum: number
@@ -33,7 +34,7 @@
   currentTest: string | null
   status: string | null
   remark: string | null
-  createTime: string | null
+  createTime: number | null
 }
 
 interface DetectionCabinetListResponse extends ApiResponse {
@@ -108,7 +109,7 @@
     testStatus: testStatusMap[item.testStatus || ''] || '空闲',
     currentTest: item.currentTest || '',
     status: statusMap[item.status || ''] || '禁用',
-    createdAt: item.createTime || '',
+    createdAt: formatBusinessTime(item.createTime),
   }
 }
 
diff --git a/src/api/deviceCollectors.ts b/src/api/deviceCollectors.ts
index 7cedd80..878f909 100644
--- a/src/api/deviceCollectors.ts
+++ b/src/api/deviceCollectors.ts
@@ -6,6 +6,7 @@
   DeviceCollectorRecord,
 } from '@/stores/deviceCollectors'
 import { request, type ApiResponse } from './request'
+import { formatBusinessTime } from '@/utils/businessTime'
 
 export interface DeviceCollectorListParams {
   pageNum: number
@@ -32,7 +33,7 @@
   currentTest: string | null
   status: string | null
   remark: string | null
-  createTime: string | null
+  createTime: number | null
 }
 
 interface CollectorListResponse extends ApiResponse {
@@ -116,7 +117,7 @@
     testStatus: testStatusMap[item.testStatus || ''] || '待检测',
     currentTest: item.currentTest || '',
     status: statusMap[item.status || ''] || '禁用',
-    createdAt: item.createTime || '',
+    createdAt: formatBusinessTime(item.createTime),
   }
 }
 
diff --git a/src/api/testTypes.ts b/src/api/testTypes.ts
index 8b395f4..35a6c93 100644
--- a/src/api/testTypes.ts
+++ b/src/api/testTypes.ts
@@ -1,5 +1,6 @@
 import type { TestTypeRecord, TestTypeStatus } from '@/stores/testTypes'
 import { request, type ApiResponse } from './request'
+import { formatBusinessTime } from '@/utils/businessTime'
 
 export interface TestTypeListParams {
   pageNum: number
@@ -16,7 +17,7 @@
   testTypeName: string | null
   status: string | null
   createBy: string | null
-  createTime: string | null
+  createTime: number | null
   remark: string | null
 }
 
@@ -45,7 +46,7 @@
   return {
     id: item.testTypeId,
     name: item.testTypeName || '-',
-    createdAt: item.createTime || '',
+    createdAt: formatBusinessTime(item.createTime),
     creator: item.createBy || '-',
     remark: item.remark || '',
     status: statusMap[item.status || ''] || '禁用',
diff --git a/src/views/TestList.vue b/src/views/TestList.vue
index a8284a1..cb3b722 100644
--- a/src/views/TestList.vue
+++ b/src/views/TestList.vue
@@ -117,7 +117,7 @@
 
 async function removeTest(item: TestRecord) {
   try {
-    await ElMessageBox.confirm(`删除测试“${item.name}”后无法恢复,是否继续?`, '确认删除测试', {
+    await ElMessageBox.confirm(`删除测试“${item.name}”将一并删除全部测试数据、绑定、阈值、修订和异常记录,且无法恢复,是否继续?`, '确认删除测试', {
       confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning', confirmButtonClass: 'danger-confirm',
     })
     const deletionPage = currentPage.value
@@ -242,7 +242,7 @@
                 <div class="row-actions">
                   <button type="button" @click="openConsole(item)">控制台</button>
                   <button v-if="item.editable" type="button" @click="editTest(item)">编辑</button>
-                  <button v-if="item.editable" class="delete-action" type="button" :disabled="deletingTestId !== null" @click="removeTest(item)">{{ deletingTestId === item.id ? '删除中...' : '删除' }}</button>
+                  <button class="delete-action" type="button" :disabled="deletingTestId !== null" @click="removeTest(item)">{{ deletingTestId === item.id ? '删除中...' : '删除' }}</button>
                 </div>
               </td>
             </tr>
diff --git a/tests/e2e/runtime-without-simulator.spec.ts b/tests/e2e/runtime-without-simulator.spec.ts
new file mode 100644
index 0000000..b1131f2
--- /dev/null
+++ b/tests/e2e/runtime-without-simulator.spec.ts
@@ -0,0 +1,16 @@
+import { expect, test } from '@playwright/test'
+import { Ticket04Stack } from './support/ticket04-stack'
+
+const stack = new Ticket04Stack()
+test.beforeAll(async () => stack.start('druid'))
+test.afterAll(async () => stack.stop())
+
+test('无模拟器成品包以无设备和 BTS 模式完成启动及重复迁移', async ({ request }) => {
+  expect((await request.get(`${stack.backendUrl}/captchaImage`)).ok()).toBe(true)
+  expect(stack.queryDatabase('SELECT COUNT(*) FROM t_device_adapter')).toBe('0')
+  const migrations = stack.queryDatabase('SELECT GROUP_CONCAT(version ORDER BY installed_rank) FROM flyway_schema_history WHERE success=1')
+  expect(migrations).toBe('1,2,3,4,5')
+  await stack.restartBackend('druid,bts')
+  expect((await request.get(`${stack.backendUrl}/captchaImage`)).ok()).toBe(true)
+  expect(stack.queryDatabase('SELECT GROUP_CONCAT(version ORDER BY installed_rank) FROM flyway_schema_history WHERE success=1')).toBe(migrations)
+})
diff --git a/tests/e2e/support/ticket04-stack.ts b/tests/e2e/support/ticket04-stack.ts
index ee44333..88d685c 100644
--- a/tests/e2e/support/ticket04-stack.ts
+++ b/tests/e2e/support/ticket04-stack.ts
@@ -87,7 +87,7 @@
 
   readonly backendUrl = `http://127.0.0.1:${apiPort}`
 
-  async start() {
+  async start(profiles = 'druid,dev-simulator') {
     await Promise.all([mysqlPort, redisPort, apiPort, simulatorPort].map(assertPortAvailable))
     this.docker = resolveDocker()
     mkdirSync(artifactDir, { recursive: true })
@@ -107,7 +107,7 @@
       ])
       await this.waitForContainers()
       this.buildBackend()
-      await this.startBackend()
+      await this.startBackend(profiles)
       this.disableCaptchaInIsolatedDatabase()
     } catch (error) {
       await this.stop()
@@ -115,9 +115,15 @@
     }
   }
 
-  async restartBackend() {
+  async restartBackend(profiles = 'druid,dev-simulator') {
     await this.stopBackend()
-    await this.startBackend()
+    await this.startBackend(profiles)
+  }
+
+  queryDatabase(sql: string) {
+    if (!/^SELECT\s/i.test(sql)) throw new Error('验收查询只允许 SELECT')
+    return this.dockerRun(['exec', this.mysqlContainer, 'mysql', '-uroot', `-p${mysqlPassword}`,
+      '--database=battery_detection', '--batch', '--skip-column-names', '-e', sql]).stdout.trim()
   }
 
   redisCommand(...args: string[]) {
@@ -189,10 +195,10 @@
     if (!existsSync(backendJar)) throw new Error(`未生成后端可执行包: ${backendJar}`)
   }
 
-  private async startBackend() {
+  private async startBackend(profiles = 'druid,dev-simulator') {
     const logPath = resolve(artifactDir, 'backend.log')
     const log = createWriteStream(logPath, { flags: 'a' })
-    const jdbcUrl = `jdbc:mysql://127.0.0.1:${mysqlPort}/battery_detection?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8`
+    const jdbcUrl = `jdbc:mysql://127.0.0.1:${mysqlPort}/battery_detection?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&connectionTimeZone=UTC&forceConnectionTimeZoneToSession=true`
     this.backend = spawn('java', [
       '-jar', backendJar,
       `--server.port=${apiPort}`,
@@ -203,7 +209,7 @@
       `--spring.data.redis.port=${redisPort}`,
       `--spring.data.redis.password=${redisPassword}`,
       '--spring.data.redis.database=0',
-      '--spring.profiles.active=druid,dev-simulator',
+      `--spring.profiles.active=${profiles}`,
       '--spring.devtools.restart.enabled=false',
       `--battery.dev-simulator.port=${simulatorPort}`,
       `--ruoyi.profile=${artifactDir}`,
@@ -218,7 +224,7 @@
     await waitUntil(async () => {
       if (this.backend?.exitCode !== null) throw new Error(`后端提前退出,详见 ${logPath}`)
       try {
-        return (await fetch(`${this.backendUrl}/dev/bts-simulator/state`)).ok
+        return (await fetch(`${this.backendUrl}/captchaImage`)).ok
       } catch {
         return false
       }
diff --git a/tests/e2e/test-management-acceptance.spec.ts b/tests/e2e/test-management-acceptance.spec.ts
index 7a8aa2a..3cfee33 100644
--- a/tests/e2e/test-management-acceptance.spec.ts
+++ b/tests/e2e/test-management-acceptance.spec.ts
@@ -9,9 +9,11 @@
 }
 
 interface PersistedRow {
+  id: string
   recordNo: string
   batteryNo: string
   stableIdentity?: string
+  sequenceId?: number | null
   voltage?: number | string | null
   endVoltage?: number | string | null
   dischargeCapacity?: number | string | null
@@ -124,6 +126,7 @@
   await page.getByPlaceholder('输入电池号').fill(names.battery)
   await page.getByRole('button', { name: '保存新建电池组', exact: true }).click()
   await expect(page.locator('tbody tr').filter({ hasText: names.group })).toBeVisible()
+  expect(stack.queryDatabase("SELECT field_value FROM t_battery_group_relation_field relation JOIN t_battery_group_field definition ON definition.field_id=relation.battery_group_field_id WHERE definition.field_name='F' AND relation.del_flag=0")).toBe('2')
 }
 
 async function createTest(page: Page, names: Record<string, string>) {
@@ -217,7 +220,9 @@
     await expect(page.locator('.running-state')).toHaveText('暂停')
     await expect(page.locator('.reception-state')).toHaveText('暂停接收')
     await expect(page.locator('.cell-card .temperature')).toHaveText('-')
-    const pausedCount = (await persistedRows(page, testId, 'records')).length
+    const pausedRows = await persistedRows(page, testId, 'records')
+    const pausedCount = pausedRows.length
+    const pausedCabinetSequence = Math.max(0, ...pausedRows.map(row => Number(row.sequenceId) || 0))
     await page.waitForTimeout(1_500)
     expect((await persistedRows(page, testId, 'records')).length).toBe(pausedCount)
 
@@ -225,6 +230,8 @@
     await page.getByRole('button', { name: '继续接收', exact: true }).last().click()
     await expect(page.locator('.running-state')).toHaveText('检测中')
     await expect.poll(async () => (await persistedRows(page, testId, 'records')).length).toBeGreaterThan(pausedCount)
+    await expect.poll(async () => Math.max(0, ...(await persistedRows(page, testId, 'records')).map(row => Number(row.sequenceId) || 0)))
+      .toBeGreaterThan(pausedCabinetSequence)
   })
 
   await test.step('三类当前有效数据可修订并形成统一修订记录', async () => {
@@ -241,13 +248,15 @@
     await editFirstRow(page, '循环', '放电容量', '4.0000', 'Ticket06 循环数据修订')
 
     await page.getByRole('button', { name: '实时', exact: true }).click()
-    const recordsBeforeRemoval = (await persistedRows(page, testId, 'records')).length
+    const firstRowText = await page.locator('.console-data-panel tbody tr').first().innerText()
+    const recordToRemove = (await persistedRows(page, testId, 'records')).find(row => firstRowText.includes(row.recordNo))
+    expect(recordToRemove).toBeDefined()
     await page.locator('.console-data-panel tbody tr').first().getByRole('button', { name: '删除', exact: true }).click()
-    await expect(page.getByRole('dialog', { name: '移除数据' })).toBeVisible()
+    await expect(page.getByRole('dialog', { name: '物理删除数据' })).toBeVisible()
     await page.locator('.el-message-box__input input').fill('Ticket06 删除当前有效数据')
     await page.getByRole('button', { name: '确定', exact: true }).click()
     await expect(page.getByRole('alert').filter({ hasText: '已从当前有效视图移除 1 条数据' })).toBeVisible()
-    await expect.poll(async () => (await persistedRows(page, testId, 'records')).length).toBe(recordsBeforeRemoval - 1)
+    await expect.poll(async () => (await persistedRows(page, testId, 'records')).some(row => row.id === recordToRemove?.id)).toBe(false)
 
     await page.getByRole('button', { name: '修订记录', exact: true }).click()
     await expect(page).toHaveURL(/\/data-revision/)
@@ -302,6 +311,8 @@
     await page.getByRole('button', { name: '工步图表', exact: true }).click()
     await expect(page.locator('.detail-meta input')).toHaveValue('Ticket06 最新测试详情')
     expect((await persistedRows(page, testId, 'records')).length).toBeGreaterThan(0)
+    expect((await persistedRows(page, testId, 'steps')).some(row => Number(row.endVoltage) === 3.2222)).toBe(true)
+    expect((await persistedRows(page, testId, 'cycles')).some(row => Number(row.dischargeCapacity) === 4)).toBe(true)
   })
 
   await test.step('停止立即进入完成态,异常事实仍可人工关闭追溯', async () => {
@@ -318,7 +329,7 @@
     })
     await page.getByRole('button', { name: '停止测试', exact: true }).click()
     await page.getByRole('button', { name: '停止测试', exact: true }).last().click()
-    await expect(page.locator('.running-state')).toHaveText('检测完成')
+    await expect(page.locator('.running-state')).toHaveText('检测中')
     await expect(page.getByRole('alert').filter({ hasText: '模拟停止失败' })).toBeVisible()
     await expect(page.locator('.project-meta')).toContainText('检测中')
     await page.unroute(`**/dev-api/system/test/${testId}/reception/end`)
@@ -347,10 +358,75 @@
     expect(warningsAfterClose.rows.some(item => item.exceptionStatus === 'CLOSED')).toBe(true)
   })
 
+  await test.step('三类导入直接落表,修改当前 F 后已有测试读取新值', async () => {
+    for (const [index, kind] of ['REALTIME', 'STEP', 'CYCLE'].entries()) {
+      const row = { recordNo: `IMPORT-${kind}`, batteryNo: names.battery, recordedAt: Date.now() + 3_600_000 + index,
+        cycleNo: 999, stepNo: 1, stepName: '人工核对', sequenceId: 1, voltage: 3.8, current: 1,
+        capacity: 2, energy: 4, endVoltage: 3.9, startVoltage: 3.1, stepDuration: 60,
+        chargeCapacity: 2, dischargeCapacity: 1.9, chargeEnergy: 4, dischargeEnergy: 3.8, efficiency: 95 }
+      const result = await apiJson<ApiResult<unknown>>(page, `/system/test/${testId}/console-data/actions`, {
+        method: 'POST', body: JSON.stringify({ dataType: kind, operationType: 'IMPORT', scopeSummary: '导入一条核对记录',
+          identitySummary: '[]', changesSummary: JSON.stringify([row]), reason: 'Ticket13 导入验收' }),
+      })
+      expect(result.code).toBe(200)
+      const table = `t_test_${kind.toLowerCase()}_record`
+      expect(stack.queryDatabase(`SELECT COUNT(*) FROM ${table} WHERE test_id=${testId} AND source_identity='IMPORT'`)).toBe('1')
+      const endpoint = kind === 'REALTIME' ? 'records' : kind === 'STEP' ? 'steps' : 'cycles'
+      expect((await persistedRows(page, testId, endpoint)).some(item => item.recordNo === row.recordNo)).toBe(true)
+      if (kind === 'REALTIME') expect(stack.queryDatabase(`SELECT cabinet_sequence_id FROM ${table} WHERE test_id=${testId} AND source_identity='IMPORT'`)).toBe('1')
+    }
+    const detail = await apiJson<ApiResult<{ batteries: Array<{ groupId: number }> }>>(page, `/system/test/${testId}`)
+    const groupId = detail.data.batteries[0]!.groupId
+    const group = await apiJson<ApiResult<Record<string, unknown> & { batteries: Array<{ batteryNo: string }> }>>(page, `/system/batteryGroup/${groupId}`)
+    const updated = await apiJson<ApiResult<unknown>>(page, '/system/batteryGroup', { method: 'PUT',
+      body: JSON.stringify({ ...group.data, f: '4', batteryNos: group.data.batteries.map(battery => battery.batteryNo) }) })
+    expect(updated.code).toBe(200)
+    expect((await apiJson<ApiResult<{ f: string }>>(page, `/system/batteryGroup/${groupId}`)).data.f).toBe('4')
+    const current = await apiJson<ApiResult<{ batteries: Array<{ fields: Array<{ fieldName: string; fieldValue: string }> }> }>>(page, `/system/test/${testId}`)
+    expect(current.data.batteries[0]!.fields.find(field => field.fieldName === 'F')?.fieldValue).toBe('4')
+    await page.goto(`/test-list/${testId}/console?tab=project-chart`)
+    await expect(page.getByText(/电池组 F 不可用/)).toHaveCount(0)
+  })
+
   await test.step('设备协议日志只包含发现和实时查询', async () => {
     await page.goto(`${stack.backendUrl}/dev/bts-simulator.html`)
     const commands = await page.locator('#requests .request-command').allTextContents()
     expect(commands.length).toBeGreaterThan(0)
     expect(commands.every(command => command === 'getdevinfo' || command === 'inquire')).toBe(true)
   })
+
+  await test.step('同通道跨测试复用,所有状态整体删除无痕且不影响其他事实', async () => {
+    await page.goto(`/test-list/${testId}/console`)
+    const original = await apiJson<ApiResult<Record<string, unknown>>>(page, `/system/test/${testId}`)
+    const deviceCounts = stack.queryDatabase('SELECT (SELECT COUNT(*) FROM t_detection_cabinet), (SELECT COUNT(*) FROM t_collector), (SELECT COUNT(*) FROM t_battery_group)')
+    const tables = ['t_test_realtime_record', 't_test_step_record', 't_test_cycle_record', 't_test_data_revision_action',
+      't_test_warning_exception', 't_test_logic_step_mapping', 't_test_collector_channel', 't_test_channel_binding',
+      't_test_warning_threshold', 't_test_battery', 't_test']
+    for (const lifecycle of ['WAITING', 'RUNNING', 'PAUSED']) {
+      const created = await apiJson<ApiResult<{ testId: number }>>(page, '/system/test', {
+        method: 'POST', body: JSON.stringify({ ...original.data, testId: null, testName: `${names.test}-${lifecycle}` }),
+      })
+      expect(created.code).toBe(200)
+      const id = created.data.testId
+      if (lifecycle !== 'WAITING') {
+        expect((await apiJson<ApiResult<unknown>>(page, `/system/test/${id}/reception/start`, { method: 'POST' })).code).toBe(200)
+        await expect.poll(async () => (await persistedRows(page, id, 'records')).length).toBeGreaterThan(0)
+      }
+      if (lifecycle === 'PAUSED') expect((await apiJson<ApiResult<unknown>>(page, `/system/test/${id}/reception/pause`, { method: 'POST' })).code).toBe(200)
+      expect((await apiJson<ApiResult<unknown>>(page, `/system/test/${id}`, { method: 'DELETE' })).code).toBe(200)
+      for (const table of tables) expect(stack.queryDatabase(`SELECT COUNT(*) FROM ${table} WHERE test_id=${id}`)).toBe('0')
+      expect((await apiJson<ApiResult<unknown>>(page, `/system/test/${testId}`)).code).toBe(200)
+    }
+    expect((await apiJson<ApiResult<unknown>>(page, `/system/test/${testId}`, { method: 'DELETE' })).code).toBe(200)
+    for (const table of tables) expect(stack.queryDatabase(`SELECT COUNT(*) FROM ${table} WHERE test_id=${testId}`)).toBe('0')
+    expect(stack.queryDatabase('SELECT COUNT(*) FROM sys_oper_log WHERE business_type=3 AND oper_url LIKE \'%/system/test/%\'')).toBe('0')
+    expect(stack.queryDatabase('SELECT (SELECT COUNT(*) FROM t_detection_cabinet), (SELECT COUNT(*) FROM t_collector), (SELECT COUNT(*) FROM t_battery_group)')).toBe(deviceCounts)
+  })
+
+  await test.step('无设备模式和真实 BTS 装配均可启动', async () => {
+    await stack.restartBackend('druid')
+    expect((await page.request.get(`${stack.backendUrl}/captchaImage`)).ok()).toBe(true)
+    await stack.restartBackend('druid,bts')
+    expect((await page.request.get(`${stack.backendUrl}/captchaImage`)).ok()).toBe(true)
+  })
 })

--
Gitblit v1.7.1