const path = require('path');
const { startRelay } = require('./helpers/relay');
const { setupPanic, teardownPanic } = require('./helpers/setup');
const binPath = path.resolve(__dirname, '../../target/debug/beam');
const config = {
panicPort: 8765,
relayPort: 9100,
runId: Date.now().toString(36),
};
const { clients, alice, bob } = setupPanic({ numClients: 2, panicPort: config.panicPort });
describe('18. Who: SEA set() sync across sessions (headless)', function () {
this.timeout(300000);
it('Two clients connected to panic-server', function () {
return clients.atLeast(2);
});
it('BEAM relay started', function () {
this.timeout(10000);
startRelay({ binPath, port: config.relayPort });
return new Promise((resolve) => setTimeout(resolve, 2000));
});
it('Create Alice: auth + write first set entry', function () {
return alice.run(async function (test) {
test.async();
if (!globalThis.user) { await require('./helpers/gunboot').bootGun(test.props.relayPort); }
const alias = 'whoAlice' + test.props.runId;
user.create(alias, 'xyzabcmnopq', function (ack) {
if (ack.err || !ack.pub) { test.fail('create failed: ' + (ack.err || 'no pub')); return; }
globalThis.alicePub = ack.pub;
user.auth(alias, 'xyzabcmnopq', function (ack) {
if (ack.err || !ack.pub) { test.fail('auth failed: ' + (ack.err || 'no pub')); return; }
user.get('who').get('said').set({
what: 'Hello world!',
}, function (ack) {
if (ack.err) { test.fail('set failed: ' + ack.err); return; }
test.done();
});
});
});
setTimeout(() => test.fail('timeout — create+auth+set in 90s'), 90000);
}, { runId: config.runId, relayPort: config.relayPort });
});
it('Create Bob: auth only', function () {
return bob.run(async function (test) {
test.async();
if (!globalThis.user) { await require('./helpers/gunboot').bootGun(test.props.relayPort); }
const alias = 'whoBob' + test.props.runId;
user.create(alias, 'zyxcbaqponm', function (ack) {
if (ack.err || !ack.pub) { test.fail('create failed: ' + (ack.err || 'no pub')); return; }
user.auth(alias, 'zyxcbaqponm', function (ack) {
if (ack.err || !ack.pub) { test.fail('auth failed: ' + (ack.err || 'no pub')); return; }
test.done();
});
});
setTimeout(() => test.fail('timeout — create+auth bob in 90s'), 90000);
}, { runId: config.runId, relayPort: config.relayPort });
});
it('Bob finds Alice via ~@alias reverse index', function () {
return bob.run(async function (test) {
test.async();
if (!globalThis.user) { await require('./helpers/gunboot').bootGun(test.props.relayPort); }
const alias = 'whoAlice' + test.props.runId;
gun.get('~@' + alias).map().once(function (data) {
if (!data || !data.pub) { return; }
globalThis.ref = gun.get('~' + data.pub);
test.done();
});
setTimeout(() => test.fail('timeout — ~@alias discovery in 45s'), 45000);
}, { runId: config.runId, relayPort: config.relayPort });
});
it('Bob listens: collects first set item', function () {
return bob.run(async function (test) {
test.async();
if (!globalThis.user) { await require('./helpers/gunboot').bootGun(test.props.relayPort); }
globalThis.count = [];
globalThis.ref.get('who').get('said').map().once(function (data) {
if (!data) { return; }
globalThis.count.push(data);
if (globalThis.count.length - 1) { return; }
test.done();
});
setTimeout(() => test.fail('timeout — first set item in 30s'), 30000);
}, { relayPort: config.relayPort });
});
it('Alice re-auths (new session) + writes second set entry', function () {
return alice.run(async function (test) {
test.async();
if (!globalThis.user) { await require('./helpers/gunboot').bootGun(test.props.relayPort); }
const alias = 'whoAlice' + test.props.runId;
user.auth(alias, 'xyzabcmnopq', function (ack) {
if (ack.err || !ack.pub) { test.fail('re-auth failed: ' + (ack.err || 'no pub')); return; }
user.get('who').get('said').set({
what: 'AAA',
}, function (ack) {
if (ack.err) { test.fail('set failed: ' + ack.err); return; }
test.done();
});
});
setTimeout(() => test.fail('timeout — re-auth + second set in 60s'), 60000);
}, { runId: config.runId, relayPort: config.relayPort });
});
it("Bob's existing subscription receives the second entry (AAA)", function () {
return bob.run(async function (test) {
test.async();
const start = Date.now();
const check = setInterval(() => {
const items = globalThis.count || [];
if (items.length >= 2 && items[1] && items[1].what === 'AAA') {
clearInterval(check);
test.done();
} else if (Date.now() - start > 30000) {
clearInterval(check);
test.fail('timeout — collected ' + JSON.stringify(items));
}
}, 200);
});
});
after(function () {
teardownPanic();
});
});