Skip to content

Commit db6eae7

Browse files
committed
refactor: improve code quality and test coverage
- Use structural anchors instead of hardcoded line numbers in protocol sync script for better maintainability - Add comprehensive unit tests for normalizeClientID and normalizeRoomName functions covering edge cases and boundary conditions - Migrate state management modules from var to const/let for ES6 compliance
1 parent 71150f4 commit db6eae7

8 files changed

Lines changed: 241 additions & 51 deletions

File tree

internal/signal/join_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package signal
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestNormalizeClientID(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
input string
11+
maxLen int
12+
expected string
13+
}{
14+
// 有效情况
15+
{"simple alphanumeric", "user123", 64, "user123"},
16+
{"with hyphen", "user-123", 64, "user-123"},
17+
{"with underscore", "user_123", 64, "user_123"},
18+
{"uppercase", "USER123", 64, "USER123"},
19+
{"mixed case", "User_123-ABC", 64, "User_123-ABC"},
20+
{"only numbers", "123456", 64, "123456"},
21+
{"single char", "a", 64, "a"},
22+
{"exact max length", "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", 62, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"},
23+
24+
// 无效情况 - 应返回空字符串
25+
{"empty string", "", 64, ""},
26+
{"only spaces", " ", 64, ""},
27+
{"with space", "user 123", 64, ""},
28+
{"with special char @", "user@123", 64, ""},
29+
{"with special char !", "user!123", 64, ""},
30+
{"with special char #", "user#123", 64, ""},
31+
{"with special char $", "user$123", 64, ""},
32+
{"with special char %", "user%123", 64, ""},
33+
{"with special char &", "user&123", 64, ""},
34+
{"with special char *", "user*123", 64, ""},
35+
{"with dot", "user.123", 64, ""},
36+
{"with plus", "user+123", 64, ""},
37+
{"with equals", "user=123", 64, ""},
38+
{"with slash", "user/123", 64, ""},
39+
{"with backslash", "user\\123", 64, ""},
40+
{"with colon", "user:123", 64, ""},
41+
{"with semicolon", "user;123", 64, ""},
42+
{"with comma", "user,123", 64, ""},
43+
{"with question", "user?123", 64, ""},
44+
{"with brackets", "user[123]", 64, ""},
45+
{"with parentheses", "user(123)", 64, ""},
46+
{"with braces", "user{123}", 64, ""},
47+
{"with angle brackets", "user<123>", 64, ""},
48+
{"with pipe", "user|123", 64, ""},
49+
{"with tilde", "user~123", 64, ""},
50+
{"with backtick", "user`123", 64, ""},
51+
{"with quote", "user'123", 64, ""},
52+
{"with double quote", "user\"123", 64, ""},
53+
{"exceeds max length", "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZx", 62, ""},
54+
{"control char tab", "user\t123", 64, ""},
55+
{"control char newline", "user\n123", 64, ""},
56+
{"unicode chinese", "用户123", 64, ""},
57+
{"unicode emoji", "user😀123", 64, ""},
58+
59+
// 边界情况
60+
{"leading spaces", " user123", 64, "user123"},
61+
{"trailing spaces", "user123 ", 64, "user123"},
62+
{"both sides spaces", " user123 ", 64, "user123"},
63+
{"max length 1", "a", 1, "a"},
64+
{"exceeds max length 1", "ab", 1, ""},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
got := normalizeClientID(tt.input, tt.maxLen)
70+
if got != tt.expected {
71+
t.Errorf("normalizeClientID(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.expected)
72+
}
73+
})
74+
}
75+
}
76+
77+
func TestNormalizeRoomName(t *testing.T) {
78+
tests := []struct {
79+
name string
80+
input string
81+
maxLen int
82+
expected string
83+
}{
84+
// 有效情况
85+
{"simple room", "room1", 64, "room1"},
86+
{"with space", "room 1", 64, "room 1"},
87+
{"with hyphen", "room-1", 64, "room-1"},
88+
{"with underscore", "room_1", 64, "room_1"},
89+
{"with dot", "room.1", 64, "room.1"},
90+
{"chinese characters", "房间一", 64, "房间一"},
91+
{"mixed language", "团队 room.1", 64, "团队 room.1"},
92+
{"single char", "a", 64, "a"},
93+
{"exact max length", "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", 62, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"},
94+
{"special chars allowed", "room-1_v2.0", 64, "room-1_v2.0"},
95+
96+
// 无效情况 - 应返回空字符串
97+
{"empty string", "", 64, ""},
98+
{"only spaces", " ", 64, ""},
99+
{"exceeds max length", "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZx", 62, ""},
100+
{"control char tab", "room\t1", 64, ""},
101+
{"control char newline", "room\n1", 64, ""},
102+
{"control char carriage return", "room\r1", 64, ""},
103+
{"control char null", "room\x001", 64, ""},
104+
{"control char bell", "room\x071", 64, ""},
105+
{"control char escape", "room\x1b1", 64, ""},
106+
107+
// 边界情况
108+
{"leading spaces", " room1", 64, "room1"},
109+
{"trailing spaces", "room1 ", 64, "room1"},
110+
{"both sides spaces", " room1 ", 64, "room1"},
111+
{"max length 1", "a", 1, "a"},
112+
{"exceeds max length 1", "ab", 1, ""},
113+
{"emoji allowed", "room😀1", 64, "room😀1"},
114+
}
115+
116+
for _, tt := range tests {
117+
t.Run(tt.name, func(t *testing.T) {
118+
got := normalizeRoomName(tt.input, tt.maxLen)
119+
if got != tt.expected {
120+
t.Errorf("normalizeRoomName(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.expected)
121+
}
122+
})
123+
}
124+
}
125+
126+
func TestNormalizeClientIDConsistency(t *testing.T) {
127+
// 测试多次调用结果一致
128+
input := "user_123-ABC"
129+
for i := 0; i < 10; i++ {
130+
got := normalizeClientID(input, 64)
131+
if got != input {
132+
t.Errorf("normalizeClientID inconsistent on iteration %d: got %q, want %q", i, got, input)
133+
}
134+
}
135+
}
136+
137+
func TestNormalizeRoomNameConsistency(t *testing.T) {
138+
// 测试多次调用结果一致
139+
input := "团队 room.1"
140+
for i := 0; i < 10; i++ {
141+
got := normalizeRoomName(input, 64)
142+
if got != input {
143+
t.Errorf("normalizeRoomName inconsistent on iteration %d: got %q, want %q", i, got, input)
144+
}
145+
}
146+
}
147+
148+
func TestNormalizeClientIDWithMaxClientIDLength(t *testing.T) {
149+
// 测试使用实际常量 MaxClientIDLength
150+
tests := []struct {
151+
name string
152+
input string
153+
expected string
154+
}{
155+
{"valid id", "user123", "user123"},
156+
{"too long", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ""}, // 65 chars
157+
}
158+
159+
for _, tt := range tests {
160+
t.Run(tt.name, func(t *testing.T) {
161+
got := normalizeClientID(tt.input, MaxClientIDLength)
162+
if got != tt.expected {
163+
t.Errorf("normalizeClientID(%q, MaxClientIDLength) = %q, want %q", tt.input, got, tt.expected)
164+
}
165+
})
166+
}
167+
}
168+
169+
func TestNormalizeRoomNameWithMaxRoomIDLength(t *testing.T) {
170+
// 测试使用实际常量 MaxRoomIDLength
171+
tests := []struct {
172+
name string
173+
input string
174+
expected string
175+
}{
176+
{"valid room", "room1", "room1"},
177+
{"too long", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ""}, // 65 chars
178+
}
179+
180+
for _, tt := range tests {
181+
t.Run(tt.name, func(t *testing.T) {
182+
got := normalizeRoomName(tt.input, MaxRoomIDLength)
183+
if got != tt.expected {
184+
t.Errorf("normalizeRoomName(%q, MaxRoomIDLength) = %q, want %q", tt.input, got, tt.expected)
185+
}
186+
})
187+
}
188+
}

scripts/check-protocol-sync.sh

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ NC='\033[0m' # No Color
2525
echo "检查协议定义一致性..."
2626
echo ""
2727

28-
# 从 YAML 提取消息类型 enum(Message schema 下的 type.enum,行 53-64
28+
# 从 YAML 提取 Message.type enum(通过锚定 "Message:" schema 下的 type.enum
2929
extract_yaml_message_types() {
30-
sed -n '53,64p' "$YAML_FILE" | \
30+
sed -n '/^ Message:/,/^ [A-Z]/p' "$YAML_FILE" | \
31+
sed -n '/^ enum:/,/^ [a-z]/p' | \
3132
grep -E '^\s+-\s+\w+' | \
3233
sed 's/^\s*-\s*//' | sort -u
3334
}
3435

35-
# 从 YAML 提取错误码 enum(Error schema 下的 code.enum,行 95-110
36+
# 从 YAML 提取 Error.code enum(通过锚定 "Error:" schema 下的 code.enum
3637
extract_yaml_error_codes() {
37-
sed -n '95,110p' "$YAML_FILE" | \
38+
sed -n '/^ Error:/,/^ [A-Z]/p' "$YAML_FILE" | \
39+
sed -n '/^ enum:/,/^ [a-z]/p' | \
3840
grep -E '^\s+-\s+\w+' | \
3941
sed 's/^\s*-\s*//' | sort -u
4042
}

web/src/browserApi.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
* @returns {Object} 浏览器 API 接口
1212
*/
1313
export function createBrowserApi(overrides) {
14-
var merged = overrides || {};
15-
var api = {
14+
const merged = overrides || {};
15+
const api = {
1616
/**
1717
* 创建 WebSocket 连接
1818
* @param {string} url - WebSocket URL

web/src/controllers/stats.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export function createStatsController(appState) {
2-
var pollInterval = null;
2+
let pollInterval = null;
33
// Use WeakMap to automatically clean up when peer connections are closed
4-
var prevStats = new WeakMap();
4+
let prevStats = new WeakMap();
55

66
// 获取子状态引用
77
const peers = appState.peers;
@@ -12,7 +12,7 @@ export function createStatsController(appState) {
1212
}
1313

1414
function extractOutboundVideo(report) {
15-
for (var entry of report.values()) {
15+
for (const entry of report.values()) {
1616
if (entry.type === 'outbound-rtp' && entry.kind === 'video') {
1717
return entry;
1818
}
@@ -21,7 +21,7 @@ export function createStatsController(appState) {
2121
}
2222

2323
function extractCandidatePair(report) {
24-
for (var entry of report.values()) {
24+
for (const entry of report.values()) {
2525
if (entry.type === 'candidate-pair' && entry.state === 'succeeded') {
2626
return entry;
2727
}
@@ -30,7 +30,7 @@ export function createStatsController(appState) {
3030
}
3131

3232
function extractInboundRtp(report, kind) {
33-
for (var entry of report.values()) {
33+
for (const entry of report.values()) {
3434
if (entry.type === 'inbound-rtp' && entry.kind === kind) {
3535
return entry;
3636
}
@@ -39,7 +39,7 @@ export function createStatsController(appState) {
3939
}
4040

4141
function extractTrack(report, kind) {
42-
for (var entry of report.values()) {
42+
for (const entry of report.values()) {
4343
if (entry.type === 'track' && entry.kind === kind) {
4444
return entry;
4545
}
@@ -49,14 +49,14 @@ export function createStatsController(appState) {
4949

5050
function computeStats(pc) {
5151
return pc.getStats().then(function (report) {
52-
var video = extractOutboundVideo(report);
53-
var audioIn = extractInboundRtp(report, 'audio');
54-
var videoIn = extractTrack(report, 'video');
55-
var pair = extractCandidatePair(report);
56-
var now = Date.now();
57-
var prev = prevStats.get(pc) || {};
58-
59-
var result = {
52+
const video = extractOutboundVideo(report);
53+
const audioIn = extractInboundRtp(report, 'audio');
54+
const videoIn = extractTrack(report, 'video');
55+
const pair = extractCandidatePair(report);
56+
const now = Date.now();
57+
const prev = prevStats.get(pc) || {};
58+
59+
const result = {
6060
videoBitrate: '--',
6161
audioLoss: '--',
6262
rtt: '--',
@@ -65,16 +65,16 @@ export function createStatsController(appState) {
6565
};
6666

6767
if (video) {
68-
var delta = now - (prev.timestamp || now);
69-
var bytesDelta = (video.bytesSent || 0) - (prev.bytesSent || 0);
68+
const delta = now - (prev.timestamp || now);
69+
const bytesDelta = (video.bytesSent || 0) - (prev.bytesSent || 0);
7070
if (delta > 0 && bytesDelta > 0) {
7171
result.videoBitrate = formatBitrate(bytesDelta, delta);
7272
}
7373
if (video.frameWidth && video.frameHeight) {
7474
result.resolution = video.frameWidth + 'x' + video.frameHeight;
7575
}
7676
if (video.codecId) {
77-
for (var entry of report.values()) {
77+
for (const entry of report.values()) {
7878
if (entry.id === video.codecId && entry.mimeType) {
7979
result.codec = entry.mimeType.replace('video/', '');
8080
break;
@@ -84,8 +84,8 @@ export function createStatsController(appState) {
8484
}
8585

8686
if (audioIn) {
87-
var total = audioIn.packetsReceived || 0;
88-
var lost = audioIn.packetsLost || 0;
87+
const total = audioIn.packetsReceived || 0;
88+
const lost = audioIn.packetsLost || 0;
8989
if (total > 0) {
9090
result.audioLoss = (lost / total * 100).toFixed(1) + '%';
9191
}

web/src/state/mediaState.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import { createObservable } from './observable.js';
1010
* @returns {Object} 媒体状态接口
1111
*/
1212
export function createMediaState() {
13-
var observable = createObservable();
13+
const observable = createObservable();
1414

1515
// 私有状态
16-
var _localStream = null;
17-
var _screenStream = null;
18-
var _usingScreen = false;
19-
var _muted = false;
20-
var _cameraOff = false;
21-
var _recorder = null;
22-
var _recordedChunks = [];
16+
let _localStream = null;
17+
let _screenStream = null;
18+
let _usingScreen = false;
19+
let _muted = false;
20+
let _cameraOff = false;
21+
let _recorder = null;
22+
let _recordedChunks = [];
2323

2424
/**
2525
* 获取当前状态快照

web/src/state/observable.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
* @returns {Object} 包含 subscribe 和 notify 方法的对象
1010
*/
1111
export function createObservable() {
12-
var subscribers = new Set();
13-
var notifying = false;
12+
let subscribers = new Set();
13+
let notifying = false;
1414

1515
/**
1616
* 订阅状态变化

web/src/state/peersState.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { createObservable } from './observable.js';
1010
* @returns {Object} Peer 状态接口
1111
*/
1212
export function createPeersState() {
13-
var observable = createObservable();
13+
const observable = createObservable();
1414

1515
// 私有状态
16-
var _peers = new Map();
16+
const _peers = new Map();
1717

1818
/**
1919
* 获取当前状态快照
@@ -32,7 +32,7 @@ export function createPeersState() {
3232
function set(peerId, peer) { _peers.set(peerId, peer); observable.notify(); }
3333
function has(peerId) { return _peers.has(peerId); }
3434
function remove(peerId) {
35-
var result = _peers.delete(peerId);
35+
const result = _peers.delete(peerId);
3636
if (result) observable.notify();
3737
return result;
3838
}

0 commit comments

Comments
 (0)