133 lines
5.0 KiB
JavaScript
133 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
import crypto from 'node:crypto';
|
||
|
||
const baseUrl = process.env.AIHR_BASE_URL || 'http://127.0.0.1:8080';
|
||
const phone = process.env.AIHR_VERIFY_PHONE || '13900000001';
|
||
const smsCode = process.env.AIHR_VERIFY_SMS_CODE || '123456';
|
||
const clientId = '428a8310cd442757ae699df5d894f051';
|
||
|
||
async function request(endpoint, { method = 'GET', body, token } = {}) {
|
||
const response = await fetch(`${baseUrl}${endpoint}`, {
|
||
method,
|
||
headers: {
|
||
clientid: clientId,
|
||
'Content-Language': 'zh_CN',
|
||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||
...(body ? { 'Content-Type': 'application/json;charset=utf-8' } : {})
|
||
},
|
||
body: body ? JSON.stringify(body) : undefined,
|
||
signal: AbortSignal.timeout(30_000)
|
||
});
|
||
const text = await response.text();
|
||
let payload;
|
||
try {
|
||
payload = JSON.parse(text);
|
||
} catch {
|
||
throw new Error(`${endpoint} 返回了非 JSON 响应(HTTP ${response.status})`);
|
||
}
|
||
if (!response.ok || Number(payload.code) !== 200) {
|
||
throw new Error(`${endpoint} 失败:HTTP ${response.status} / ${payload.code} ${payload.msg || ''}`);
|
||
}
|
||
return payload.data;
|
||
}
|
||
|
||
function assert(condition, message) {
|
||
if (!condition) throw new Error(message);
|
||
}
|
||
|
||
await request(`/resource/sms/code?phonenumber=${encodeURIComponent(phone)}`);
|
||
const login = await request('/auth/mobile/sms-login', {
|
||
method: 'POST',
|
||
body: { phonenumber: phone, smsCode, tenantId: '000000' }
|
||
});
|
||
const token = login?.access_token;
|
||
assert(token, '手机号登录未返回 access_token');
|
||
|
||
const conversationId = crypto.randomUUID();
|
||
const first = await request('/api/knowledge/query', {
|
||
method: 'POST',
|
||
token,
|
||
body: {
|
||
queryText: '报修受理后第一步应该做什么?',
|
||
category: 'sop',
|
||
position: '生活顾问',
|
||
limit: 5,
|
||
conversationId,
|
||
contextVersion: 0
|
||
}
|
||
});
|
||
assert(first?.conversationId === conversationId, '首问没有返回同一 conversationId');
|
||
assert(Number(first?.contextVersion) === 1, '首问 contextVersion 未从 0 递增到 1');
|
||
assert(first?.intent === 'QA', `首问意图错误:${first?.intent}`);
|
||
assert(!first?.noEvidence, '首问未召回已授权的报修依据');
|
||
|
||
const followup = await request('/api/knowledge/query', {
|
||
method: 'POST',
|
||
token,
|
||
body: {
|
||
queryText: '把对应原文件给我',
|
||
category: 'sop',
|
||
position: '生活顾问',
|
||
limit: 5,
|
||
conversationId,
|
||
contextVersion: first.contextVersion
|
||
}
|
||
});
|
||
assert(followup?.conversationId === conversationId, '追问没有保持同一 conversationId');
|
||
assert(Number(followup?.contextVersion) === 2, '追问 contextVersion 未从 1 递增到 2');
|
||
assert(followup?.intent === 'FILE', `追问意图错误:${followup?.intent}`);
|
||
assert(String(followup?.rewrittenQuery || '').includes('报修'), '追问没有结合首问主题重写');
|
||
assert(Array.isArray(followup?.resources) && followup.resources.length > 0, '追问未返回授权原文件');
|
||
|
||
const resource = followup.resources[0];
|
||
assert(resource?.contentUrl?.startsWith('/api/knowledge/resources/'), '原文件没有使用受保护内容地址');
|
||
const download = await fetch(`${baseUrl}${resource.contentUrl}`, {
|
||
headers: { clientid: clientId, Authorization: `Bearer ${token}` },
|
||
signal: AbortSignal.timeout(30_000)
|
||
});
|
||
assert(download.ok, `原文件下载失败:HTTP ${download.status}`);
|
||
assert((await download.arrayBuffer()).byteLength > 0, '原文件下载内容为空');
|
||
|
||
const videoFollowup = await request('/api/knowledge/query', {
|
||
method: 'POST',
|
||
token,
|
||
body: {
|
||
queryText: '有对应操作视频吗?',
|
||
category: 'sop',
|
||
position: '生活顾问',
|
||
limit: 5,
|
||
conversationId,
|
||
contextVersion: followup.contextVersion
|
||
}
|
||
});
|
||
assert(Number(videoFollowup?.contextVersion) === 3, '视频追问 contextVersion 未递增到 3');
|
||
assert(videoFollowup?.intent === 'VIDEO', `视频追问意图错误:${videoFollowup?.intent}`);
|
||
assert(Array.isArray(videoFollowup?.resources) && videoFollowup.resources.length === 0, '无视频时返回了伪造资源');
|
||
assert(String(videoFollowup?.answer || '').includes('未找到'), '无视频时没有明确说明未找到');
|
||
|
||
const staleResponse = await fetch(`${baseUrl}/api/knowledge/query`, {
|
||
method: 'POST',
|
||
headers: {
|
||
clientid: clientId,
|
||
Authorization: `Bearer ${token}`,
|
||
'Content-Type': 'application/json;charset=utf-8'
|
||
},
|
||
body: JSON.stringify({
|
||
queryText: '再说一遍',
|
||
category: 'sop',
|
||
conversationId,
|
||
contextVersion: first.contextVersion
|
||
}),
|
||
signal: AbortSignal.timeout(30_000)
|
||
});
|
||
const stalePayload = await staleResponse.json();
|
||
assert(Number(stalePayload.code) === 409, `旧上下文版本未被拒绝:${stalePayload.code}`);
|
||
|
||
console.log('PASS 手机号真实登录');
|
||
console.log('PASS 首问召回有依据答案,contextVersion=1');
|
||
console.log(`PASS 追问识别 FILE 并重写为:${followup.rewrittenQuery}`);
|
||
console.log(`PASS 授权原文件下载:${resource.title}`);
|
||
console.log('PASS 视频追问无匹配时明确返回空资源');
|
||
console.log('PASS 旧 contextVersion 返回 409');
|