clew-code 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -4
- package/bin/clew.cjs +2 -2
- package/dist/main.js +2015 -2015
- package/package.json +5 -2
- package/scripts/auto-close-duplicates.ts +277 -0
- package/scripts/backfill-duplicate-comments.ts +213 -0
- package/scripts/codegraph.ts +221 -0
- package/scripts/comment-on-duplicates.sh +95 -0
- package/scripts/edit-issue-labels.sh +84 -0
- package/scripts/final-peer-rename.mjs +46 -0
- package/scripts/fix-encoding.mjs +83 -0
- package/scripts/fix-inconsistencies.mjs +67 -0
- package/scripts/generate-docs.ts +307 -0
- package/scripts/gh.sh +96 -0
- package/scripts/install.ps1 +40 -0
- package/scripts/install.sh +65 -0
- package/scripts/issue-lifecycle.ts +38 -0
- package/scripts/lifecycle-comment.ts +53 -0
- package/scripts/normalize-html.mjs +37 -0
- package/scripts/preload.ts +159 -0
- package/scripts/rename-content-peer-to-swarm.mjs +67 -0
- package/scripts/rename-hooks-mesh.mjs +6 -0
- package/scripts/rename-peer-to-swarm.mjs +62 -0
- package/scripts/run_devcontainer_claude_code.ps1 +152 -0
- package/scripts/scrape.py +82 -0
- package/scripts/session.ts +195 -0
- package/scripts/sweep.ts +168 -0
- package/scripts/write-discovery-test.cjs +93 -0
- package/scripts/write-discovery-test2.cjs +65 -0
- package/scripts/write-server-final.cjs +108 -0
- package/scripts/write-server-test.cjs +69 -0
- package/scripts/write-server-test2.cjs +99 -0
- package/scripts/write-server-test3.cjs +59 -0
- package/scripts/write-server-test4.cjs +108 -0
- package/scripts/write-server-v2.cjs +142 -0
- package/scripts/write-server-v2b.cjs +129 -0
- package/scripts/write-server-v2c.cjs +136 -0
- package/scripts/write-store-test.cjs +12 -0
- package/scripts/write-store-test2.cjs +163 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('GET /mesh-info', async () => {
|
|
5
|
+
const port = await server.start(mi({ id:'info-test' }));
|
|
6
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
7
|
+
expect(res.status).toBe(200);
|
|
8
|
+
const d = await res.json();
|
|
9
|
+
expect(d.id).toBe('info-test');
|
|
10
|
+
expect(d.isBusy).toBeFalse();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('POST /mesh-msg fires callback', async () => {
|
|
14
|
+
let m = null;
|
|
15
|
+
server.setCallbacks({ onMessage: msg => { m = msg; } });
|
|
16
|
+
const port = await server.start(mi());
|
|
17
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
18
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
19
|
+
body: JSON.stringify({ from:'p2', fromName:'w2', text:'hello' }),
|
|
20
|
+
});
|
|
21
|
+
expect(res.status).toBe(200);
|
|
22
|
+
expect((await res.json()).ok).toBeTrue();
|
|
23
|
+
expect(m.from).toBe('p2');
|
|
24
|
+
expect(m.text).toBe('hello');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('POST /mesh-msg chunk info', async () => {
|
|
28
|
+
let m = null;
|
|
29
|
+
server.setCallbacks({ onMessage: msg => { m = msg; } });
|
|
30
|
+
const port = await server.start(mi());
|
|
31
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
32
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
33
|
+
body: JSON.stringify({ from:'p2', text:'c', chunkGroup:'g1', chunkIndex:0, chunkTotal:2 }),
|
|
34
|
+
});
|
|
35
|
+
expect(m.chunkGroup).toBe('g1');
|
|
36
|
+
expect(m.chunkIndex).toBe(0);
|
|
37
|
+
expect(m.chunkTotal).toBe(2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('POST /mesh-msg defaults', async () => {
|
|
41
|
+
let m = null;
|
|
42
|
+
server.setCallbacks({ onMessage: msg => { m = msg; } });
|
|
43
|
+
const port = await server.start(mi());
|
|
44
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
45
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
46
|
+
body: JSON.stringify({}),
|
|
47
|
+
});
|
|
48
|
+
expect(m.from).toBe('unknown');
|
|
49
|
+
expect(m.text).toBe('');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('POST /mesh-todo fires callback', async () => {
|
|
53
|
+
let t = null;
|
|
54
|
+
server.setCallbacks({ onTodo: todo => { t = todo; } });
|
|
55
|
+
const port = await server.start(mi());
|
|
56
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
|
|
57
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
58
|
+
body: JSON.stringify({ from:'p2', message:'fix' }),
|
|
59
|
+
});
|
|
60
|
+
expect(res.status).toBe(200);
|
|
61
|
+
expect((await res.json()).ok).toBeTrue();
|
|
62
|
+
expect(t.message).toBe('fix');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('POST /mesh-todo defaults', async () => {
|
|
66
|
+
let t = null;
|
|
67
|
+
server.setCallbacks({ onTodo: todo => { t = todo; } });
|
|
68
|
+
const port = await server.start(mi());
|
|
69
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
|
|
70
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
71
|
+
body: JSON.stringify({}),
|
|
72
|
+
});
|
|
73
|
+
expect(t.from).toBe('unknown');
|
|
74
|
+
expect(t.message).toBe('');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('POST /mesh-exec with onExec', async () => {
|
|
78
|
+
server.setCallbacks({ onExec: async () => ({ stdout:'ok', stderr:'', exitCode:0 }) });
|
|
79
|
+
const port = await server.start(mi());
|
|
80
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
81
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
82
|
+
body: JSON.stringify({ command:'echo hi', from:'t' }),
|
|
83
|
+
});
|
|
84
|
+
expect(res.status).toBe(200);
|
|
85
|
+
expect((await res.json()).running).toBeTrue();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('POST /mesh-exec 501 without onExec', async () => {
|
|
89
|
+
const port = await server.start(mi());
|
|
90
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
91
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
92
|
+
body: JSON.stringify({ command:'hi', from:'t' }),
|
|
93
|
+
});
|
|
94
|
+
expect(res.status).toBe(501);
|
|
95
|
+
expect((await res.json()).stderr).toBe('Exec not supported');
|
|
96
|
+
});
|
|
97
|
+
`;
|
|
98
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
99
|
+
console.log('MeshServer.test.ts part 2 appended');
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('POST /mesh-exec queues when busy', async () => {
|
|
5
|
+
let resolve;
|
|
6
|
+
server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
|
|
7
|
+
const port = await server.start(mi());
|
|
8
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'long', from:'t' }) });
|
|
9
|
+
expect(server.isBusy).toBeTrue();
|
|
10
|
+
const r2 = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'q', from:'t' }) });
|
|
11
|
+
expect(r2.status).toBe(202);
|
|
12
|
+
expect((await r2.json()).queued).toBeTrue();
|
|
13
|
+
expect(server.queueDepth).toBe(1);
|
|
14
|
+
if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
|
|
15
|
+
await new Promise(r => setTimeout(r, 50));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('POST /mesh-exec 503 queue full', async () => {
|
|
19
|
+
let resolve;
|
|
20
|
+
server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
|
|
21
|
+
const port = await server.start(mi());
|
|
22
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'first', from:'t' }) });
|
|
23
|
+
const max = server.maxQueueSize || 50;
|
|
24
|
+
for (let i=0; i<max; i++) {
|
|
25
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:\`t-\${i}\`, from:'t' }) });
|
|
26
|
+
}
|
|
27
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'x', from:'t' }) });
|
|
28
|
+
expect(r.status).toBe(503);
|
|
29
|
+
expect((await r.json()).error).toBe('Queue full');
|
|
30
|
+
if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
|
|
31
|
+
await new Promise(r => setTimeout(r, 50));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('GET /mesh-queue-status', async () => {
|
|
35
|
+
const port = await server.start(mi());
|
|
36
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-status\`);
|
|
37
|
+
expect(r.status).toBe(200);
|
|
38
|
+
const d = await r.json();
|
|
39
|
+
expect(d.isBusy).toBeFalse();
|
|
40
|
+
expect(d.queueDepth).toBe(0);
|
|
41
|
+
expect(d.currentTask).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('POST /mesh-queue-cancel', async () => {
|
|
45
|
+
let resolve;
|
|
46
|
+
server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
|
|
47
|
+
const port = await server.start(mi());
|
|
48
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'first', from:'t' }) });
|
|
49
|
+
const qr = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'x', from:'t' }) });
|
|
50
|
+
const tid = (await qr.json()).id;
|
|
51
|
+
const cr = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id: tid }) });
|
|
52
|
+
expect(cr.status).toBe(200);
|
|
53
|
+
expect((await cr.json()).ok).toBeTrue();
|
|
54
|
+
if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
|
|
55
|
+
await new Promise(r => setTimeout(r, 50));
|
|
56
|
+
});
|
|
57
|
+
`;
|
|
58
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
59
|
+
console.log('MeshServer.test.ts part 3a appended');
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('POST /mesh-queue-cancel 404', async () => {
|
|
5
|
+
const port = await server.start(mi());
|
|
6
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id:'nope' }) });
|
|
7
|
+
expect(r.status).toBe(404);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('POST /mesh-queue-cancel 400', async () => {
|
|
11
|
+
const port = await server.start(mi());
|
|
12
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({}) });
|
|
13
|
+
expect(r.status).toBe(400);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('POST /mesh-queue-cancel-all', async () => {
|
|
17
|
+
let resolve;
|
|
18
|
+
server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
|
|
19
|
+
const port = await server.start(mi());
|
|
20
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'first', from:'t' }) });
|
|
21
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'t1', from:'t' }) });
|
|
22
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'t2', from:'t' }) });
|
|
23
|
+
expect(server.queueDepth).toBe(2);
|
|
24
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel-all\`, { method:'POST' });
|
|
25
|
+
expect(r.status).toBe(200);
|
|
26
|
+
expect((await r.json()).cancelled).toBe(2);
|
|
27
|
+
if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
|
|
28
|
+
await new Promise(r => setTimeout(r, 50));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('cancelAllQueuedTasks when empty', () => {
|
|
32
|
+
expect(server.cancelAllQueuedTasks()).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('GET /mesh-events SSE', async () => {
|
|
36
|
+
const port = await server.start(mi());
|
|
37
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
38
|
+
expect(r.status).toBe(200);
|
|
39
|
+
expect(r.headers.get('content-type')).toContain('text/event-stream');
|
|
40
|
+
await r.body?.cancel();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('SSE connected event', async () => {
|
|
44
|
+
const port = await server.start(mi());
|
|
45
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
46
|
+
const reader = r.body?.getReader();
|
|
47
|
+
let data = '';
|
|
48
|
+
if (reader) { const {value} = await reader.read(); data = new TextDecoder().decode(value); }
|
|
49
|
+
expect(data).toContain('event: connected');
|
|
50
|
+
expect(data).toContain('"status":"ok"');
|
|
51
|
+
await r.body?.cancel();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('broadcastEvent sends SSE', async () => {
|
|
55
|
+
const port = await server.start(mi());
|
|
56
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
57
|
+
const reader = r.body?.getReader();
|
|
58
|
+
const dec = new TextDecoder();
|
|
59
|
+
if (reader) await reader.read();
|
|
60
|
+
server.broadcastEvent('new_message', { text:'sse test' });
|
|
61
|
+
let data = '';
|
|
62
|
+
if (reader) { const {value}=await reader.read(); data=dec.decode(value); }
|
|
63
|
+
expect(data).toContain('event: new_message');
|
|
64
|
+
expect(data).toContain('"text":"sse test"');
|
|
65
|
+
await r.body?.cancel();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('updatePeerInfo', async () => {
|
|
69
|
+
const port = await server.start(mi({ id:'orig' }));
|
|
70
|
+
server.updatePeerInfo({ hostname:'upd' });
|
|
71
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
72
|
+
const d = await r.json();
|
|
73
|
+
expect(d.hostname).toBe('upd');
|
|
74
|
+
expect(d.id).toBe('orig');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('extraInfo in mesh-info', async () => {
|
|
78
|
+
const port = await server.start(mi());
|
|
79
|
+
server.extraInfo = { cf: 'cv' };
|
|
80
|
+
expect((await (await fetch(\`http://127.0.0.1:\${port}/mesh-info\`)).json()).cf).toBe('cv');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('cancelQueuedTask false for unknown', () => {
|
|
84
|
+
expect(server.cancelQueuedTask('nope')).toBeFalse();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('getTasks', () => {
|
|
88
|
+
const t = server.getTasks();
|
|
89
|
+
expect(t).toHaveProperty('queue');
|
|
90
|
+
expect(t).toHaveProperty('current');
|
|
91
|
+
expect(t.queue).toEqual([]);
|
|
92
|
+
expect(t.current).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('unknown route 404', async () => {
|
|
96
|
+
const port = await server.start(mi());
|
|
97
|
+
expect((await fetch(\`http://127.0.0.1:\${port}/unknown\`)).status).toBe(404);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('malformed JSON 400', async () => {
|
|
101
|
+
const port = await server.start(mi());
|
|
102
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, { method:'POST', headers:{'Content-Type':'application/json'}, body:'not-json' });
|
|
103
|
+
expect(r.status).toBe(400);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
`;
|
|
107
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
108
|
+
console.log('MeshServer.test.ts completed');
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { MeshServer, getGlobalMeshServer } from './MeshServer.js';
|
|
5
|
+
|
|
6
|
+
function mi(o) { return { id:'tp', hostname:'th', ip:'127.0.0.1', port:0, cwd:'/r', version:'t', lastSeen:Date.now(), status:'online', ...o }; }
|
|
7
|
+
|
|
8
|
+
describe('MeshServer', () => {
|
|
9
|
+
let server;
|
|
10
|
+
beforeEach(() => { server = new MeshServer(); });
|
|
11
|
+
afterEach(() => { server.stop(); });
|
|
12
|
+
|
|
13
|
+
test('constructor creates instance', () => {
|
|
14
|
+
expect(server).toBeInstanceOf(MeshServer);
|
|
15
|
+
expect(server.port).toBe(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('constructor accepts callbacks', () => {
|
|
19
|
+
const s = new MeshServer({ onMessage: () => {} });
|
|
20
|
+
expect(s).toBeInstanceOf(MeshServer);
|
|
21
|
+
s.stop();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('getGlobalMeshServer singleton', () => {
|
|
25
|
+
expect(getGlobalMeshServer()).toBe(getGlobalMeshServer());
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('setCallbacks merges', () => {
|
|
29
|
+
server.setCallbacks({ onMessage: () => {} });
|
|
30
|
+
server.setCallbacks({ onTodo: () => {} });
|
|
31
|
+
expect(server).toBeInstanceOf(MeshServer);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('isBusy/queueDepth initially false/0', () => {
|
|
35
|
+
expect(server.isBusy).toBeFalse();
|
|
36
|
+
expect(server.queueDepth).toBe(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('getTodos empty, updateTodoStatus false for unknown', () => {
|
|
40
|
+
expect(server.getTodos()).toEqual([]);
|
|
41
|
+
expect(server.updateTodoStatus('x','done')).toBeFalse();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('start returns port', async () => {
|
|
45
|
+
const port = await server.start(mi());
|
|
46
|
+
expect(typeof port).toBe('number');
|
|
47
|
+
expect(port).toBeGreaterThan(0);
|
|
48
|
+
expect(server.port).toBe(port);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('start idempotent', async () => {
|
|
52
|
+
const p1 = await server.start(mi());
|
|
53
|
+
const p2 = await server.start(mi());
|
|
54
|
+
expect(p1).toBe(p2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('stop does not throw', () => {
|
|
58
|
+
expect(() => server.stop()).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('start-stop-start works', async () => {
|
|
62
|
+
await server.start(mi());
|
|
63
|
+
server.stop();
|
|
64
|
+
const p = await server.start(mi());
|
|
65
|
+
expect(p).toBeGreaterThan(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── HTTP Endpoints ─────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
test('GET /mesh-info returns peer info', async () => {
|
|
71
|
+
const port = await server.start(mi({ id:'info-test' }));
|
|
72
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
73
|
+
expect(res.status).toBe(200);
|
|
74
|
+
const d = await res.json();
|
|
75
|
+
expect(d.id).toBe('info-test');
|
|
76
|
+
expect(d.isBusy).toBeFalse();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('POST /mesh-msg fires callback', async () => {
|
|
80
|
+
let m = null;
|
|
81
|
+
server.setCallbacks({ onMessage: msg => { m = msg; } });
|
|
82
|
+
const port = await server.start(mi());
|
|
83
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
84
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
85
|
+
body: JSON.stringify({ from:'p2', fromName:'w2', text:'hello' }),
|
|
86
|
+
});
|
|
87
|
+
expect(res.status).toBe(200);
|
|
88
|
+
expect((await res.json()).ok).toBeTrue();
|
|
89
|
+
expect(m.from).toBe('p2');
|
|
90
|
+
expect(m.text).toBe('hello');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('POST /mesh-msg handles chunk info', async () => {
|
|
94
|
+
let m = null;
|
|
95
|
+
server.setCallbacks({ onMessage: msg => { m = msg; } });
|
|
96
|
+
const port = await server.start(mi());
|
|
97
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
98
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
99
|
+
body: JSON.stringify({ from:'p2', text:'c', chunkGroup:'g1', chunkIndex:0, chunkTotal:2 }),
|
|
100
|
+
});
|
|
101
|
+
expect(m.chunkGroup).toBe('g1');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('POST /mesh-msg with empty body uses defaults', async () => {
|
|
105
|
+
let m = null;
|
|
106
|
+
server.setCallbacks({ onMessage: msg => { m = msg; } });
|
|
107
|
+
const port = await server.start(mi());
|
|
108
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
109
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
110
|
+
body: JSON.stringify({}),
|
|
111
|
+
});
|
|
112
|
+
expect(m.from).toBe('unknown');
|
|
113
|
+
expect(m.text).toBe('');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('POST /mesh-todo fires callback', async () => {
|
|
117
|
+
let t = null;
|
|
118
|
+
server.setCallbacks({ onTodo: todo => { t = todo; } });
|
|
119
|
+
const port = await server.start(mi());
|
|
120
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
|
|
121
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
122
|
+
body: JSON.stringify({ from:'p2', message:'fix' }),
|
|
123
|
+
});
|
|
124
|
+
expect(res.status).toBe(200);
|
|
125
|
+
expect((await res.json()).ok).toBeTrue();
|
|
126
|
+
expect(t.message).toBe('fix');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('POST /mesh-todo empty body defaults', async () => {
|
|
130
|
+
let t = null;
|
|
131
|
+
server.setCallbacks({ onTodo: todo => { t = todo; } });
|
|
132
|
+
const port = await server.start(mi());
|
|
133
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
|
|
134
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
135
|
+
body: JSON.stringify({}),
|
|
136
|
+
});
|
|
137
|
+
expect(t.from).toBe('unknown');
|
|
138
|
+
expect(t.message).toBe('');
|
|
139
|
+
});
|
|
140
|
+
`;
|
|
141
|
+
fs.writeFileSync(p, code, 'utf8');
|
|
142
|
+
console.log('MeshServer.test.ts part 1 written');
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('POST /mesh-exec with onExec returns result', async () => {
|
|
5
|
+
let execCmd = '';
|
|
6
|
+
server.setCallbacks({ onExec: async (cmd) => {
|
|
7
|
+
execCmd = cmd;
|
|
8
|
+
return { stdout:'ok\\\\n', stderr:'', exitCode:0 };
|
|
9
|
+
}});
|
|
10
|
+
const port = await server.start(mi());
|
|
11
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
12
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
13
|
+
body: JSON.stringify({ command:'echo hi', from:'t' }),
|
|
14
|
+
});
|
|
15
|
+
expect(res.status).toBe(200);
|
|
16
|
+
const d = await res.json();
|
|
17
|
+
expect(d.running).toBeTrue();
|
|
18
|
+
expect(execCmd).toBe('echo hi');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('POST /mesh-exec returns 501 without onExec', async () => {
|
|
22
|
+
const port = await server.start(mi());
|
|
23
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
24
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
25
|
+
body: JSON.stringify({ command:'hi', from:'t' }),
|
|
26
|
+
});
|
|
27
|
+
expect(res.status).toBe(501);
|
|
28
|
+
expect((await res.json()).stderr).toBe('Exec not supported');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('POST /mesh-exec queues when busy', async () => {
|
|
32
|
+
let resolveExec;
|
|
33
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
34
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
35
|
+
const port = await server.start(mi());
|
|
36
|
+
|
|
37
|
+
// Start first exec (don't await - it won't complete until we resolve)
|
|
38
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
39
|
+
method:'POST', body: JSON.stringify({ command:'long', from:'t' }),
|
|
40
|
+
});
|
|
41
|
+
// Wait for server to process the request and mark busy
|
|
42
|
+
await new Promise(r => setTimeout(r, 200));
|
|
43
|
+
expect(server.isBusy).toBeTrue();
|
|
44
|
+
|
|
45
|
+
// Second exec should queue
|
|
46
|
+
const r2 = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
47
|
+
method:'POST', body: JSON.stringify({ command:'q', from:'t' }),
|
|
48
|
+
});
|
|
49
|
+
expect(r2.status).toBe(202);
|
|
50
|
+
const d2 = await r2.json();
|
|
51
|
+
expect(d2.queued).toBeTrue();
|
|
52
|
+
expect(server.queueDepth).toBe(1);
|
|
53
|
+
|
|
54
|
+
// Resolve first exec
|
|
55
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
56
|
+
const r1 = await p1;
|
|
57
|
+
expect(r1.status).toBe(200);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('POST /mesh-exec 503 queue full', async () => {
|
|
61
|
+
let resolveExec;
|
|
62
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
63
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
64
|
+
const port = await server.start(mi());
|
|
65
|
+
|
|
66
|
+
// Start first exec
|
|
67
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
68
|
+
method:'POST', body: JSON.stringify({ command:'first', from:'t' }),
|
|
69
|
+
});
|
|
70
|
+
await new Promise(r => setTimeout(r, 200));
|
|
71
|
+
expect(server.isBusy).toBeTrue();
|
|
72
|
+
|
|
73
|
+
// Fill queue
|
|
74
|
+
const max = (server).maxQueueSize || 50;
|
|
75
|
+
for (let i = 0; i < max; i++) {
|
|
76
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
77
|
+
method:'POST', body: JSON.stringify({ command:\`t-\${i}\`, from:'t' }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Next one should be rejected
|
|
82
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
83
|
+
method:'POST', body: JSON.stringify({ command:'x', from:'t' }),
|
|
84
|
+
});
|
|
85
|
+
expect(r.status).toBe(503);
|
|
86
|
+
expect((await r.json()).error).toBe('Queue full');
|
|
87
|
+
|
|
88
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
89
|
+
await p1;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('GET /mesh-queue-status returns queue state', async () => {
|
|
93
|
+
const port = await server.start(mi());
|
|
94
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-status\`);
|
|
95
|
+
expect(r.status).toBe(200);
|
|
96
|
+
const d = await r.json();
|
|
97
|
+
expect(d.isBusy).toBeFalse();
|
|
98
|
+
expect(d.queueDepth).toBe(0);
|
|
99
|
+
expect(d.currentTask).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('POST /mesh-queue-cancel cancels queued task', async () => {
|
|
103
|
+
let resolveExec;
|
|
104
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
105
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
106
|
+
const port = await server.start(mi());
|
|
107
|
+
|
|
108
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
109
|
+
method:'POST', body: JSON.stringify({ command:'first', from:'t' }),
|
|
110
|
+
});
|
|
111
|
+
await new Promise(r => setTimeout(r, 200));
|
|
112
|
+
|
|
113
|
+
// Queue a task, then cancel it
|
|
114
|
+
const qr = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
115
|
+
method:'POST', body: JSON.stringify({ command:'x', from:'t' }),
|
|
116
|
+
});
|
|
117
|
+
const tid = (await qr.json()).id;
|
|
118
|
+
const cr = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, {
|
|
119
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ id:tid }),
|
|
120
|
+
});
|
|
121
|
+
expect(cr.status).toBe(200);
|
|
122
|
+
expect((await cr.json()).ok).toBeTrue();
|
|
123
|
+
|
|
124
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
125
|
+
await p1;
|
|
126
|
+
});
|
|
127
|
+
`;
|
|
128
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
129
|
+
console.log('MeshServer.test.ts part 2 written');
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('POST /mesh-queue-cancel 404 for unknown', async () => {
|
|
5
|
+
const port = await server.start(mi());
|
|
6
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, {
|
|
7
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id:'nope' }),
|
|
8
|
+
});
|
|
9
|
+
expect(r.status).toBe(404);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('POST /mesh-queue-cancel 400 if id missing', async () => {
|
|
13
|
+
const port = await server.start(mi());
|
|
14
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, {
|
|
15
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({}),
|
|
16
|
+
});
|
|
17
|
+
expect(r.status).toBe(400);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('POST /mesh-queue-cancel-all', async () => {
|
|
21
|
+
let resolveExec;
|
|
22
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
23
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
24
|
+
const port = await server.start(mi());
|
|
25
|
+
|
|
26
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
27
|
+
method:'POST', body: JSON.stringify({ command:'first', from:'t' }),
|
|
28
|
+
});
|
|
29
|
+
await new Promise(r => setTimeout(r, 200));
|
|
30
|
+
|
|
31
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
32
|
+
method:'POST', body: JSON.stringify({ command:'t1', from:'t' }),
|
|
33
|
+
});
|
|
34
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
35
|
+
method:'POST', body: JSON.stringify({ command:'t2', from:'t' }),
|
|
36
|
+
});
|
|
37
|
+
expect(server.queueDepth).toBe(2);
|
|
38
|
+
|
|
39
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel-all\`, { method:'POST' });
|
|
40
|
+
expect(r.status).toBe(200);
|
|
41
|
+
expect((await r.json()).cancelled).toBe(2);
|
|
42
|
+
expect(server.queueDepth).toBe(0);
|
|
43
|
+
|
|
44
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
45
|
+
await p1;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('cancelAllQueuedTasks when empty', () => {
|
|
49
|
+
expect(server.cancelAllQueuedTasks()).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('GET /mesh-events SSE returns event-stream content type', async () => {
|
|
53
|
+
const port = await server.start(mi());
|
|
54
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
55
|
+
expect(r.status).toBe(200);
|
|
56
|
+
expect(r.headers.get('content-type')).toContain('text/event-stream');
|
|
57
|
+
await r.body?.cancel();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('SSE connected event contains status', async () => {
|
|
61
|
+
const port = await server.start(mi());
|
|
62
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
63
|
+
const reader = r.body?.getReader();
|
|
64
|
+
let data = '';
|
|
65
|
+
if (reader) {
|
|
66
|
+
const {value} = await reader.read();
|
|
67
|
+
data = new TextDecoder().decode(value);
|
|
68
|
+
reader.cancel();
|
|
69
|
+
}
|
|
70
|
+
expect(data).toContain('event: connected');
|
|
71
|
+
expect(data).toContain('"status":"ok"');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('broadcastEvent sends event to SSE clients', async () => {
|
|
75
|
+
const port = await server.start(mi());
|
|
76
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
77
|
+
const reader = r.body?.getReader();
|
|
78
|
+
const dec = new TextDecoder();
|
|
79
|
+
if (reader) {
|
|
80
|
+
// Read and discard connected event
|
|
81
|
+
await reader.read();
|
|
82
|
+
// Broadcast
|
|
83
|
+
server.broadcastEvent('new_message', { text:'sse test' });
|
|
84
|
+
// Read next event
|
|
85
|
+
const {value} = await reader.read();
|
|
86
|
+
const data = dec.decode(value);
|
|
87
|
+
expect(data).toContain('event: new_message');
|
|
88
|
+
expect(data).toContain('"text":"sse test"');
|
|
89
|
+
reader.cancel();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('updatePeerInfo updates stored peer info', async () => {
|
|
94
|
+
const port = await server.start(mi({ id:'orig' }));
|
|
95
|
+
server.updatePeerInfo({ hostname:'upd' });
|
|
96
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
97
|
+
const d = await r.json();
|
|
98
|
+
expect(d.hostname).toBe('upd');
|
|
99
|
+
expect(d.id).toBe('orig');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('extraInfo appears in mesh-info response', async () => {
|
|
103
|
+
const port = await server.start(mi());
|
|
104
|
+
server.extraInfo = { cf: 'cv' };
|
|
105
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
106
|
+
expect((await r.json()).cf).toBe('cv');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('cancelQueuedTask returns false for unknown', () => {
|
|
110
|
+
expect(server.cancelQueuedTask('nope')).toBeFalse();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('getTasks returns queue and current', () => {
|
|
114
|
+
const t = server.getTasks();
|
|
115
|
+
expect(t).toHaveProperty('queue');
|
|
116
|
+
expect(t).toHaveProperty('current');
|
|
117
|
+
expect(t.queue).toEqual([]);
|
|
118
|
+
expect(t.current).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('unknown route returns 404', async () => {
|
|
122
|
+
const port = await server.start(mi());
|
|
123
|
+
expect((await fetch(\`http://127.0.0.1:\${port}/unknown\`)).status).toBe(404);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('malformed JSON returns 400', async () => {
|
|
127
|
+
const port = await server.start(mi());
|
|
128
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
129
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body:'not-json',
|
|
130
|
+
});
|
|
131
|
+
expect(r.status).toBe(400);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
`;
|
|
135
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
136
|
+
console.log('MeshServer.test.ts completed');
|