← Public packages
@tannerlinsley/skylight
Read and manage Skylight calendars, profiles, chores, routines, lists, meals, rewards, photos and settings.
tests/operations.test.js
126 lines · 8.0 KB · JavaScriptimport test from 'node:test';
import assert from 'node:assert/strict';
import { createOperations } from '../operations.js';
const file = { name: 'fixture.png', base64: 'YWJj' };
function sample(schema) {
if (Array.isArray(schema.type)) return sample({ ...schema, type: schema.type[0] });
if (schema.const !== undefined) return schema.const;
if (schema.enum) return schema.enum[0];
if (schema.anyOf) return sample(schema.anyOf[0]);
if (schema.pattern?.includes('d{4}')) return '2026-09-22';
if (schema.type === 'object') return Object.fromEntries((schema.required ?? []).map(k => [k, sample(schema.properties[k])]));
if (schema.type === 'array') return [sample(schema.items)];
if (schema.type === 'number' || schema.type === 'integer') return 1;
if (schema.type === 'boolean') return false;
if (schema.type === 'string') return 'fixture';
return {};
}
function setup() {
const requests = [], uploads = [];
const client = {
resolveFrameId() { throw new Error('Writes must not auto-discover frames'); },
async request(method, path, options) {
requests.push({ method, path, options });
if (path.endsWith('cloud_upload_credentials')) return { data: { credentials: { access_key_id: 'test', secret_access_key: 'test', session_token: 'test' }, region: 'us-east-1', bucket: 'test-bucket', key_prefix: 'test/' } };
if (method === 'GET' && path.endsWith('/list_items')) return { data: [{ id: 'fixture', type: 'item', attributes: {} }] };
return { data: { id: 'fixture', type: 'fixture', attributes: {}, message_ids: ['fixture'] } };
},
};
return { dispatch: createOperations(client, {}, async params => { uploads.push(params); return 'test-etag'; }), requests, uploads };
}
function argsFor(tool) {
const args = sample(tool.inputSchema);
if (tool.inputSchema.properties.frameId) args.frameId = 'frame-test';
if (tool.inputSchema.properties.file) args.file = file;
if (tool.name === 'skylight_link_apple_calendar') args.app_specific_password = '{{secret:appleAppPassword}}';
return args;
}
test('All 114 upstream operations, with 77 writes, are discoverable with schemas', async () => {
const { dispatch } = setup();
const tools = await dispatch({ operation: 'tools' });
assert.equal(tools.length, 114);
assert.equal(tools.filter(t => !t.readOnly).length, 77);
assert.equal(new Set(tools.map(t => t.name)).size, 114);
assert.ok(tools.every(t => t.inputSchema.type === 'object'));
});
test('Every write previews without any API request or upload, including inner confirm flags', async () => {
const { dispatch, requests, uploads } = setup();
for (const tool of await dispatch({ operation: 'tools', mode: 'write' })) {
const args = argsFor(tool);
if (tool.inputSchema.properties.confirm) args.confirm = true;
const preview = await dispatch({ operation: tool.name, args });
assert.equal(preview.dryRun, true, tool.name);
assert.match(preview.previewToken, /^[0-9a-f]{64}$/);
assert.equal(requests.length, 0, tool.name);
assert.equal(uploads.length, 0, tool.name);
}
});
test('Confirmed writes exercise every upstream handler against the mock API', async () => {
for (const tool of await setup().dispatch({ operation: 'tools', mode: 'write' })) {
const { dispatch, requests, uploads } = setup();
const args = argsFor(tool);
const preview = await dispatch({ operation: tool.name, args });
try { await dispatch({ operation: tool.name, args, confirm: true, previewToken: preview.previewToken }); }
catch (error) { throw new Error(tool.name + ': ' + error.message); }
assert.ok(requests.some(r => r.method !== 'GET') || uploads.length > 0, tool.name);
}
});
test('Wrong/missing preview token, changed args, implicit frames and unknown fields cannot write', async () => {
const { dispatch, requests } = setup();
const operation = 'skylight_delete_chore';
const args = { id: 'chore-test', frameId: 'frame-test', apply_to: 'all' };
const preview = await dispatch({ operation, args });
await assert.rejects(dispatch({ operation, args, confirm: true }), /previewToken/);
await assert.rejects(dispatch({ operation, args: { ...args, id: 'other' }, confirm: true, previewToken: preview.previewToken }), /previewToken/);
await assert.rejects(dispatch({ operation: 'skylight_create_chore', args: { summary: 'Test', category_id: 'member' } }), /frameId/);
await assert.rejects(dispatch({ operation, args: { ...args, surprise: true } }));
const dry = await dispatch({ operation, args, confirm: true, previewToken: preview.previewToken, dryRun: true });
assert.equal(dry.dryRun, true); assert.equal(dry.bulk, true);
assert.equal(requests.length, 0);
});
test('Representative edits preserve exact method, endpoint and body through upstream code', async () => {
const examples = [
['skylight_update_category', { id: 'member', color: '#123456' }, 'PUT', '/frames/frame-test/categories/member', { color: '#123456' }],
['skylight_create_chore', { summary: 'Make bed', category_id: 'member' }, 'POST', '/frames/frame-test/chores', { summary: 'Make bed', category_id: 'member' }],
['skylight_update_event', { id: 'event', category_ids: ['member'] }, 'PUT', '/frames/frame-test/calendar_events/event', { category_ids: ['member'] }],
['skylight_delete_event', { id: 'event' }, 'DELETE', '/frames/frame-test/calendar_events/event', undefined],
];
for (const [operation, partial, method, path, body] of examples) {
const { dispatch, requests } = setup(); const args = { ...partial, frameId: 'frame-test' };
const p = await dispatch({ operation, args });
await dispatch({ operation, args, confirm: true, previewToken: p.previewToken });
assert.equal(requests[0].method, method); assert.equal(requests[0].path, path); assert.deepEqual(requests[0].options?.body, body);
}
});
test('File uploads use bytes, redact preview payloads, and keep temporary credentials out of results', async () => {
const { dispatch, requests, uploads } = setup();
const args = { file, frameId: 'frame-test' }, operation = 'skylight_upload_photo';
const p = await dispatch({ operation, args });
assert.equal(p.args.file.bytes, 3); assert.equal(p.args.file.base64, undefined);
const r = await dispatch({ operation, args, confirm: true, previewToken: p.previewToken });
assert.equal(uploads[0].body.toString(), 'abc');
assert.equal(requests.at(-1).path, '/messages/uploads');
assert.ok(!JSON.stringify(r).includes('secret_access_key'));
await assert.rejects(dispatch({ operation, args: { ...args, file: { name: '../secrets.txt', base64: 'YWJj' } } }));
});
test('Apple password requires a stored secret and previews never echo a resolvable placeholder', async () => {
const { dispatch } = setup(); const operation = 'skylight_link_apple_calendar';
await assert.rejects(dispatch({ operation, args: { frameId: 'frame-test', email: 'test@example.com', app_specific_password: 'plaintext' } }), /Kody secret/);
const p = await dispatch({ operation, args: { frameId: 'frame-test', email: 'test@example.com', app_specific_password: '{{secret:appleAppPassword}}' } });
assert.equal(p.args.app_specific_password, '[stored credential]');
});
test('Clearing a list aborts if its reviewed membership changed', async () => {
const { dispatch, requests } = setup();
const operation = 'skylight_clear_list', args = { frameId: 'frame-test', listId: 'list-test', item_ids: ['old-item'] };
const p = await dispatch({ operation, args });
await assert.rejects(dispatch({ operation, args, confirm: true, previewToken: p.previewToken }), /contents changed/);
assert.ok(requests.every(r => r.method === 'GET'));
});
test('Avatar transport sends supplied bytes in multipart form without reading local files', async () => {
const { dispatch, requests } = setup();
const operation = 'skylight_set_member_avatar', args = { frameId: 'frame-test', id: 'member', file };
const p = await dispatch({ operation, args });
await dispatch({ operation, args, confirm: true, previewToken: p.previewToken });
assert.equal(requests[0].method, 'PUT');
assert.equal(await requests[0].options.formData.get('profile_picture').text(), 'abc');
});