Spaces:
Sleeping
Sleeping
| import assert from 'node:assert/strict'; | |
| import { createHmac, generateKeyPairSync } from 'node:crypto'; | |
| import { createServer } from 'node:http'; | |
| import test from 'node:test'; | |
| const DIST_ROOT = new URL('../dist/src/', import.meta.url); | |
| async function importFresh(relativePath) { | |
| return import(new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, DIST_ROOT).href); | |
| } | |
| function setEnv(updates) { | |
| const previous = new Map(); | |
| for (const [key, value] of Object.entries(updates)) { | |
| previous.set(key, process.env[key]); | |
| if (value === undefined || value === null) delete process.env[key]; | |
| else process.env[key] = value; | |
| } | |
| return () => { | |
| for (const [key, value] of previous.entries()) { | |
| if (value === undefined) delete process.env[key]; | |
| else process.env[key] = value; | |
| } | |
| }; | |
| } | |
| test('GitHub Agent help exposes routing actions', async () => { | |
| const { executeGitHubAgentAction } = await importFresh('./runtime.js'); | |
| const help = await executeGitHubAgentAction({ action: 'agent.help' }); | |
| assert.equal(help.ok, true); | |
| assert.ok(help.actions.includes('sin.github.app.routing.status')); | |
| assert.ok(help.actions.includes('sin.github.webhook.route')); | |
| assert.ok(help.actions.includes('sin.github.issue.comment.as_app')); | |
| }); | |
| test('routeGitHubWebhook prefers installation.app_id over shared central path', async () => { | |
| const restoreEnv = setEnv({ | |
| SIN_GITHUB_APP_ROUTING_JSON: JSON.stringify({ | |
| mode: 'central-endpoint-routing', | |
| defaultWebhookPath: '/api/webhooks/github-apps', | |
| apps: [ | |
| { | |
| appIdEnv: 'SIN_HERMES_GITHUB_APP_ID', | |
| appSlug: 'sin-hermes', | |
| preferredAgentSlug: 'sin-hermesbote', | |
| botName: 'SIN-Hermes[bot]', | |
| webhookPath: '/api/webhooks/github-apps', | |
| webhookSecretEnv: 'SIN_HERMES_GITHUB_APP_WEBHOOK_SECRET', | |
| }, | |
| { | |
| appIdEnv: 'SIN_BUGBOUNTY_GITHUB_APP_ID', | |
| appSlug: 'sin-bugbounty', | |
| preferredAgentSlug: 'sin-bugbounty', | |
| botName: 'SIN-BugBounty[bot]', | |
| webhookPath: '/api/webhooks/github-apps', | |
| webhookSecretEnv: 'SIN_BUGBOUNTY_GITHUB_APP_WEBHOOK_SECRET', | |
| }, | |
| ], | |
| }), | |
| SIN_HERMES_GITHUB_APP_ID: '123456', | |
| SIN_HERMES_GITHUB_APP_WEBHOOK_SECRET: 'secret-1', | |
| SIN_BUGBOUNTY_GITHUB_APP_ID: '345678', | |
| SIN_BUGBOUNTY_GITHUB_APP_WEBHOOK_SECRET: 'secret-3', | |
| }); | |
| try { | |
| const { routeGitHubWebhook } = await importFresh('./github-app-routing.js'); | |
| const rawBody = JSON.stringify({ | |
| action: 'opened', | |
| installation: { id: 987654321, app_id: 345678 }, | |
| repository: { full_name: 'OpenSIN-AI/OpenSIN-backend' }, | |
| }); | |
| const signature256 = `sha256=${createHmac('sha256', 'secret-3').update(rawBody).digest('hex')}`; | |
| const result = await routeGitHubWebhook({ | |
| rawBody, | |
| routePath: '/api/webhooks/github-apps', | |
| signature256, | |
| eventName: 'pull_request', | |
| deliveryId: 'delivery-central-test', | |
| }); | |
| assert.equal(result.ok, true); | |
| assert.equal(result.route.agentSlug, 'sin-bugbounty'); | |
| assert.equal(result.route.botName, 'SIN-BugBounty[bot]'); | |
| } finally { | |
| restoreEnv(); | |
| } | |
| }); | |
| test('commentIssueAsGitHubApp exchanges installation token and posts comment', async () => { | |
| const { privateKey } = generateKeyPairSync('rsa', { | |
| modulusLength: 2048, | |
| privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, | |
| publicKeyEncoding: { type: 'pkcs1', format: 'pem' }, | |
| }); | |
| let commentRequestBody = ''; | |
| const server = createServer((req, res) => { | |
| if (req.method === 'POST' && req.url === '/app/installations/987654321/access_tokens') { | |
| res.writeHead(200, { 'content-type': 'application/json' }); | |
| res.end(JSON.stringify({ token: 'installation-token-1', expires_at: '2030-01-01T00:00:00Z' })); | |
| return; | |
| } | |
| if (req.method === 'POST' && req.url === '/repos/OpenSIN-AI/OpenSIN-backend/issues/42/comments') { | |
| const chunks = []; | |
| req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); | |
| req.on('end', () => { | |
| commentRequestBody = Buffer.concat(chunks).toString('utf8'); | |
| res.writeHead(200, { 'content-type': 'application/json' }); | |
| res.end(JSON.stringify({ id: 9001, html_url: 'https://github.com/OpenSIN-AI/OpenSIN-backend/issues/42#issuecomment-9001' })); | |
| }); | |
| return; | |
| } | |
| res.writeHead(404, { 'content-type': 'application/json' }); | |
| res.end(JSON.stringify({ error: 'not_found' })); | |
| }); | |
| await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); | |
| const address = server.address(); | |
| const port = typeof address === 'object' && address ? address.port : 0; | |
| const restoreEnv = setEnv({ | |
| SIN_GITHUB_API_BASE_URL: `http://127.0.0.1:${port}`, | |
| SIN_GITHUB_APP_ROUTING_JSON: JSON.stringify({ | |
| defaultMode: 'hybrid', | |
| defaultWebhookPath: '/github/webhook', | |
| apps: [ | |
| { | |
| agentSlug: 'sin-hermesbote', | |
| appSlug: 'sin-hermes', | |
| botName: 'SIN-Hermes[bot]', | |
| appId: 123456, | |
| privateKeyPem: privateKey, | |
| webhookSecret: 'secret-1', | |
| }, | |
| ], | |
| }), | |
| }); | |
| try { | |
| const { commentIssueAsGitHubApp } = await importFresh('./github-app-routing.js'); | |
| const result = await commentIssueAsGitHubApp({ | |
| repo: 'OpenSIN-AI/OpenSIN-backend', | |
| issueNumber: 42, | |
| body: 'Hello from SIN-Hermes[bot]', | |
| agentSlug: 'sin-hermesbote', | |
| installationId: 987654321, | |
| }); | |
| assert.equal(result.ok, true); | |
| assert.equal(result.agentSlug, 'sin-hermesbote'); | |
| assert.equal(result.commentId, 9001); | |
| assert.match(commentRequestBody, /Hello from SIN-Hermes\[bot\]/); | |
| } finally { | |
| restoreEnv(); | |
| await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); | |
| } | |
| }); | |