test: 建立桌面端自动化回归测试体系并完善跨平台 CI

(cherry picked from commit 74267ae2f63f8256c3da84b04f5b330e6e9d4c67)
This commit is contained in:
电摇小子
2026-08-03 10:08:28 +08:00
committed by Wxw-Gu
parent b4f909a597
commit 3e57a8432d
36 changed files with 2598 additions and 110 deletions
+2 -1
View File
@@ -13,6 +13,7 @@ const sanitize = (value: unknown, depth = 0): unknown => {
return value
.replace(/\bsk-[a-z0-9_-]{8,}\b/gi, '***')
.replace(/\bBearer\s+[a-z0-9._~-]{8,}\b/gi, 'Bearer ***')
.replace(/\b(?:0x)?[a-f0-9]{64}\b/gi, '***')
.slice(0, 2000)
}
if (Array.isArray(value)) return value.slice(0, 30).map((item) => sanitize(item, depth + 1))
@@ -56,7 +57,7 @@ export class AppLogger {
mode: isPackagedRuntime() ? 'packaged' : 'development',
level: entry.level,
scope: String(entry.scope || 'app').slice(0, 80),
message: String(entry.message || '').slice(0, 500),
message: String(sanitize(entry.message || '')).slice(0, 500),
details: sanitize(entry.details || {})
}
fs.appendFileSync(this.logPath, `${JSON.stringify(record)}\n`, { encoding: 'utf8' })
+11 -15
View File
@@ -203,9 +203,11 @@ function getMimeType(extension) {
function strictRemovePadding(buffer) {
if (buffer.length === 0) return buffer
const paddingLength = buffer[buffer.length - 1]
if (paddingLength <= 0 || paddingLength > 16 || paddingLength > buffer.length) return buffer
if (paddingLength <= 0 || paddingLength > 16 || paddingLength > buffer.length) {
throw new Error('invalid PKCS#7 padding')
}
for (let index = buffer.length - paddingLength; index < buffer.length; index += 1) {
if (buffer[index] !== paddingLength) return buffer
if (buffer[index] !== paddingLength) throw new Error('invalid PKCS#7 padding')
}
return buffer.subarray(0, buffer.length - paddingLength)
}
@@ -1636,19 +1638,13 @@ export class ImageDecryptService {
private strictRemovePadding(buffer: Buffer): Buffer {
if (buffer.length === 0) return buffer
const lastByte = buffer[buffer.length - 1]
if (lastByte <= 16 && lastByte > 0) {
const paddingLength = lastByte
let valid = true
for (let i = buffer.length - paddingLength; i < buffer.length; i++) {
if (buffer[i] !== lastByte) {
valid = false
break
}
}
if (valid) {
return buffer.subarray(0, buffer.length - paddingLength)
}
if (lastByte <= 0 || lastByte > 16 || lastByte > buffer.length) {
throw new Error('invalid PKCS#7 padding')
}
return buffer
const paddingLength = lastByte
for (let i = buffer.length - paddingLength; i < buffer.length; i += 1) {
if (buffer[i] !== lastByte) throw new Error('invalid PKCS#7 padding')
}
return buffer.subarray(0, buffer.length - paddingLength)
}
}
+5 -19
View File
@@ -24,6 +24,11 @@ import { FirstUseWelcome } from './components/FirstUseWelcome'
import { ExportWorkspace } from './components/export/ExportWorkspace'
import { AISearchWorkspace } from './components/search/AISearchWorkspace'
import type { ExportJobProgress, ExportRequest, ExportTaskRecord } from '../../shared/export'
import {
getMessageIdentity,
mergeMessagePages,
sortMessagesChronologically
} from './utils/message-pages'
const SIDEBAR_MIN_WIDTH = 260
const SIDEBAR_MAX_WIDTH = 380
@@ -47,12 +52,6 @@ const INITIAL_MESSAGE_COUNT = 20
const MESSAGE_PAGE_SIZE = 100
const MESSAGE_PREFETCH_COUNT = INITIAL_MESSAGE_COUNT + MESSAGE_PAGE_SIZE
const EXPORT_PREVIEW_LIMIT = 20
const getMessageIdentity = (message: Message): string => {
if (message.localId) return `local:${message.localId}`
if (message.id) return `id:${message.id}`
return `${message.createTime || 0}:${message.from}:${message.type}:${message.content}`
}
const normalizeQuotedText = (value: string | undefined): string =>
String(value || '')
.replace(/\s+/g, ' ')
@@ -194,19 +193,6 @@ const buildSyntheticGroupMessages = (
void buildSyntheticGroupMessages
const sortMessagesChronologically = (items: Message[]): Message[] =>
[...items].sort((left, right) => {
const timeDelta = (left.createTime || 0) - (right.createTime || 0)
if (timeDelta !== 0) return timeDelta
return getMessageIdentity(left).localeCompare(getMessageIdentity(right))
})
const mergeMessagePages = (older: Message[], current: Message[]): Message[] => {
const merged = new Map<string, Message>()
for (const message of [...older, ...current]) merged.set(getMessageIdentity(message), message)
return sortMessagesChronologically(Array.from(merged.values()))
}
function App(): React.ReactElement {
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isDatabaseConnected, setIsDatabaseConnected] = useState(false)
+20
View File
@@ -0,0 +1,20 @@
import type { Message } from '../../../shared/types'
export const getMessageIdentity = (message: Message): string => {
if (message.localId) return `local:${message.localId}`
if (message.id) return `id:${message.id}`
return `${message.createTime || 0}:${message.from}:${message.type}:${message.content}`
}
export const sortMessagesChronologically = (items: Message[]): Message[] =>
[...items].sort((left, right) => {
const timeDelta = (left.createTime || 0) - (right.createTime || 0)
if (timeDelta !== 0) return timeDelta
return getMessageIdentity(left).localeCompare(getMessageIdentity(right))
})
export const mergeMessagePages = (older: Message[], current: Message[]): Message[] => {
const merged = new Map<string, Message>()
for (const message of [...older, ...current]) merged.set(getMessageIdentity(message), message)
return sortMessagesChronologically(Array.from(merged.values()))
}