Changeset 0.37.17 (#229)

Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
2026-03-24 16:50:00 +00:00
committed by xcaliber
parent e0687d2ea6
commit 96a4f16bc5
27 changed files with 4107 additions and 107 deletions

View File

@@ -0,0 +1,153 @@
/**
* SDK Test Runner — Domain: admin
*
* Tests admin-scoped endpoints. Requires admin role.
* KNOWN ISSUES: some admin list endpoints use non-standard envelope
* keys (§E in ICD drift audit).
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('admin', async function () {
// Guard: skip if not admin
if (!window.sw || !sw.isAdmin) {
await T.test('admin', 'guard', 'skip — not admin', async function () {
T.assert(true, 'skipped (user is not admin)');
});
return;
}
// ── Users ──
await T.test('admin', 'users', 'sw.api.admin.users.list()', {
sdk: function () { return sw.api.admin.users.list(); },
raw: { method: 'GET', path: '/admin/users' },
validate: function (r) {
// KNOWN: backend returns {users: [...]} not {data: [...]}
var arr = r;
if (r && r.users) arr = r.users;
if (r && r.data) arr = r.data;
T.assert(Array.isArray(arr), 'expected array (maybe wrapped in non-standard key)');
}
});
// ── Configs (provider configs) ──
await T.test('admin', 'configs', 'sw.api.admin.configs.list()', {
sdk: function () { return sw.api.admin.configs.list(); },
raw: { method: 'GET', path: '/admin/configs' },
validate: function (r) {
// KNOWN: backend returns {configs: [...]} not {data: [...]}
var arr = r;
if (r && r.configs) arr = r.configs;
if (r && r.data) arr = r.data;
T.assert(Array.isArray(arr), 'expected array');
}
});
// ── Models ──
await T.test('admin', 'models', 'sw.api.admin.models.list()', {
sdk: function () { return sw.api.admin.models.list(); },
raw: { method: 'GET', path: '/admin/models' },
validate: function (r) {
// KNOWN: backend returns {models: [...]} not {data: [...]}
var arr = r;
if (r && r.models) arr = r.models;
if (r && r.data) arr = r.data;
T.assert(Array.isArray(arr), 'expected array');
}
});
// ── Settings ──
await T.test('admin', 'settings', 'sw.api.admin.settings.get()', {
sdk: function () { return sw.api.admin.settings.get(); },
raw: { method: 'GET', path: '/admin/settings' },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); }
});
// ── Packages ──
await T.test('admin', 'packages', 'sw.api.admin.packages.list()', {
sdk: function () { return sw.api.admin.packages.list(); },
raw: { method: 'GET', path: '/admin/packages' },
validate: function (r) { T.assert(Array.isArray(r) || (r && Array.isArray(r.data)), 'expected array'); }
});
// ── Surfaces ──
await T.test('admin', 'surfaces', 'sw.api.admin.surfaces.list()', {
sdk: function () { return sw.api.admin.surfaces.list(); },
raw: { method: 'GET', path: '/admin/surfaces' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Extensions ──
await T.test('admin', 'extensions', 'sw.api.admin.extensions.list()', {
sdk: function () { return sw.api.admin.extensions.list(); },
raw: { method: 'GET', path: '/admin/extensions' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Tasks ──
await T.test('admin', 'tasks', 'sw.api.admin.tasks.list()', {
sdk: function () { return sw.api.admin.tasks.list(); },
raw: { method: 'GET', path: '/admin/tasks' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Routing ──
await T.test('admin', 'routing', 'sw.api.admin.routing.policies()', {
sdk: function () { return sw.api.admin.routing.policies(); },
raw: { method: 'GET', path: '/admin/routing/policies' },
validate: function (r) { T.assert(Array.isArray(r) || (r && Array.isArray(r.data)), 'expected array'); }
});
// ── Provider health ──
await T.test('admin', 'providers', 'sw.api.admin.providers.health()', {
sdk: function () { return sw.api.admin.providers.health(); },
raw: { method: 'GET', path: '/admin/providers/health' },
validate: function (r) { T.assert(typeof r === 'object' || Array.isArray(r), 'expected object or array'); }
});
// ── Storage ──
await T.test('admin', 'storage', 'sw.api.admin.storage.status()', {
sdk: function () { return sw.api.admin.storage.status(); },
raw: { method: 'GET', path: '/admin/storage/status' },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); }
});
// ── Vault ──
await T.test('admin', 'vault', 'sw.api.admin.vault.status()', {
sdk: function () { return sw.api.admin.vault.status(); },
raw: { method: 'GET', path: '/admin/vault/status' },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); }
});
// ── Channels (archived) ──
await T.test('admin', 'channels', 'sw.api.admin.channels.archived()', {
sdk: function () { return sw.api.admin.channels.archived(); },
raw: { method: 'GET', path: '/admin/channels/archived' },
validate: function (r) { T.assert(Array.isArray(r) || (r && Array.isArray(r.data)), 'expected array'); }
});
// ── Projects ──
await T.test('admin', 'projects', 'sw.api.admin.projects.list()', {
sdk: function () { return sw.api.admin.projects.list(); },
raw: { method: 'GET', path: '/admin/projects' },
validate: function (r) { T.assert(Array.isArray(r) || (r && Array.isArray(r.data)), 'expected array'); }
});
});
})();

View File

@@ -0,0 +1,158 @@
/**
* SDK Test Runner — Domain: channels
*
* Tests: list, create, get, update, delete, messages, participants,
* models, kbs, files, markRead, generateTitle, path, siblings.
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('channels', async function () {
var tag = 'sdkr-' + Date.now();
var channelId = null;
// ── List ──
await T.test('channels', 'list', 'sw.api.channels.list() returns array', {
sdk: function () { return sw.api.channels.list(); },
raw: { method: 'GET', path: '/channels' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Create ──
await T.test('channels', 'crud', 'sw.api.channels.create() returns channel shape', {
sdk: function () {
return sw.api.channels.create({ title: tag + '-ch', type: 'direct' });
},
raw: { method: 'POST', path: '/channels', body: { title: tag + '-ch-raw', type: 'direct' } },
validate: function (r) {
T.assertShape(r, T.S.channel, 'channel');
channelId = r.id;
T.registerCleanup(function () { if (channelId) return T.safeDelete('/channels/' + channelId); });
}
});
if (!channelId) return;
// ── Get ──
await T.test('channels', 'crud', 'sw.api.channels.get(id) returns full shape', {
sdk: function () { return sw.api.channels.get(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId },
validate: function (r) {
T.assertShape(r, T.S.channelFull, 'channelFull');
T.assert(r.id === channelId, 'id mismatch');
}
});
// ── Update ──
await T.test('channels', 'crud', 'sw.api.channels.update(id, data)', {
sdk: function () { return sw.api.channels.update(channelId, { title: tag + '-updated' }); },
raw: { method: 'PUT', path: '/channels/' + channelId, body: { title: tag + '-updated' } },
validate: function (r) {
T.assertShape(r, T.S.channelFull, 'channel');
T.assert(r.title === tag + '-updated', 'title not updated');
}
});
// ── Messages ──
await T.test('channels', 'messages', 'sw.api.channels.messages(id) returns array', {
sdk: function () { return sw.api.channels.messages(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/messages' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Send message ──
var msgId = null;
await T.test('channels', 'messages', 'sw.api.channels.send(id, data)', {
sdk: function () {
return sw.api.channels.send(channelId, { content: 'sdk-test-msg', role: 'user' });
},
raw: { method: 'POST', path: '/channels/' + channelId + '/messages',
body: { content: 'sdk-test-msg', role: 'user' } },
validate: function (r) {
T.assertShape(r, T.S.message, 'message');
msgId = r.id;
}
});
// ── Siblings ──
if (msgId) {
await T.test('channels', 'messages', 'sw.api.channels.siblings(id, msgId)', {
sdk: function () { return sw.api.channels.siblings(channelId, msgId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/messages/' + msgId + '/siblings' },
validate: function (r) {
// Siblings returns { siblings: [...], current_index, total }
var arr = r.siblings || T.unwrapList(r);
T.assert(Array.isArray(arr), 'expected siblings array');
}
});
}
// ── Path ──
await T.test('channels', 'tree', 'sw.api.channels.path(id)', {
sdk: function () { return sw.api.channels.path(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/path' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
// ── Mark Read ──
await T.test('channels', 'read', 'sw.api.channels.markRead(id)', {
sdk: function () { return sw.api.channels.markRead(channelId); },
raw: { method: 'POST', path: '/channels/' + channelId + '/mark-read', body: {} },
validate: function () { /* 200 OK is sufficient */ }
});
// ── KBs ──
await T.test('channels', 'kbs', 'sw.api.channels.kbs(id) returns array', {
sdk: function () { return sw.api.channels.kbs(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/knowledge-bases' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
// ── Files ──
await T.test('channels', 'files', 'sw.api.channels.files(id)', {
sdk: function () { return sw.api.channels.files(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/files' },
validate: function (r) {
// May be array or {files: [], count: N}
T.assert(typeof r === 'object' || Array.isArray(r), 'expected object or array');
}
});
// ── Participants ──
await T.test('channels', 'participants', 'sw.api.channels.participants(id)', {
sdk: function () { return sw.api.channels.participants(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/participants' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
// ── Models ──
await T.test('channels', 'models', 'sw.api.channels.models(id)', {
sdk: function () { return sw.api.channels.models(channelId); },
raw: { method: 'GET', path: '/channels/' + channelId + '/models' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
// ── Delete ──
await T.test('channels', 'crud', 'sw.api.channels.del(id)', {
sdk: function () { return sw.api.channels.del(channelId); },
raw: { method: 'DELETE', path: '/channels/' + channelId },
validate: function () { channelId = null; }
});
});
})();

View File

@@ -0,0 +1,82 @@
/**
* SDK Test Runner — Domain: knowledge
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('knowledge', async function () {
var tag = 'sdkr-' + Date.now();
var kbId = null;
await T.test('knowledge', 'list', 'sw.api.knowledge.list()', {
sdk: function () { return sw.api.knowledge.list(); },
raw: { method: 'GET', path: '/knowledge-bases' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
await T.test('knowledge', 'crud', 'sw.api.knowledge.create()', {
sdk: function () {
return sw.api.knowledge.create({ name: tag + '-kb', scope: 'personal' });
},
raw: { method: 'POST', path: '/knowledge-bases', body: { name: tag + '-kb', scope: 'personal' } },
validate: function (r) {
T.assertShape(r, T.S.kb, 'kb');
kbId = r.id;
T.registerCleanup(function () { if (kbId) return T.safeDelete('/knowledge-bases/' + kbId); });
}
});
if (!kbId) return;
await T.test('knowledge', 'crud', 'sw.api.knowledge.get(id)', {
sdk: function () { return sw.api.knowledge.get(kbId); },
raw: { method: 'GET', path: '/knowledge-bases/' + kbId },
validate: function (r) {
T.assertShape(r, T.S.kb, 'kb');
T.assert(r.id === kbId, 'id mismatch');
}
});
await T.test('knowledge', 'crud', 'sw.api.knowledge.update(id, data)', {
sdk: function () { return sw.api.knowledge.update(kbId, { name: tag + '-kb-upd' }); },
raw: { method: 'PUT', path: '/knowledge-bases/' + kbId, body: { name: tag + '-kb-upd' } },
validate: function (r) { T.assertShape(r, T.S.kb, 'kb'); }
});
// ── Discoverable ──
await T.test('knowledge', 'discover', 'sw.api.knowledge.discoverable()', {
sdk: function () { return sw.api.knowledge.discoverable(); },
raw: { method: 'GET', path: '/knowledge-bases-discoverable' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Documents ──
await T.test('knowledge', 'docs', 'sw.api.knowledge.documents(id)', {
sdk: function () { return sw.api.knowledge.documents(kbId); },
raw: { method: 'GET', path: '/knowledge-bases/' + kbId + '/documents' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Search ──
// Search requires an embedding provider — only run if fixture provisioned one
var hasEmbedding = T.fixtures && T.fixtures.embeddingModel;
await T.test('knowledge', 'search', 'sw.api.knowledge.search(id, query)', {
sdk: function () { return sw.api.knowledge.search(kbId, 'test', 5); },
raw: { method: 'POST', path: '/knowledge-bases/' + kbId + '/search',
body: { query: 'test', limit: 5 } },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); },
skip: hasEmbedding ? null : 'no embedding provider configured — set up a provider with embedding models'
});
await T.test('knowledge', 'crud', 'sw.api.knowledge.del(id)', {
sdk: function () { return sw.api.knowledge.del(kbId); },
raw: { method: 'DELETE', path: '/knowledge-bases/' + kbId },
validate: function () { kbId = null; }
});
});
})();

View File

@@ -0,0 +1,196 @@
/**
* SDK Test Runner — Domain: misc
*
* Covers smaller domains: notifications, folders, profile,
* data portability, health, models, users, presence, usage, groups,
* tools, git.
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('misc', async function () {
var tag = 'sdkr-' + Date.now();
// ═══════════════════════════════════════════════════════════
// HEALTH
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'health', 'sw.api.health()', {
sdk: function () { return sw.api.health(); },
raw: { method: 'GET', path: '/health' },
validate: function (r) { T.assert(r.status === 'ok', 'status should be ok'); }
});
// ═══════════════════════════════════════════════════════════
// PROFILE
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'profile', 'sw.api.profile.get()', {
sdk: function () { return sw.api.profile.get(); },
raw: { method: 'GET', path: '/profile' },
validate: function (r) { T.assertShape(r, T.S.profile, 'profile'); }
});
await T.test('misc', 'profile', 'sw.api.profile.permissions()', {
sdk: function () {
if (sw.api.profile.permissions) return sw.api.profile.permissions();
throw new Error('sw.api.profile.permissions not defined');
},
raw: { method: 'GET', path: '/profile/permissions' },
validate: function (r) {
T.assert(Array.isArray(r.permissions), 'permissions should be array');
}
});
// ═══════════════════════════════════════════════════════════
// MODELS
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'models', 'sw.api.models.enabled()', {
sdk: function () {
if (sw.api.models && sw.api.models.enabled) return sw.api.models.enabled();
throw new Error('sw.api.models.enabled not defined');
},
raw: { method: 'GET', path: '/models/enabled' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ═══════════════════════════════════════════════════════════
// FOLDERS
// ═══════════════════════════════════════════════════════════
var folderId = null;
await T.test('misc', 'folders', 'sw.api.folders.list()', {
sdk: function () { return sw.api.folders.list(); },
raw: { method: 'GET', path: '/folders' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
await T.test('misc', 'folders', 'sw.api.folders.create()', {
sdk: function () {
return sw.api.folders.create(tag + '-folder');
},
raw: { method: 'POST', path: '/folders', body: { name: tag + '-folder', parent_id: null, sort_order: 0 } },
validate: function (r) {
// Backend returns { folder: {...} } — SDK passes envelope through
var obj = r.folder || r;
T.assertShape(obj, T.S.folder, 'folder');
folderId = obj.id;
T.registerCleanup(function () { if (folderId) return T.safeDelete('/folders/' + folderId); });
}
});
if (folderId) {
await T.test('misc', 'folders', 'sw.api.folders.update(id, data)', {
sdk: function () { return sw.api.folders.update(folderId, { name: tag + '-fold-upd' }); },
raw: { method: 'PUT', path: '/folders/' + folderId, body: { name: tag + '-fold-upd' } },
validate: function (r) { T.assert(r && (r.ok || r.id || r.folder), 'expected ok or folder'); }
});
await T.test('misc', 'folders', 'sw.api.folders.del(id)', {
sdk: function () { return sw.api.folders.del(folderId); },
raw: { method: 'DELETE', path: '/folders/' + folderId },
validate: function () { folderId = null; }
});
}
// ═══════════════════════════════════════════════════════════
// NOTIFICATIONS (duplicate key bug fixed — prefs/setPref/delPref restored)
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'notifications', 'sw.api.notifications.list()', {
sdk: function () { return sw.api.notifications.list(); },
raw: { method: 'GET', path: '/notifications' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
await T.test('misc', 'notifications', 'sw.api.notifications.unreadCount()', {
sdk: function () { return sw.api.notifications.unreadCount(); },
raw: { method: 'GET', path: '/notifications/unread-count' },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); }
});
await T.test('misc', 'notifications', 'sw.api.notifications.prefs()', {
sdk: function () {
if (typeof sw.api.notifications.prefs === 'function') return sw.api.notifications.prefs();
throw new Error('sw.api.notifications.prefs is ' + typeof sw.api.notifications.prefs);
},
raw: { method: 'GET', path: '/notifications/preferences' },
validate: function () { /* existence is sufficient */ }
});
// ═══════════════════════════════════════════════════════════
// USERS
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'users', 'sw.api.users.search(q)', {
sdk: function () { return sw.api.users.search('admin'); },
raw: { method: 'GET', path: '/users/search?q=admin' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ═══════════════════════════════════════════════════════════
// PRESENCE
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'presence', 'sw.api.presence.heartbeat()', {
sdk: function () { return sw.api.presence.heartbeat(); },
raw: { method: 'POST', path: '/presence/heartbeat', body: {} },
validate: function () { /* 200 OK is sufficient */ }
});
// ═══════════════════════════════════════════════════════════
// USAGE
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'usage', 'sw.api.usage.mine()', {
sdk: function () { return sw.api.usage.mine(); },
raw: { method: 'GET', path: '/usage' },
validate: function (r) { T.assert(typeof r === 'object' || Array.isArray(r), 'expected object or array'); }
});
// ═══════════════════════════════════════════════════════════
// GROUPS
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'groups', 'sw.api.groups.mine()', {
sdk: function () { return sw.api.groups.mine(); },
raw: { method: 'GET', path: '/groups/mine' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ═══════════════════════════════════════════════════════════
// TOOLS
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'tools', 'sw.api.tools.list()', {
sdk: function () { return sw.api.tools.list(); },
raw: { method: 'GET', path: '/tools' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ═══════════════════════════════════════════════════════════
// DATA PORTABILITY (KNOWN BUG: deleteAccount wrong method+path)
// ═══════════════════════════════════════════════════════════
await T.test('misc', 'export', 'sw.api.dataPortability.exportMe()', {
sdk: function () { return sw.api.dataPortability.exportMe(); },
raw: { method: 'GET', path: '/export/me' },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); },
skip: 'empty DB export may 500 — expected in dev'
});
// deleteAccount: SDK uses POST /profile/delete, backend is DELETE /me
// We don't actually call delete — just verify the method exists
await T.test('misc', 'export', 'sw.api.dataPortability.deleteAccount exists', async function () {
T.assert(typeof sw.api.dataPortability.deleteAccount === 'function',
'deleteAccount should be a function');
// NOTE: SDK calls POST /profile/delete — backend is DELETE /me.
// This is a known SDK_BUG from the ICD drift audit (§A).
// We don't actually invoke it because it would delete the account.
});
});
})();

View File

@@ -0,0 +1,106 @@
/**
* SDK Test Runner — Domain: notes
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('notes', async function () {
var tag = 'sdkr-' + Date.now();
var noteId = null;
// ── List ──
await T.test('notes', 'list', 'sw.api.notes.list()', {
sdk: function () { return sw.api.notes.list(); },
raw: { method: 'GET', path: '/notes' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Create ──
await T.test('notes', 'crud', 'sw.api.notes.create()', {
sdk: function () {
return sw.api.notes.create({ title: tag + '-note', content: '# Test\nBody text' });
},
raw: { method: 'POST', path: '/notes', body: { title: tag + '-note', content: '# Test\nBody text' } },
validate: function (r) {
T.assertShape(r, T.S.note, 'note');
noteId = r.id;
T.registerCleanup(function () { if (noteId) return T.safeDelete('/notes/' + noteId); });
}
});
if (!noteId) return;
// ── Get ──
await T.test('notes', 'crud', 'sw.api.notes.get(id)', {
sdk: function () { return sw.api.notes.get(noteId); },
raw: { method: 'GET', path: '/notes/' + noteId },
validate: function (r) {
T.assertShape(r, T.S.note, 'note');
T.assert(r.id === noteId, 'id mismatch');
}
});
// ── Update ──
await T.test('notes', 'crud', 'sw.api.notes.update(id, data)', {
sdk: function () { return sw.api.notes.update(noteId, { title: tag + '-note-upd', content: 'updated' }); },
raw: { method: 'PUT', path: '/notes/' + noteId, body: { title: tag + '-note-upd', content: 'updated' } },
validate: function (r) {
T.assertShape(r, T.S.note, 'note');
}
});
// ── Search ──
await T.test('notes', 'search', 'sw.api.notes.search(query)', {
sdk: function () { return sw.api.notes.search(tag, 5); },
raw: { method: 'GET', path: '/notes/search?q=' + encodeURIComponent(tag) + '&limit=5' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Search Titles ──
await T.test('notes', 'search', 'sw.api.notes.searchTitles(query)', {
sdk: function () { return sw.api.notes.searchTitles(tag, 5); },
raw: { method: 'GET', path: '/notes/search-titles?q=' + encodeURIComponent(tag) + '&limit=5' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Folders ──
await T.test('notes', 'folders', 'sw.api.notes.folders()', {
sdk: function () { return sw.api.notes.folders(); },
raw: { method: 'GET', path: '/notes/folders' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Backlinks ──
await T.test('notes', 'links', 'sw.api.notes.backlinks(id)', {
sdk: function () { return sw.api.notes.backlinks(noteId); },
raw: { method: 'GET', path: '/notes/' + noteId + '/backlinks' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Graph ──
await T.test('notes', 'graph', 'sw.api.notes.graph()', {
sdk: function () { return sw.api.notes.graph(); },
raw: { method: 'GET', path: '/notes/graph' },
validate: function (r) { T.assert(typeof r === 'object' || Array.isArray(r), 'expected object or array'); }
});
// ── Delete ──
await T.test('notes', 'crud', 'sw.api.notes.del(id)', {
sdk: function () { return sw.api.notes.del(noteId); },
raw: { method: 'DELETE', path: '/notes/' + noteId },
validate: function () { noteId = null; }
});
});
})();

View File

@@ -0,0 +1,66 @@
/**
* SDK Test Runner — Domain: personas
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('personas', async function () {
var tag = 'sdkr-' + Date.now();
var personaId = null;
// Policy: allow_user_personas is toggled by fixtures.js.
// If fixtures didn't run (or user isn't admin), create will 403.
await T.test('personas', 'list', 'sw.api.personas.list()', {
sdk: function () { return sw.api.personas.list(); },
raw: { method: 'GET', path: '/personas' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
await T.test('personas', 'crud', 'sw.api.personas.create()', {
sdk: function () {
return sw.api.personas.create({
name: tag + '-persona', system_prompt: 'You are a test bot.',
scope: 'personal'
});
},
raw: { method: 'POST', path: '/personas', body: {
name: tag + '-persona', system_prompt: 'You are a test bot.',
scope: 'personal'
}},
validate: function (r) {
T.assertShape(r, T.S.persona, 'persona');
personaId = r.id;
T.registerCleanup(function () { if (personaId) return T.safeDelete('/personas/' + personaId); });
}
});
if (!personaId) return;
// NOTE: User-scoped personas only have LIST + CREATE routes.
// GET/PUT/DELETE /personas/:id are admin-only (ICD gap).
// Use admin endpoints for full CRUD testing.
await T.test('personas', 'crud', 'sw.api.admin.personas.update(id, data)', {
sdk: function () { return sw.api.admin.personas.update(personaId, { name: tag + '-pers-upd' }); },
raw: { method: 'PUT', path: '/admin/personas/' + personaId, body: { name: tag + '-pers-upd' } },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); }
});
// ── KBs ──
await T.test('personas', 'kbs', 'sw.api.admin.personas.kbs(id)', {
sdk: function () { return sw.api.admin.personas.kbs(personaId); },
raw: { method: 'GET', path: '/admin/personas/' + personaId + '/knowledge-bases' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
await T.test('personas', 'crud', 'sw.api.admin.personas.del(id)', {
sdk: function () { return sw.api.admin.personas.del(personaId); },
raw: { method: 'DELETE', path: '/admin/personas/' + personaId },
validate: function () { personaId = null; }
});
});
})();

View File

@@ -0,0 +1,134 @@
/**
* SDK Test Runner — Domain: projects
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('projects', async function () {
var tag = 'sdkr-' + Date.now();
var projectId = null;
var channelId = null;
var noteId = null;
// ── List ──
await T.test('projects', 'list', 'sw.api.projects.list()', {
sdk: function () { return sw.api.projects.list(); },
raw: { method: 'GET', path: '/projects' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
// ── Create ──
await T.test('projects', 'crud', 'sw.api.projects.create()', {
sdk: function () {
return sw.api.projects.create({ name: tag + '-proj', description: 'sdk test' });
},
raw: { method: 'POST', path: '/projects', body: { name: tag + '-proj', description: 'sdk test' } },
validate: function (r) {
T.assertShape(r, T.S.project, 'project');
projectId = r.id;
T.registerCleanup(function () { if (projectId) return T.safeDelete('/projects/' + projectId); });
}
});
if (!projectId) return;
// ── Get ──
await T.test('projects', 'crud', 'sw.api.projects.get(id)', {
sdk: function () { return sw.api.projects.get(projectId); },
raw: { method: 'GET', path: '/projects/' + projectId },
validate: function (r) {
T.assertShape(r, T.S.project, 'project');
T.assert(r.id === projectId, 'id mismatch');
}
});
// ── Update ──
await T.test('projects', 'crud', 'sw.api.projects.update(id, data)', {
sdk: function () { return sw.api.projects.update(projectId, { name: tag + '-proj-upd' }); },
raw: { method: 'PUT', path: '/projects/' + projectId, body: { name: tag + '-proj-upd' } },
validate: function (r) {
T.assertShape(r, T.S.project, 'project');
}
});
// ── Channel association ──
await T.test('projects', 'assoc', 'sw.api.projects.addChannel()', async function () {
var ch = await T.raw.post('/channels', { title: tag + '-proj-ch', type: 'direct' });
channelId = ch.id;
T.registerCleanup(function () { if (channelId) return T.safeDelete('/channels/' + channelId); });
});
if (channelId) {
await T.test('projects', 'assoc', 'sw.api.projects.addChannel(id, chId)', {
sdk: function () { return sw.api.projects.addChannel(projectId, channelId, 0); },
raw: { method: 'POST', path: '/projects/' + projectId + '/channels',
body: { channel_id: channelId, position: 0 } },
validate: function () { /* ok */ }
});
await T.test('projects', 'assoc', 'sw.api.projects.channels(id)', {
sdk: function () { return sw.api.projects.channels(projectId); },
raw: { method: 'GET', path: '/projects/' + projectId + '/channels' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
await T.test('projects', 'assoc', 'sw.api.projects.removeChannel()', {
sdk: function () { return sw.api.projects.removeChannel(projectId, channelId); },
raw: { method: 'DELETE', path: '/projects/' + projectId + '/channels/' + channelId },
validate: function () { /* ok */ }
});
}
// ── Note association ──
await T.test('projects', 'assoc', 'create scratch note for project', async function () {
var n = await T.raw.post('/notes', { title: tag + '-proj-note', content: 'test' });
noteId = n.id;
T.registerCleanup(function () { if (noteId) return T.safeDelete('/notes/' + noteId); });
});
if (noteId) {
await T.test('projects', 'assoc', 'sw.api.projects.addNote()', {
sdk: function () { return sw.api.projects.addNote(projectId, noteId); },
raw: { method: 'POST', path: '/projects/' + projectId + '/notes',
body: { note_id: noteId } },
validate: function () { /* ok */ }
});
await T.test('projects', 'assoc', 'sw.api.projects.notes(id)', {
sdk: function () { return sw.api.projects.notes(projectId); },
raw: { method: 'GET', path: '/projects/' + projectId + '/notes' },
validate: function (r) { T.assert(Array.isArray(r), 'expected array'); }
});
await T.test('projects', 'assoc', 'sw.api.projects.removeNote()', {
sdk: function () { return sw.api.projects.removeNote(projectId, noteId); },
raw: { method: 'DELETE', path: '/projects/' + projectId + '/notes/' + noteId },
validate: function () { /* ok */ }
});
}
// ── Files ──
await T.test('projects', 'files', 'sw.api.projects.files(id)', {
sdk: function () { return sw.api.projects.files(projectId); },
raw: { method: 'GET', path: '/projects/' + projectId + '/files' },
validate: function (r) { T.assert(typeof r === 'object' || Array.isArray(r), 'expected obj/arr'); }
});
// ── Delete ──
await T.test('projects', 'crud', 'sw.api.projects.del(id)', {
sdk: function () { return sw.api.projects.del(projectId); },
raw: { method: 'DELETE', path: '/projects/' + projectId },
validate: function () { projectId = null; }
});
});
})();

