guanghulab/scripts/notion-connectivity-test.js

327 lines
15 KiB
JavaScript
Raw Permalink Normal View History

// 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);
});