import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURES_DIR = join(__dirname, '..', 'wire', 'fixtures');
function loadFixtures(dir, out = []) {
for (const entry of readdirSync(dir)) {
const path = join(dir, entry);
const stat = statSync(path);
if (stat.isDirectory()) {
loadFixtures(path, out);
} else if (entry.endsWith('.json')) {
const content = readFileSync(path, 'utf-8');
out.push(JSON.parse(content));
}
}
return out;
}
async function getGun() {
const mod = await import('gun');
return mod.default || mod;
}
async function testPutFixture(t, fixture, Gun) {
const expected = fixture.expected;
if (!expected.parses) {
t.skip('wire-parse-error fixture — not testable at Gun.js API level');
return;
}
const gun = Gun({ localStorage: false, radisk: false });
for (const soul of expected.souls || []) {
const fields = expected.fields?.[soul] || [];
const values = expected.values?.[soul] || {};
const data = {};
for (const key of fields) {
const val = values[key];
if (val !== null && typeof val === 'object' && val['#']) {
data[key] = { '#': val['#'] };
} else {
data[key] = val;
}
}
if (Object.keys(data).length === 0) {
t.skip('no fields to put — Gun.js needs at least one field');
return;
}
gun.get(soul).put(data);
const result = await new Promise((resolve) => {
gun.get(soul).once((node) => resolve(node));
});
assert.ok(result, `Gun.js returned null for soul ${soul}`);
for (const key of fields) {
const expectedVal = values[key];
assert.ok(
key in result,
`field ${key} missing from Gun.js node ${soul}`
);
if (expectedVal !== null && typeof expectedVal === 'object' && expectedVal['#']) {
assert.ok(
result[key] && result[key]['#'] === expectedVal['#'],
`relation mismatch for ${soul}.${key}: expected ${expectedVal['#']}, got ${result[key]?.['#']}`
);
} else {
const actual = result[key];
if (typeof expectedVal === 'number') {
assert.equal(actual, expectedVal, `value mismatch for ${soul}.${key}`);
} else {
assert.deepEqual(actual, expectedVal, `value mismatch for ${soul}.${key}`);
}
}
}
}
}
async function testGetFixture(t, fixture, Gun) {
const expected = fixture.expected;
if (!expected.parses) {
t.skip('wire-parse-error fixture — not testable at Gun.js API level');
return;
}
const gun = Gun({ localStorage: false, radisk: false });
const input = JSON.parse(fixture.input);
const getSoul = input.get?.['#'];
if (!getSoul) {
t.skip('get fixture without soul — not testable at API level');
return;
}
gun.get(getSoul).put({ _test: 'mirror' });
const result = await new Promise((resolve) => {
gun.get(getSoul).once((node) => resolve(node));
});
assert.ok(result, `Gun.js get returned null for soul ${getSoul}`);
assert.equal(result._test, 'mirror', `Gun.js get data mismatch`);
}
async function testGenericFixture(t, fixture, Gun) {
const expected = fixture.expected;
if (!expected.parses) {
t.skip('wire-parse-error fixture — not testable at Gun.js API level');
return;
}
const gun = Gun({ localStorage: false, radisk: false });
assert.ok(gun, 'Gun instance created');
}
test('BEAM ↔ Gun.js wire mirror tests', async (t) => {
const fixtures = loadFixtures(FIXTURES_DIR);
assert.ok(fixtures.length > 0, 'no fixtures found');
console.log(`Loaded ${fixtures.length} wire fixtures from ${FIXTURES_DIR}`);
const Gun = await getGun();
assert.ok(Gun, 'Gun.js loaded successfully');
for (const fixture of fixtures) {
await t.test(fixture.name, async (subt) => {
const cat = fixture.category;
if (cat === 'put') {
await testPutFixture(subt, fixture, Gun);
} else if (cat === 'get') {
await testGetFixture(subt, fixture, Gun);
} else {
await testGenericFixture(subt, fixture, Gun);
}
});
}
console.log(`\nMirror tests complete: ${fixtures.length} fixtures checked`);
});