This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/packages/sdk-test-runner/js/domains/git-board.js
gobha c03ece4230 V0.38.5 git board rewrite (#238)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-25 22:24:59 +00:00

472 lines
20 KiB
JavaScript

/**
* SDK Test Runner — Domain: git-board (v0.38.5)
*
* Tests the real-world library composition pattern:
* gitea-client library (connection types, exports) → git-board consumer
* (dependency, tools, delegation). Validates the full package lifecycle
* with realistic manifests and Starlark scripts.
* Requires admin role.
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
// ── Minimal zip builder ────────────────────────
function buildZip(entries) {
var localHeaders = [];
var centralHeaders = [];
var offset = 0;
entries.forEach(function (entry) {
var nameBytes = new TextEncoder().encode(entry.name);
var dataBytes = new TextEncoder().encode(entry.content);
var local = new Uint8Array(30 + nameBytes.length + dataBytes.length);
var dv = new DataView(local.buffer);
dv.setUint32(0, 0x04034b50, true);
dv.setUint16(4, 20, true);
dv.setUint16(8, 0, true);
dv.setUint16(10, 0, true);
dv.setUint16(12, 0, true);
dv.setUint32(14, crc32(dataBytes), true);
dv.setUint32(18, dataBytes.length, true);
dv.setUint32(22, dataBytes.length, true);
dv.setUint16(26, nameBytes.length, true);
dv.setUint16(28, 0, true);
local.set(nameBytes, 30);
local.set(dataBytes, 30 + nameBytes.length);
localHeaders.push(local);
var central = new Uint8Array(46 + nameBytes.length);
var cdv = new DataView(central.buffer);
cdv.setUint32(0, 0x02014b50, true);
cdv.setUint16(4, 20, true);
cdv.setUint16(6, 20, true);
cdv.setUint16(28, nameBytes.length, true);
cdv.setUint32(16, crc32(dataBytes), true);
cdv.setUint32(20, dataBytes.length, true);
cdv.setUint32(24, dataBytes.length, true);
cdv.setUint32(42, offset, true);
central.set(nameBytes, 46);
centralHeaders.push(central);
offset += local.length;
});
var centralSize = centralHeaders.reduce(function (s, c) { return s + c.length; }, 0);
var eocd = new Uint8Array(22);
var edv = new DataView(eocd.buffer);
edv.setUint32(0, 0x06054b50, true);
edv.setUint16(8, entries.length, true);
edv.setUint16(10, entries.length, true);
edv.setUint32(12, centralSize, true);
edv.setUint32(16, offset, true);
var result = new Uint8Array(offset + centralSize + 22);
var pos = 0;
localHeaders.forEach(function (h) { result.set(h, pos); pos += h.length; });
centralHeaders.forEach(function (h) { result.set(h, pos); pos += h.length; });
result.set(eocd, pos);
return new Blob([result], { type: 'application/zip' });
}
var crcTable = null;
function crc32(bytes) {
if (!crcTable) {
crcTable = new Uint32Array(256);
for (var n = 0; n < 256; n++) {
var c = n;
for (var k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
crcTable[n] = c;
}
}
var crc = 0xFFFFFFFF;
for (var i = 0; i < bytes.length; i++) crc = crcTable[(crc ^ bytes[i]) & 0xFF] ^ (crc >>> 8);
return (crc ^ 0xFFFFFFFF) >>> 0;
}
function makePkgFile(manifest, extraFiles) {
var entries = [{ name: 'manifest.json', content: JSON.stringify(manifest) }];
if (extraFiles) entries = entries.concat(extraFiles);
var blob = buildZip(entries);
return new File([blob], manifest.id + '.pkg', { type: 'application/zip' });
}
// ── Starlark script content ────────────────────
// Realistic but self-contained — no real HTTP calls needed.
var LIB_HTTP_HELPERS = [
'def auth_headers(conn):',
' token = conn.get("api_token", "")',
' if not token:',
' return {}',
' return {"Authorization": "token " + token}',
'',
'def _base(conn):',
' url = conn.get("base_url", "")',
' if url.endswith("/"):',
' url = url[:-1]',
' return url',
'',
'def gitea_get(conn, path):',
' url = _base(conn) + "/api/v1" + path',
' resp = http.get(url=url, headers=auth_headers(conn))',
' if int(resp["status"]) >= 400:',
' return None',
' body = resp.get("body", "")',
' if not body:',
' return None',
' return json.decode(body)',
'',
'def gitea_post(conn, path, data):',
' url = _base(conn) + "/api/v1" + path',
' hdrs = dict(auth_headers(conn), **{"Content-Type": "application/json"})',
' resp = http.post(url=url, body=json.encode(data), headers=hdrs)',
' if int(resp["status"]) >= 400:',
' return None',
' body = resp.get("body", "")',
' if not body:',
' return None',
' return json.decode(body)',
'',
'def gitea_patch(conn, path, data):',
' url = _base(conn) + "/api/v1" + path',
' hdrs = dict(auth_headers(conn), **{"Content-Type": "application/json", "X-HTTP-Method-Override": "PATCH"})',
' resp = http.post(url=url, body=json.encode(data), headers=hdrs)',
' if int(resp["status"]) >= 400:',
' return None',
' body = resp.get("body", "")',
' if not body:',
' return None',
' return json.decode(body)',
'',
'def resp(status, data):',
' return {"status": status, "body": json.encode(data), "headers": {"Content-Type": "application/json"}}',
].join('\n');
var LIB_REPOS = [
'load("star/http_helpers.star", "gitea_get")',
'',
'def get_repos(conn):',
' data = gitea_get(conn, "/repos/search?limit=50&sort=updated")',
' if data == None:',
' return None',
' repos = data if type(data) == "list" else data.get("data", [])',
' out = []',
' for r in repos:',
' out.append({',
' "full_name": r.get("full_name", ""),',
' "name": r.get("name", ""),',
' "owner": r.get("owner", {}).get("login", ""),',
' })',
' return out',
].join('\n');
var LIB_ISSUES = [
'load("star/http_helpers.star", "gitea_get", "gitea_post", "gitea_patch")',
'',
'def get_issues(conn, owner, repo, state):',
' return []',
'',
'def get_issue(conn, owner, repo, number):',
' return {"number": number, "title": "stub", "body": "", "state": "open", "labels": [], "assignee": "", "created_at": "", "comments": []}',
'',
'def create_issue(conn, owner, repo, title, body, labels):',
' return {"number": 0, "title": title, "html_url": ""}',
'',
'def update_issue(conn, owner, repo, number, title, body, state):',
' return {"number": number, "state": state or "open", "title": title or "stub"}',
'',
'def add_comment(conn, owner, repo, number, body):',
' return {"id": 0, "body": body}',
].join('\n');
var LIB_CI = [
'load("star/http_helpers.star", "gitea_get")',
'',
'def get_ci_status(conn, owner, repo, ref):',
' return {"ref": ref, "state": "pending", "statuses": [], "count": 0}',
].join('\n');
var LIB_SCRIPT = [
'load("star/http_helpers.star", "gitea_get", "gitea_post", "gitea_patch", "auth_headers", "resp")',
'load("star/repos.star", _repos_get = "get_repos")',
'load("star/issues.star", _issues_get = "get_issues", _issue_get = "get_issue", _issue_create = "create_issue", _issue_update = "update_issue", _comment_add = "add_comment")',
'load("star/ci.star", _ci_get = "get_ci_status")',
'',
'# Re-export for lib.require() visibility',
'get_repos = _repos_get',
'get_issues = _issues_get',
'get_issue = _issue_get',
'create_issue = _issue_create',
'update_issue = _issue_update',
'add_comment = _comment_add',
'get_ci_status = _ci_get',
'',
'def get_prs(conn, owner, repo, state):',
' return []',
'',
'def on_request(req):',
' conn = connections.get("gitea")',
' if conn == None:',
' return resp(400, {"error": "no connection"})',
' return resp(200, {"ok": True})',
].join('\n');
var CONSUMER_SCRIPT = [
'gitea = lib.require("{LIB_ID}")',
'',
'def _conn():',
' return connections.get("gitea")',
'',
'def on_request(req):',
' conn = _conn()',
' if conn == None:',
' return {"status": 400, "body": json.encode({"error": "no connection"}), "headers": {"Content-Type": "application/json"}}',
' return {"status": 200, "body": json.encode({"ok": True}), "headers": {"Content-Type": "application/json"}}',
'',
'def on_tool_call(tool_name, params):',
' conn = _conn()',
' if conn == None:',
' return {"error": "no connection"}',
' if tool_name == "list_issues":',
' data = gitea.get_issues(conn, params.get("owner",""), params.get("repo",""), "open")',
' return {"issues": data or [], "count": len(data or [])}',
' return {"error": "unknown tool"}',
].join('\n');
// ── Tests ──────────────────────────────────────
T.registerDomain('git-board', async function () {
if (!window.sw || !sw.isAdmin) {
await T.test('git-board', 'guard', 'skip — not admin', async function () {
T.assert(true, 'skipped (user is not admin)');
});
return;
}
var ts = Date.now();
var libId = 'gb-test-lib-' + ts;
var consumerId = 'gb-test-board-' + ts;
var connType = 'gb-test-gitea-' + ts;
var connId = null;
// ── 1. Install library with multi-file structure + connection type ──
await T.test('git-board', 'install-library', 'install gitea-client-style library with multi-file and connection type', async function () {
var manifest = {
id: libId,
title: 'GB Test Gitea Client',
type: 'library',
tier: 'starlark',
version: '1.0.1',
exports: ['get_repos', 'get_issues', 'get_issue', 'create_issue', 'update_issue', 'add_comment', 'get_prs', 'get_ci_status'],
permissions: ['api.http', 'connections.read', 'db.read', 'db.write'],
connections: [{
type: connType,
label: 'Test Gitea Instance',
fields: {
base_url: { type: 'url', required: 'true', label: 'Server URL' },
api_token: { type: 'secret', required: 'true', label: 'API Token' },
org: { type: 'string', required: 'false', label: 'Default Org' }
},
scopes: ['global', 'team', 'personal']
}],
api_routes: [
{ method: 'GET', path: '/repos' },
{ method: 'GET', path: '/issues' }
],
db_tables: {
repos: {
columns: { full_name: 'text', owner: 'text', name: 'text' },
indexes: []
}
},
schema_version: 1
};
var file = makePkgFile(manifest, [
{ name: 'script.star', content: LIB_SCRIPT },
{ name: 'star/http_helpers.star', content: LIB_HTTP_HELPERS },
{ name: 'star/repos.star', content: LIB_REPOS },
{ name: 'star/issues.star', content: LIB_ISSUES },
{ name: 'star/ci.star', content: LIB_CI }
]);
var r = await sw.api.admin.packages.install(file);
T.assert(r && r.id === libId, 'library should install');
T.assert(r.type === 'library', 'type should be library');
});
T.cleanup.push(async function () {
if (libId) try { await sw.api.admin.packages.del(libId); } catch (_) {}
});
// ── 2. Grant library permissions ──
await T.test('git-board', 'grant-lib-perms', 'grant library permissions', async function () {
await sw.api.post('/api/v1/admin/extensions/' + libId + '/permissions/grant-all', {});
T.assert(true, 'permissions granted');
});
// ── 3. Connection type appears ──
await T.test('git-board', 'conn-type-registered', 'connection type registered from library manifest', async function () {
var types = await sw.api.connectionTypes.list();
T.assert(Array.isArray(types), 'should be array');
var found = types.find(function (t) { return t.type === connType; });
T.assert(found, 'connection type should appear: ' + connType);
T.assert(found.package_id === libId, 'package_id should be library');
T.assert(found.fields && found.fields.api_token, 'should have api_token field');
T.assert(found.fields.api_token.type === 'secret', 'api_token should be secret type');
T.assert(found.fields.org, 'should have org field');
});
// ── 4. Install consumer with dependency + tools ──
await T.test('git-board', 'install-consumer', 'install git-board-style consumer with dependency and tools', async function () {
var manifest = {
id: consumerId,
title: 'GB Test Board',
type: 'full',
tier: 'starlark',
route: '/s/' + consumerId,
auth: 'authenticated',
layout: 'single',
version: '0.2.0',
permissions: ['connections.read'],
dependencies: {},
api_routes: [
{ method: 'GET', path: '/repos' },
{ method: 'GET', path: '/board' },
{ method: 'GET', path: '/ci/*' }
],
tools: [
{ name: 'list_issues', description: 'List issues', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true } } },
{ name: 'get_issue', description: 'Get issue', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true }, number: { type: 'number', required: true } } },
{ name: 'create_issue', description: 'Create issue', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true }, title: { type: 'string', required: true } } },
{ name: 'update_issue', description: 'Update issue', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true }, number: { type: 'number', required: true } } },
{ name: 'add_comment', description: 'Add comment', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true }, number: { type: 'number', required: true }, body: { type: 'string', required: true } } },
{ name: 'list_pull_requests', description: 'List PRs', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true } } },
{ name: 'get_ci_status', description: 'Get CI status', parameters: { owner: { type: 'string', required: true }, repo: { type: 'string', required: true }, ref: { type: 'string', required: true } } }
],
settings: {
default_owner: { type: 'string', label: 'Default Owner', default: '' },
default_repo: { type: 'string', label: 'Default Repo', default: '' }
}
};
manifest.dependencies[libId] = '>=1.0.0';
var script = CONSUMER_SCRIPT.replace('{LIB_ID}', libId);
var file = makePkgFile(manifest, [
{ name: 'script.star', content: script }
]);
var r = await sw.api.admin.packages.install(file);
T.assert(r && r.id === consumerId, 'consumer should install');
T.assert(r.type === 'full', 'type should be full');
});
T.cleanup.push(async function () {
if (consumerId) try { await sw.api.admin.packages.del(consumerId); } catch (_) {}
});
// ── 5. Grant consumer permissions ──
await T.test('git-board', 'grant-consumer-perms', 'grant consumer permissions', async function () {
await sw.api.post('/api/v1/admin/extensions/' + consumerId + '/permissions/grant-all', {});
T.assert(true, 'permissions granted');
});
// ── 6. Dependency recorded ──
await T.test('git-board', 'dependency-recorded', 'consumer depends on library', async function () {
var r = await sw.api.admin.packages.dependencies(consumerId);
// SDK unwraps {data:[...]} to bare array
var deps = Array.isArray(r) ? r : (r && r.data) || [];
T.assert(deps.length > 0, 'should have dependencies');
var dep = deps.find(function (d) { return d.library_id === libId; });
T.assert(dep, 'dependency on library should exist');
T.assert(dep.version_spec === '>=1.0.0', 'version spec should match');
});
// ── 7. Tools registered ──
await T.test('git-board', 'tools-registered', 'consumer has 7 tools registered', async function () {
var r = await sw.api.admin.packages.get(consumerId);
T.assert(r, 'package should exist');
var tools = r.tools || (r.manifest && r.manifest.tools) || [];
T.assert(tools.length === 7, 'should have 7 tools, got ' + tools.length);
var names = tools.map(function (t) { return t.name; }).sort();
T.assert(names.indexOf('list_issues') >= 0, 'should include list_issues');
T.assert(names.indexOf('get_ci_status') >= 0, 'should include get_ci_status');
T.assert(names.indexOf('add_comment') >= 0, 'should include add_comment');
});
// ── 8. Library consumers listed ──
await T.test('git-board', 'consumers-listed', 'library shows consumer in consumers list', async function () {
var r = await sw.api.admin.packages.consumers(libId);
// SDK unwraps {data:[...]} to bare array
var consumers = Array.isArray(r) ? r : (r && r.data) || [];
T.assert(consumers.length > 0, 'should have consumers');
var found = consumers.find(function (c) { return c.consumer_id === consumerId; });
T.assert(found, 'consumer should appear in library consumers list');
});
// ── 9. Uninstall protection ──
await T.test('git-board', 'uninstall-protection', 'cannot uninstall library while consumer exists', async function () {
var threw = false;
try {
await sw.api.admin.packages.del(libId);
} catch (e) {
threw = true;
var msg = (e && e.message) || String(e);
T.assert(msg.indexOf('409') >= 0 || msg.indexOf('active') >= 0 || msg.indexOf('depend') >= 0,
'should be 409 or dependency error: ' + msg);
}
T.assert(threw, 'delete should have thrown');
});
// ── 10. Create connection of library type ──
await T.test('git-board', 'create-connection', 'create connection using library-declared type', async function () {
var r = await sw.api.admin.connections.create({
type: connType,
package_id: libId,
name: 'GB Test Connection',
config: { base_url: 'https://test.example.com', api_token: 'test-token-gb', org: 'test-org' }
});
T.assert(r && r.id, 'should create connection');
T.assert(r.type === connType, 'type should match');
connId = r.id;
});
T.cleanup.push(async function () {
if (connId) try { await sw.api.admin.connections.del(connId); } catch (_) {}
});
// ── Cleanup ──────────────────────────────────
await T.test('git-board', 'cleanup-connection', 'delete test connection', async function () {
if (connId) {
await sw.api.admin.connections.del(connId);
connId = null;
}
T.assert(true, 'connection deleted');
});
await T.test('git-board', 'cleanup-consumer', 'uninstall consumer', async function () {
await sw.api.admin.packages.del(consumerId);
consumerId = null;
T.assert(true, 'consumer uninstalled');
});
await T.test('git-board', 'cleanup-library', 'uninstall library', async function () {
await sw.api.admin.packages.del(libId);
libId = null;
T.assert(true, 'library uninstalled');
});
await T.test('git-board', 'types-removed', 'connection type removed after library uninstall', async function () {
var types = await sw.api.connectionTypes.list();
var found = types.find(function (t) { return t.type === connType; });
T.assert(!found, 'type should not appear after library uninstall');
});
});
})();