View File

@@ -0,0 +1,107 @@
/**
* SDK Test Runner — Domain: tasks
*
* KNOWN BUGS:
* sw.api.tasks.start(id) → POST /tasks/:id/start (backend: /tasks/:id/run)
* sw.api.tasks.stop(id) → POST /tasks/:id/stop (backend: /tasks/:id/kill)
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('tasks', async function () {
var tag = 'sdkr-' + Date.now();
var taskId = null;
// ── List ──
await T.test('tasks', 'list', 'sw.api.tasks.list()', {
sdk: function () { return sw.api.tasks.list(); },
raw: { method: 'GET', path: '/tasks' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Create ──
await T.test('tasks', 'crud', 'sw.api.tasks.create()', {
sdk: function () {
return sw.api.tasks.create({
name: tag + '-task', task_type: 'prompt',
schedule: '@daily', user_prompt: 'SDK test prompt', is_active: false
});
},
raw: { method: 'POST', path: '/tasks', body: {
name: tag + '-task', task_type: 'prompt',
schedule: '@daily', user_prompt: 'SDK test prompt', is_active: false
}},
validate: function (r) {
T.assertShape(r, T.S.task, 'task');
taskId = r.id;
T.registerCleanup(function () { if (taskId) return T.safeDelete('/tasks/' + taskId); });
}
});
if (!taskId) return;
// ── Get ──
await T.test('tasks', 'crud', 'sw.api.tasks.get(id)', {
sdk: function () { return sw.api.tasks.get(taskId); },
raw: { method: 'GET', path: '/tasks/' + taskId },
validate: function (r) {
T.assert(r.id === taskId, 'id mismatch');
}
});
// ── Update ──
await T.test('tasks', 'crud', 'sw.api.tasks.update(id, data)', {
sdk: function () { return sw.api.tasks.update(taskId, { name: tag + '-task-upd' }); },
raw: { method: 'PUT', path: '/tasks/' + taskId, body: { name: tag + '-task-upd' } },
validate: function (r) { T.assert(typeof r === 'object', 'expected object'); }
});
// ── Start ──
await T.test('tasks', 'lifecycle', 'sw.api.tasks.start(id)', {
sdk: function () {
if (sw.api.tasks.start) return sw.api.tasks.start(taskId);
throw new Error('sw.api.tasks.start not defined');
},
raw: { method: 'POST', path: '/tasks/' + taskId + '/run', body: {} },
validate: function () { /* 200/202 is sufficient */ }
});
// ── Stop ── (404 expected when no active run)
await T.test('tasks', 'lifecycle', 'sw.api.tasks.stop(id)', {
sdk: function () {
if (sw.api.tasks.stop) return sw.api.tasks.stop(taskId);
throw new Error('sw.api.tasks.stop not defined');
},
raw: { method: 'POST', path: '/tasks/' + taskId + '/kill', body: {} },
validate: function () { /* 200/202 is sufficient */ },
skip: 'no active run to cancel — 404 expected without task execution'
});
// ── Runs ──
await T.test('tasks', 'runs', 'sw.api.tasks.runs(id)', {
sdk: function () {
if (sw.api.tasks.runs) return sw.api.tasks.runs(taskId);
throw new Error('sw.api.tasks.runs not defined');
},
raw: { method: 'GET', path: '/tasks/' + taskId + '/runs' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Delete ──
await T.test('tasks', 'crud', 'sw.api.tasks.del(id)', {
sdk: function () { return sw.api.tasks.del(taskId); },
raw: { method: 'DELETE', path: '/tasks/' + taskId },
validate: function () { taskId = null; }
});
});
})();

