guanghulab/scripts/notion-connectivity-test.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

327 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scripts/notion-connectivity-test.js
// 铸渊 → Notion 连通性测试YM-TEST-20260307-001
//
// 用法:
// node scripts/notion-connectivity-test.js
//
// 必需环境变量:
// NOTION_TOKEN Notion Integration TokenGitHub Secret
//
// 可选环境变量(有内置默认值):
// SIGNAL_LOG_DB_ID 「📡 跨平台信号日志」database_id
// CHANGES_DB_ID 「📋 GitHub 变更日志」database_id默认已内置
// SYSLOG_DB_ID 「📥 GitHub SYSLOG 收件箱」database_id默认已内置
'use strict';
const https = require('https');
const fs = require('fs');
const path = require('path');
// ══════════════════════════════════════════════════════════
// 常量
// ══════════════════════════════════════════════════════════
const NOTION_VERSION = '2022-06-28';
const NOTION_API_HOSTNAME = 'api.notion.com';
// Notion 数据库 ID默认值与 notion-bridge.js / notion-signal-bridge.js 一致)
const DEFAULT_SYSLOG_DB_ID = '330ab17507d542c9bbb96d0749b41197';
const DEFAULT_CHANGES_DB_ID = 'e740b77aa6bd4ac0a2e8a75f678fba98';
const SIGNAL_LOG_DIR = path.join(__dirname, '../signal-log');
const SIGNAL_INDEX_PATH = path.join(SIGNAL_LOG_DIR, 'index.json');
// ══════════════════════════════════════════════════════════
// Notion API 基础调用(复用 notion-bridge.js 模式)
// ══════════════════════════════════════════════════════════
function notionPost(endpoint, body, token) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(body);
const opts = {
hostname: NOTION_API_HOSTNAME,
port: 443,
path: endpoint,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Notion-Version': NOTION_VERSION,
'Content-Length': Buffer.byteLength(payload),
},
};
const req = https.request(opts, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(data);
resolve({ statusCode: res.statusCode, body: parsed });
} catch (e) {
resolve({ statusCode: res.statusCode, body: null, raw: data });
}
});
});
req.on('error', reject);
req.setTimeout(30000, () => { req.destroy(new Error('Notion API 请求超时')); });
req.write(payload);
req.end();
});
}
// ══════════════════════════════════════════════════════════
// 本地信号日志(复用 notion-signal-bridge.js 模式)
// ══════════════════════════════════════════════════════════
function loadSignalIndex() {
if (!fs.existsSync(SIGNAL_INDEX_PATH)) {
return {
description: '铸渊信号日志目录索引 · AGE OS 信号协议Notion API 直连)',
last_updated: new Date().toISOString(),
total_count: 0,
signals: []
};
}
return JSON.parse(fs.readFileSync(SIGNAL_INDEX_PATH, 'utf8'));
}
function writeLocalSignalLog(signal) {
const dateStr = signal.timestamp.slice(0, 7);
const monthDir = path.join(SIGNAL_LOG_DIR, dateStr);
fs.mkdirSync(monthDir, { recursive: true });
const filePath = path.join(monthDir, `${signal.signal_id}.json`);
fs.writeFileSync(filePath, JSON.stringify(signal, null, 2));
const index = loadSignalIndex();
index.total_count += 1;
index.signals.unshift({
signal_id: signal.signal_id,
trace_id: signal.trace_id,
type: signal.signal_type,
timestamp: signal.timestamp,
summary: signal.summary,
related_dev: null,
file: `${dateStr}/${signal.signal_id}.json`
});
index.last_updated = new Date().toISOString();
fs.writeFileSync(SIGNAL_INDEX_PATH, JSON.stringify(index, null, 2));
}
// ══════════════════════════════════════════════════════════
// 三项连通性测试
// ══════════════════════════════════════════════════════════
async function testSignalLog(token, dbId) {
return notionPost('/v1/pages', {
parent: { database_id: dbId },
properties: {
'信号编号': { title: [{ text: { content: 'SIG-TEST-20260307-001' } }] },
'信号类型': { select: { name: 'GL-ACK' } },
'发送方': { select: { name: '铸渊' } },
'接收方': { select: { name: '霜砚' } },
'方向': { select: { name: 'GitHub→Notion' } },
'关联DEV': { select: { name: '系统' } },
'关联模块': { select: { name: '全局' } },
'执行结果': { status: { name: '成功' } },
'摘要': { rich_text: [{ text: { content: '🧪 铸渊→Notion API 连通性测试 · NOTION_TOKEN 验证 · 数据桥首次握手' } }] },
'备注': { rich_text: [{ text: { content: '这是一条自动化测试信号。如果你在 Notion 信号日志里看到这条记录,说明 GitHub→Notion 数据桥已打通。' } }] }
}
}, token);
}
async function testChangeLog(token, dbId) {
return notionPost('/v1/pages', {
parent: { database_id: dbId },
properties: {
'标题': { title: [{ text: { content: '🧪 连通性测试 · 铸渊首次握手 · 2026-03-07' } }] },
'变更类型': { select: { name: 'Commit' } },
'提交者': { select: { name: '铸渊' } },
'分支': { rich_text: [{ text: { content: 'main' } }] },
'commit_sha': { rich_text: [{ text: { content: 'test-00000000' } }] },
'变更文件': { rich_text: [{ text: { content: '.github/test/bridge-connectivity-test.md' } }] },
'霜砚已读': { checkbox: false }
}
}, token);
}
async function testSyslogInbox(token, dbId) {
return notionPost('/v1/pages', {
parent: { database_id: dbId },
properties: {
'标题': { title: [{ text: { content: '🧪 SYSLOG连通性测试 · 2026-03-07' } }] },
'DEV编号': { select: { name: 'DEV-001' } },
'处理状态': { status: { name: '待处理' } },
'推送方': { rich_text: [{ text: { content: '铸渊' } }] },
'来源路径': { rich_text: [{ text: { content: 'syslog-inbox/test/bridge-test-20260307.md' } }] },
'文件内容': { rich_text: [{ text: { content: '这是一条连通性测试SYSLOG。如果霜砚能在收件箱看到这条说明铸渊→Notion SYSLOG推送管线已打通。' } }] },
'commit_sha': { rich_text: [{ text: { content: 'test-00000000' } }] }
}
}, token);
}
// ══════════════════════════════════════════════════════════
// 主执行
// ══════════════════════════════════════════════════════════
async function main() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('🧪 YM-TEST-20260307-001 · 铸渊→Notion 连通性测试');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
const token = process.env.NOTION_TOKEN;
if (!token) {
console.error('❌ NOTION_TOKEN 未设置,无法执行连通性测试');
console.error(' 请在 GitHub Secrets 中配置 NOTION_TOKEN');
process.exit(1);
}
console.log('✅ NOTION_TOKEN 已检测到');
const signalLogDbId = process.env.SIGNAL_LOG_DB_ID || '';
const changesDbId = process.env.CHANGES_DB_ID || DEFAULT_CHANGES_DB_ID;
const syslogDbId = process.env.SYSLOG_DB_ID || DEFAULT_SYSLOG_DB_ID;
const results = {
signalLog: { ok: false, pageId: null, error: null },
changeLog: { ok: false, pageId: null, error: null },
syslogInbox: { ok: false, pageId: null, error: null },
};
// ── 测试一:信号日志 ──────────────────────────────────
console.log('\n── 测试一:写入「📡 跨平台信号日志」──');
if (!signalLogDbId) {
console.log('⚠️ SIGNAL_LOG_DB_ID 未配置,跳过此测试');
results.signalLog.error = 'SIGNAL_LOG_DB_ID 未配置';
} else {
console.log(` Database ID: ${signalLogDbId}`);
try {
const res = await testSignalLog(token, signalLogDbId);
if (res.statusCode >= 200 && res.statusCode < 300) {
results.signalLog.ok = true;
results.signalLog.pageId = res.body.id;
console.log(` ✅ 写入成功 · page_id: ${res.body.id}`);
} else {
results.signalLog.error = `HTTP ${res.statusCode}: ${res.body?.message || JSON.stringify(res.body)}`;
console.log(` ❌ 写入失败 · ${results.signalLog.error}`);
}
} catch (err) {
results.signalLog.error = err.message;
console.log(` ❌ 请求异常 · ${err.message}`);
}
}
// ── 测试二:变更日志 ──────────────────────────────────
console.log('\n── 测试二:写入「📋 GitHub 变更日志」──');
console.log(` Database ID: ${changesDbId}`);
try {
const res = await testChangeLog(token, changesDbId);
if (res.statusCode >= 200 && res.statusCode < 300) {
results.changeLog.ok = true;
results.changeLog.pageId = res.body.id;
console.log(` ✅ 写入成功 · page_id: ${res.body.id}`);
} else {
results.changeLog.error = `HTTP ${res.statusCode}: ${res.body?.message || JSON.stringify(res.body)}`;
console.log(` ❌ 写入失败 · ${results.changeLog.error}`);
}
} catch (err) {
results.changeLog.error = err.message;
console.log(` ❌ 请求异常 · ${err.message}`);
}
// ── 测试三SYSLOG 收件箱 ─────────────────────────────
console.log('\n── 测试三:写入「📥 GitHub SYSLOG 收件箱」──');
console.log(` Database ID: ${syslogDbId}`);
try {
const res = await testSyslogInbox(token, syslogDbId);
if (res.statusCode >= 200 && res.statusCode < 300) {
results.syslogInbox.ok = true;
results.syslogInbox.pageId = res.body.id;
console.log(` ✅ 写入成功 · page_id: ${res.body.id}`);
} else {
results.syslogInbox.error = `HTTP ${res.statusCode}: ${res.body?.message || JSON.stringify(res.body)}`;
console.log(` ❌ 写入失败 · ${results.syslogInbox.error}`);
}
} catch (err) {
results.syslogInbox.error = err.message;
console.log(` ❌ 请求异常 · ${err.message}`);
}
// ── 写入本地信号日志 ──────────────────────────────────
const now = new Date();
const allPassed = results.signalLog.ok && results.changeLog.ok && results.syslogInbox.ok;
const signalLogSkipped = !signalLogDbId;
const partialPassed = results.changeLog.ok || results.syslogInbox.ok || results.signalLog.ok;
let bridgeStatus;
if (allPassed) {
bridgeStatus = '全通';
} else if (signalLogSkipped && results.changeLog.ok && results.syslogInbox.ok) {
bridgeStatus = '全通信号日志DB未配置其余全通';
} else if (partialPassed) {
bridgeStatus = '部分通';
} else {
bridgeStatus = '不通';
}
const signal = {
signal_id: 'SIG-TEST-20260307-001',
trace_id: 'TRC-TEST-20260307-YM001',
timestamp: now.toISOString(),
signal_type: 'GL-ACK',
direction: 'GitHub→Notion',
sender: '铸渊',
receiver: '霜砚',
summary: `🧪 连通性测试完成 · 数据桥状态 = ${bridgeStatus}`,
result: allPassed || (signalLogSkipped && results.changeLog.ok && results.syslogInbox.ok) ? '成功' : '失败',
payload: {
test_id: 'YM-TEST-20260307-001',
signal_log: results.signalLog,
change_log: results.changeLog,
syslog_inbox: results.syslogInbox,
bridge_status: bridgeStatus
}
};
try {
writeLocalSignalLog(signal);
console.log(`\n📝 本地信号日志已写入: ${signal.signal_id}`);
} catch (err) {
console.error(`\n⚠️ 本地信号日志写入失败: ${err.message}`);
}
// ── 输出汇总 ──────────────────────────────────────────
const formatResult = (r) => r.ok ? `page_id: ${r.pageId}` : `❌(${r.error}`;
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('✅ YM-TEST-20260307-001 连通性测试完成');
console.log(`· 信号日志写入:${signalLogSkipped ? '⚠️ 跳过SIGNAL_LOG_DB_ID 未配置)' : formatResult(results.signalLog)}`);
console.log(`· 变更日志写入:${formatResult(results.changeLog)}`);
console.log(`· SYSLOG收件箱写入${formatResult(results.syslogInbox)}`);
console.log(`· 总结:数据桥状态 = ${bridgeStatus}`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
// GitHub Actions: 输出到 GITHUB_OUTPUT如有
if (process.env.GITHUB_OUTPUT) {
const outputLines = [
`signal_log_ok=${results.signalLog.ok}`,
`change_log_ok=${results.changeLog.ok}`,
`syslog_inbox_ok=${results.syslogInbox.ok}`,
`bridge_status=${bridgeStatus}`,
];
fs.appendFileSync(process.env.GITHUB_OUTPUT, outputLines.join('\n') + '\n');
}
// 如果没有全通(且不是因为跳过信号日志),以非零退出码结束
if (!allPassed && !(signalLogSkipped && results.changeLog.ok && results.syslogInbox.ok)) {
process.exit(1);
}
}
main().catch(err => {
console.error('❌ 连通性测试异常退出:', err.message);
process.exit(1);
});