Files
Scoreboard/web/src/admin/tests/event-realtime.test.ts
T
AskaEth a5941be9c4 fix(admin): 改密后管理面板永久「连接中断」+ 顶部版本号多一个 v
- /ws 身份在握手时确定,而 startRealtime() 全生命周期只建一次连接;改密会作废
  服务端全部会话,服务端以 1008 关闭该连接,前端对 1008 是永久停止重连
  (给真鉴权失败设计的)→ 重新登录后无人重启连接,界面一直显示「连接中断」,
  只有重开窗口才会用新会话重新握手。
  修:新增 restartRealtime(),登录成功与退出登录时各重启一次(同时修掉
  「访客浏览后登录,旧连接仍是 guest 身份」的问题);1008 语义保持不变。
- AdminLayout 顶部版本号模板写死 v 前缀,而 serverHealth 已是完整版本串
  (dev_0.1.0)→ 显示成 vdev_0.1.0。

新增 web/src/admin/tests/event-realtime.test.ts 锁该行为(临时置空 restartRealtime
会失败);为让 node --test 能加载真实 store,测试解析钩子补 @/ 别名。
2026-09-09 23:19:23 +08:00

92 lines
2.6 KiB
TypeScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createPinia, setActivePinia } from 'pinia'
import { adminApi, mockMode } from '../api/client'
import { useEventStore } from '../stores/event'
// P0-1:WS 身份在握手时确定,登录 / 退出 / 改密后必须重建连接。
// 旧连接被服务端 1008 拒绝后客户端会停止重连,没有 restartRealtime 就永远停在 offline。
class FakeSocket {
static readonly CONNECTING = 0
static readonly OPEN = 1
static readonly CLOSED = 3
static instances: FakeSocket[] = []
readyState = FakeSocket.CONNECTING
onopen: (() => void) | null = null
onmessage: ((event: { data: unknown }) => void) | null = null
onerror: (() => void) | null = null
onclose: ((event: { code: number }) => void) | null = null
readonly url: string
readonly sent: string[] = []
constructor(url: string) {
this.url = url
FakeSocket.instances.push(this)
}
send(data: string): void {
this.sent.push(data)
}
close(): void {
this.readyState = FakeSocket.CLOSED
this.onclose?.({ code: 1006 })
}
open(): void {
this.readyState = FakeSocket.OPEN
this.onopen?.()
}
}
test('restartRealtime 关闭旧连接并按当前会话重建', async () => {
FakeSocket.instances = []
;(globalThis as { WebSocket?: unknown }).WebSocket = FakeSocket
setActivePinia(createPinia())
adminApi.bootstrap = async () => ({
settings: null,
teams: [],
sports: [],
events: [],
profiles: [],
standings: [],
athleteStandings: [],
})
adminApi.health = async () => {
throw new Error('health 不可达')
}
const store = useEventStore()
await store.load()
assert.equal(store.realtimeStatus, 'connecting')
assert.equal(FakeSocket.instances.length, 1)
const first = FakeSocket.instances[0]
assert.ok(first)
first.open()
assert.equal(store.realtimeStatus, 'online')
// 改密后服务端作废全部会话,旧连接以 1008 关闭并停在 offline
first.readyState = FakeSocket.CLOSED
first.onclose?.({ code: 1008 })
assert.equal(store.realtimeStatus, 'offline')
store.restartRealtime()
assert.equal(first.readyState, FakeSocket.CLOSED)
assert.equal(FakeSocket.instances.length, 2)
assert.equal(store.realtimeStatus, 'connecting')
const second = FakeSocket.instances[1]
assert.ok(second)
second.open()
assert.equal(store.realtimeStatus, 'online')
// 示例数据模式不建真实连接
mockMode.value = true
store.restartRealtime()
assert.equal(store.realtimeStatus, 'idle')
assert.equal(FakeSocket.instances.length, 2)
})