View File

@@ -0,0 +1,93 @@
/**
* SDK Test Runner — Domain: workflows
*
* Tests: list, create, get, update, delete, stages.
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('workflows', async function () {
var tag = 'sdkr-' + Date.now();
var wfId = null;
// ── List ──
await T.test('workflows', 'list', 'sw.api.workflows.list() returns array', {
sdk: function () { return sw.api.workflows.list(); },
raw: { method: 'GET', path: '/workflows' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Create ──
await T.test('workflows', 'crud', 'sw.api.workflows.create()', {
sdk: function () {
return sw.api.workflows.create({
name: tag + '-wf', slug: tag + '-wf',
entry_mode: 'team_only', scope: 'personal'
});
},
raw: { method: 'POST', path: '/workflows', body: {
name: tag + '-wf', slug: tag + '-wf',
entry_mode: 'team_only', scope: 'personal'
}},
validate: function (r) {
T.assertShape(r, T.S.workflow, 'workflow');
wfId = r.id;
T.registerCleanup(function () { if (wfId) return T.safeDelete('/workflows/' + wfId); });
}
});
if (!wfId) return;
// ── Get ──
await T.test('workflows', 'crud', 'sw.api.workflows.get(id)', {
sdk: function () { return sw.api.workflows.get(wfId); },
raw: { method: 'GET', path: '/workflows/' + wfId },
validate: function (r) {
T.assertShape(r, T.S.workflow, 'workflow');
T.assert(r.id === wfId, 'id mismatch');
}
});
// ── Update (KNOWN BUG: SDK uses PUT, backend expects PATCH) ──
await T.test('workflows', 'crud', 'sw.api.workflows.update(id, data)', {
sdk: function () {
return sw.api.workflows.update(wfId, { name: tag + '-wf-updated' });
},
raw: { method: 'PATCH', path: '/workflows/' + wfId,
body: { name: tag + '-wf-updated' } },
validate: function (r) {
T.assertShape(r, T.S.workflow, 'workflow');
T.assert(r.name === tag + '-wf-updated', 'name not updated');
}
});
// ── Stages ──
var stageId = null;
// stages.list via SDK (if wired) vs raw
await T.test('workflows', 'stages', 'GET /workflows/:id/stages (raw — SDK may not have this)', {
sdk: function () {
// SDK may or may not expose stages — test raw path
if (sw.api.workflows.stages) return sw.api.workflows.stages(wfId);
throw new Error('sw.api.workflows.stages not defined');
},
raw: { method: 'GET', path: '/workflows/' + wfId + '/stages' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Delete ──
await T.test('workflows', 'crud', 'sw.api.workflows.del(id)', {
sdk: function () { return sw.api.workflows.del(wfId); },
raw: { method: 'DELETE', path: '/workflows/' + wfId },
validate: function () { wfId = null; }
});
});
})();

View File

@@ -0,0 +1,83 @@
/**
* SDK Test Runner — Domain: workspaces
*
* Tests: list, create, get, update, delete, files.
*/
(function () {
'use strict';
var T = window.SDKR;
if (!T) return;
T.registerDomain('workspaces', async function () {
var tag = 'sdkr-' + Date.now();
var wsId = null;
// Get current user for owner_type/owner_id
var me = await sw.api.profile.get();
var userId = me.id;
// ── List ──
await T.test('workspaces', 'list', 'sw.api.workspaces.list()', {
sdk: function () { return sw.api.workspaces.list(); },
raw: { method: 'GET', path: '/workspaces' },
validate: function (r) { var arr = T.unwrapList(r); T.assert(arr.length >= 0, 'expected list'); }
});
// ── Create ──
await T.test('workspaces', 'crud', 'sw.api.workspaces.create()', {
sdk: function () {
return sw.api.workspaces.create({ name: tag + '-ws', owner_type: 'user', owner_id: userId });
},
raw: { method: 'POST', path: '/workspaces', body: { name: tag + '-ws', owner_type: 'user', owner_id: userId } },
validate: function (r) {
T.assertShape(r, T.S.workspace, 'workspace');
wsId = r.id;
T.registerCleanup(function () { if (wsId) return T.safeDelete('/workspaces/' + wsId); });
}
});
if (!wsId) return;
// ── Get ──
await T.test('workspaces', 'crud', 'sw.api.workspaces.get(id)', {
sdk: function () { return sw.api.workspaces.get(wsId); },
raw: { method: 'GET', path: '/workspaces/' + wsId },
validate: function (r) {
T.assertShape(r, T.S.workspace, 'workspace');
T.assert(r.id === wsId, 'id mismatch');
}
});
// ── Update (KNOWN BUG: SDK uses PUT, backend expects PATCH) ──
await T.test('workspaces', 'crud', 'sw.api.workspaces.update(id, data)', {
sdk: function () {
return sw.api.workspaces.update(wsId, { name: tag + '-ws-updated' });
},
raw: { method: 'PATCH', path: '/workspaces/' + wsId,
body: { name: tag + '-ws-updated' } },
validate: function (r) {
T.assertShape(r, T.S.workspace, 'workspace');
}
});
// ── Files list ──
await T.test('workspaces', 'files', 'sw.api.workspaces.files(id)', {
sdk: function () { return sw.api.workspaces.files(wsId); },
raw: { method: 'GET', path: '/workspaces/' + wsId + '/files' },
validate: function (r) { T.assert(typeof r === 'object' || Array.isArray(r), 'expected object or array'); }
});
// ── Delete ──
await T.test('workspaces', 'crud', 'sw.api.workspaces.del(id)', {
sdk: function () { return sw.api.workspaces.del(wsId); },
raw: { method: 'DELETE', path: '/workspaces/' + wsId },
validate: function () { wsId = null; }
});
});
})();