Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
40 lines
4.6 KiB
JavaScript
40 lines
4.6 KiB
JavaScript
/** test/webmaker-adapter.test.mjs — 실제 어댑터(portal/webmaker/adapter.mjs)와 Webmaker 의 서명·티켓 규약(modules/webmaker/lib/ariweb.js) 호환 검증. 가짜 Webmaker HTTP 서버가 lib/ariweb.verifyService 로 서명을 검사한다. */
|
|
import { test, eq, ok, rejects, tmpDir, done } from '../lib/test.mjs';
|
|
import { makeDeps, seedGroup } from './helpers.mjs';
|
|
import { create as createAdapter, loginFor } from '../portal/webmaker/adapter.mjs';
|
|
import { createRequire } from 'node:module';
|
|
import http from 'node:http';
|
|
import { join } from 'node:path';
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
process.env.DATA_ROOT = tmpDir(); // modules/webmaker/lib/ariweb 가 쓰는 데이터 경로(연결 등록부)
|
|
const ariweb = require('../modules/webmaker/lib/ariweb.js');
|
|
const deps = await makeDeps(); const who = seedGroup(deps);
|
|
const wm = createAdapter(deps); const SECRET = wm.secret();
|
|
// 가짜 Webmaker 내부 API
|
|
const server = http.createServer((req, res) => { let d = ''; req.on('data', (c) => { d += c; }); req.on('end', () => { const v = ariweb.verifyService(req, req.url, d, { key: SECRET }); if (!v.ok) { res.writeHead(401, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'service_auth_failed', reason: v.reason })); }
|
|
if (req.url.startsWith('/api/internal/ariweb/projects/p1/export')) { res.writeHead(200, { 'Content-Type': 'application/gzip', 'X-Ariweb-Version': '2', 'X-Ariweb-Commit': 'abc' }); return res.end(Buffer.from([0x1f, 0x8b, 0, 0])); }
|
|
if (req.url === '/api/internal/ariweb/projects/p9/export') { res.writeHead(404, { 'Content-Type': 'application/json' }); return res.end('{"error":"nope"}'); }
|
|
res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, echo: { method: req.method, url: req.url, body: d ? JSON.parse(d) : null }, projects: [{ id: 'p1', title: 't' }], project: { id: 'p1', title: 't', versionList: [] }, link: { siteId: 's' }, source: { provider: 'gitea' }, credit: { balance: 1 } })); }); });
|
|
await new Promise((r) => server.listen(0, '127.0.0.1', r)); deps.config.webmakerInternalUrl = `http://127.0.0.1:${server.address().port}`; deps.config.webmakerUrl = 'https://webmaker.example.test';
|
|
|
|
await test('로그인 ID 규칙과 SSO 티켓: 포털 서명이 Webmaker 검증을 통과하고 60초 뒤 만료·1회용', () => {
|
|
const login = loginFor(who.user, who.group); ok(/^aw-[a-z0-9]{1,8}-[a-z0-9]{1,8}$/.test(login), login);
|
|
const url = wm.ssoUrl({ user: who.user, group: who.group, site: { id: 's_1', name: '사이트', webmaker_project_id: 'p1' } }); ok(url.startsWith('https://webmaker.example.test/api/auth/sso?ticket='));
|
|
const ticket = decodeURIComponent(url.split('ticket=')[1]); const t = ariweb.verifyTicket(ticket, { key: SECRET }); eq(t.login, login); eq(t.groupName, who.group.name); eq(t.siteName, '사이트'); eq(t.projectId, 'p1'); ok(t.exp - Date.now() <= 60000);
|
|
ok(/이미 사용/.test(ariweb.verifyTicket(ticket, { key: SECRET }).error), '1회용'); ok(/만료/.test(ariweb.verifyTicket(ticket, { key: SECRET, now: Date.now() + 61000 }).error) || /이미 사용/.test(ariweb.verifyTicket(ticket, { key: SECRET, now: Date.now() + 61000 }).error));
|
|
ok(/서명/.test(ariweb.verifyTicket(ticket, { key: 'other' }).error), '다른 비밀은 거부');
|
|
eq(deps.store.get('SELECT COUNT(*) AS n FROM sso_tickets').n, 1); ok(!JSON.stringify(deps.store.all('SELECT * FROM sso_tickets')).includes(ticket.split('.')[1]), '서명 원문 저장 안 함');
|
|
const info = wm.provisioningInfo(); eq(info.configured, true); ok(!JSON.stringify(info).includes(SECRET));
|
|
});
|
|
await test('내부 API 호출: 서명 헤더로 통과, 경로·본문이 그대로 전달, 404/연결 실패 코드', async () => {
|
|
const p = await wm.listProjects('aw-x-y'); eq(p[0].id, 'p1');
|
|
const c = await wm.createProject({ login: 'aw-x-y', ownerName: 'N', title: 'T', mode: 'ai', link: { siteId: 's' } }); eq(c.id, 'p1');
|
|
const l = await wm.linkProject('p1', { siteId: 's', groupId: 'g' }); eq(l.siteId, 's'); eq((await wm.projectSource('p1')).provider, 'gitea'); eq((await wm.credits('aw-x-y')).credit.balance, 1); ok((await wm.health()).ok);
|
|
const file = join(tmpDir(), 'b.tar.gz'); const ex = await wm.exportVersion('p1', 2, file); eq(ex.version, '2'); eq(ex.commit, 'abc'); eq(readFileSync(file).length, 4);
|
|
eq((await rejects(() => wm.exportVersion('p9', null, file))).code, 'version_not_found');
|
|
deps.config.webmakerInternalUrl = 'http://127.0.0.1:1'; eq((await rejects(() => wm.listProjects('x'))).code, 'service_unreachable'); eq((await rejects(() => wm.exportVersion('p1', null, file))).code, 'service_unreachable');
|
|
});
|
|
server.close(); done();
|