| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- #!/usr/bin/env node
- /**
- * 烟测:验证 skill-executor 的模板替换 + Authorization 注入是否正确
- * 读取某个 skill 的 api-config.json,按实际逻辑构造 headers/url,
- * 发一次真实请求,观察响应头部是否被后端识别。
- *
- * 用法: node scripts/tools/test-skill-auth.js <skill-dir>
- * 例: node scripts/tools/test-skill-auth.js social-media/instagram-search
- */
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const skillDir = process.argv[2];
- if (!skillDir) {
- console.log('用法: node scripts/tools/test-skill-auth.js <skill-dir>');
- process.exit(1);
- }
- // 复用 executor 的模板函数
- function resolveTemplate(template, vars = {}) {
- if (typeof template !== 'string') return template;
- return template
- .replace(/\$\{(\w+)\}/g, (_, key) => vars[key] ?? '')
- .replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '')
- .replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? '');
- }
- function resolveTemplateDeep(obj, vars = {}) {
- if (obj == null) return obj;
- if (typeof obj === 'string') return resolveTemplate(obj, vars);
- if (Array.isArray(obj)) return obj.map(v => resolveTemplateDeep(v, vars));
- if (typeof obj === 'object') {
- const out = {};
- for (const [k, v] of Object.entries(obj)) out[k] = resolveTemplateDeep(v, vars);
- return out;
- }
- return obj;
- }
- const configPath = path.resolve(__dirname, '..', '..', skillDir, 'api-config.json');
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
- console.log(`Skill: ${cfg.displayName || cfg.name}`);
- // 读取 token
- const credPath = path.join(os.homedir(), '.openclaw', 'voc-credentials.json');
- const cred = fs.existsSync(credPath) ? JSON.parse(fs.readFileSync(credPath, 'utf-8')) : {};
- const vocToken = cred.vocToken || '';
- console.log(`Token: ${vocToken ? vocToken.slice(0, 6) + '...' + vocToken.slice(-4) : '(无)'}`);
- if (!cfg.endpoint) {
- console.error('此 skill 不是直接 endpoint 型(可能是 orchestration pipeline),跳过');
- process.exit(0);
- }
- const vars = {
- vocToken, TIKHUB_TOKEN: vocToken, token: vocToken, sessionToken: vocToken,
- query: 'nike', select: 'users', keyword: 'nike', count: 1, cursor: 0,
- username: 'nike', user_id: '1234567890'
- };
- const headers = resolveTemplateDeep(cfg.endpoint.headers || {}, vars);
- const query = resolveTemplateDeep(cfg.endpoint.queryParams || cfg.requestTransform?.queryParams || {}, vars);
- const url = resolveTemplate(cfg.endpoint.url, vars);
- // 兜底 Authorization
- if (vocToken && !headers.Authorization && !headers.authorization) {
- headers.Authorization = `Bearer ${vocToken}`;
- }
- // 拼 URL
- let finalUrl = url;
- const qsEntries = Object.entries(query).filter(([, v]) => v !== undefined && v !== '');
- if (qsEntries.length > 0) {
- const qs = new URLSearchParams();
- qsEntries.forEach(([k, v]) => qs.append(k, String(v)));
- finalUrl += (finalUrl.includes('?') ? '&' : '?') + qs.toString();
- }
- console.log('');
- console.log('─── 构造后的请求 ───');
- console.log(`${cfg.endpoint.method || 'GET'} ${finalUrl}`);
- console.log('Headers:');
- for (const [k, v] of Object.entries(headers)) {
- if (k.toLowerCase() === 'authorization' && vocToken) {
- console.log(` ${k}: Bearer ${vocToken.slice(0, 6)}...${vocToken.slice(-4)}`);
- } else {
- console.log(` ${k}: ${v}`);
- }
- }
- // 发一次真实请求
- console.log('');
- console.log('─── 实际调用 ───');
- (async () => {
- const t0 = Date.now();
- try {
- const resp = await fetch(finalUrl, {
- method: cfg.endpoint.method || 'GET',
- headers,
- body: ['POST','PUT','PATCH'].includes((cfg.endpoint.method||'GET').toUpperCase()) ? JSON.stringify(cfg.endpoint.body || {}) : undefined
- });
- const ms = Date.now() - t0;
- const text = await resp.text();
- let data; try { data = JSON.parse(text); } catch { data = text.slice(0, 200); }
- console.log(`HTTP ${resp.status} (${ms}ms)`);
- console.log('Response:', typeof data === 'string' ? data : JSON.stringify(data).slice(0, 300));
- console.log('');
- console.log('─── 诊断 ───');
- const msg = (data?.mess || data?.message || data?.error || '');
- if (resp.ok) {
- console.log('✅ 完整链路通!token 有效 + 余额充足');
- } else if (msg.includes('当前用户不存在') || msg.includes('无使用权限') || data?.code === 209) {
- console.log('✅ 模板注入正确!后端收到了 Bearer token 并尝试解析用户');
- console.log(' (报错原因是 token 过期,不是注入失败)');
- } else if (msg.includes('请输入') || msg.includes('sessionToken') || msg.includes('API_KEY')) {
- console.log('❌ 模板注入失败!后端没有收到 Bearer token');
- } else {
- console.log('⚠️ 其他错误:', msg);
- }
- } catch (e) {
- console.error('请求异常:', e.message);
- }
- })();
|