mirror of
https://github.com/logos-messaging/logos-delivery-js.git
synced 2026-04-21 08:13:33 +00:00
204 lines
6.8 KiB
TypeScript
204 lines
6.8 KiB
TypeScript
import { expect } from 'chai';
|
|
|
|
import { DnsClient, DnsNodeDiscovery } from './dns';
|
|
import testData from './testdata.json';
|
|
|
|
const mockData = testData.dns;
|
|
|
|
const host = 'nodes.example.org';
|
|
const rootDomain = 'JORXBYVVM7AEKETX5DGXW44EAY';
|
|
const branchDomainA = 'D2SNLTAGWNQ34NTQTPHNZDECFU';
|
|
const branchDomainB = 'D3SNLTAGWNQ34NTQTPHNZDECFU';
|
|
const branchDomainC = 'D4SNLTAGWNQ34NTQTPHNZDECFU';
|
|
const branchDomainD = 'D5SNLTAGWNQ34NTQTPHNZDECFU';
|
|
const partialBranchA = 'AAAA';
|
|
const partialBranchB = 'BBBB';
|
|
const singleBranch = `enrtree-branch:${branchDomainA}`;
|
|
const doubleBranch = `enrtree-branch:${branchDomainA},${branchDomainB}`;
|
|
const multiComponentBranch = [
|
|
`enrtree-branch:${branchDomainA},${partialBranchA}`,
|
|
`${partialBranchB},${branchDomainB}`,
|
|
];
|
|
|
|
// Note: once td.when is asked to throw for an input it will always throw.
|
|
// Input can't be re-used for a passing case.
|
|
const errorBranchA = `enrtree-branch:${branchDomainC}`;
|
|
const errorBranchB = `enrtree-branch:${branchDomainD}`;
|
|
|
|
/**
|
|
* Mocks DNS resolution.
|
|
*/
|
|
class MockDNS implements DnsClient {
|
|
fqdnRes: Map<string, string[]>;
|
|
fqdnThrows: string[];
|
|
|
|
constructor() {
|
|
this.fqdnRes = new Map();
|
|
this.fqdnThrows = [];
|
|
}
|
|
|
|
addRes(fqdn: string, res: string[]): void {
|
|
this.fqdnRes.set(fqdn, res);
|
|
}
|
|
|
|
addThrow(fqdn: string): void {
|
|
this.fqdnThrows.push(fqdn);
|
|
}
|
|
|
|
resolveTXT(fqdn: string): Promise<string[]> {
|
|
if (this.fqdnThrows.includes(fqdn)) throw 'Mock DNS throws.';
|
|
|
|
const res = this.fqdnRes.get(fqdn);
|
|
|
|
if (!res) throw `Mock DNS could not resolve ${fqdn}`;
|
|
|
|
return Promise.resolve(res);
|
|
}
|
|
}
|
|
|
|
describe('DNS Node Discovery', () => {
|
|
let mockDns: MockDNS;
|
|
|
|
beforeEach(() => {
|
|
mockDns = new MockDNS();
|
|
mockDns.addRes(host, [mockData.enrRoot]);
|
|
});
|
|
|
|
it('retrieves a single peer', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [singleBranch]);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [mockData.enrA]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
|
|
expect(peers.length).to.eq(1);
|
|
expect(peers[0].ip).to.eq('45.77.40.127');
|
|
expect(peers[0].tcp).to.eq(30303);
|
|
});
|
|
|
|
it('retrieves all peers (2) when maxQuantity larger than DNS tree size', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [doubleBranch]);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [mockData.enrA]);
|
|
mockDns.addRes(`${branchDomainB}.${host}`, [mockData.enrB]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = await dnsNodeDiscovery.getPeers(50, [mockData.enrTree]);
|
|
|
|
expect(peers.length).to.eq(2);
|
|
expect(peers[0].ip).to.not.eq(peers[1].ip);
|
|
});
|
|
|
|
it('retrieves all peers (3) when branch entries are composed of multiple strings', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, multiComponentBranch);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [mockData.enr]);
|
|
mockDns.addRes(`${branchDomainB}.${host}`, [mockData.enrA]);
|
|
mockDns.addRes(`${partialBranchA}${partialBranchB}.${host}`, [
|
|
mockData.enrB,
|
|
]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = await dnsNodeDiscovery.getPeers(50, [mockData.enrTree]);
|
|
|
|
expect(peers.length).to.eq(3);
|
|
expect(peers[0].ip).to.not.eq(peers[1].ip);
|
|
expect(peers[0].ip).to.not.eq(peers[2].ip);
|
|
expect(peers[1].ip).to.not.eq(peers[2].ip);
|
|
});
|
|
|
|
it('it tolerates circular branch references', async function () {
|
|
// root --> branchA
|
|
// branchA --> branchA
|
|
mockDns.addRes(`${rootDomain}.${host}`, [singleBranch]);
|
|
mockDns.addRes(`${branchDomainA}.${host}`, [singleBranch]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it('recovers when dns.resolve returns empty', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [singleBranch]);
|
|
|
|
// Empty response case
|
|
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
let peers = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
|
|
expect(peers.length).to.eq(0);
|
|
|
|
// No TXT records case
|
|
mockDns.addRes(`${branchDomainA}.${host}`, []);
|
|
|
|
peers = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it('ignores domain fetching errors', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [errorBranchA]);
|
|
mockDns.addThrow(`${branchDomainC}.${host}`);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it('ignores unrecognized TXT record formats', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [mockData.enrBranchBadPrefix]);
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peers = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
expect(peers.length).to.eq(0);
|
|
});
|
|
|
|
it('caches peers it previously fetched', async function () {
|
|
mockDns.addRes(`${rootDomain}.${host}`, [errorBranchB]);
|
|
mockDns.addRes(`${branchDomainD}.${host}`, [mockData.enrA]);
|
|
|
|
const dnsNodeDiscovery = new DnsNodeDiscovery(mockDns);
|
|
const peersA = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
expect(peersA.length).to.eq(1);
|
|
|
|
// Specify that a subsequent network call retrieving the same peer should throw.
|
|
// This test passes only if the peer is fetched from cache
|
|
mockDns.addThrow(`${branchDomainD}.${host}`);
|
|
|
|
const peersB = await dnsNodeDiscovery.getPeers(1, [mockData.enrTree]);
|
|
expect(peersB.length).to.eq(1);
|
|
expect(peersA[0].ip).to.eq(peersB[0].ip);
|
|
});
|
|
});
|
|
|
|
describe('DNS Node Discovery [live data]', function () {
|
|
const publicKey = 'AOFTICU2XWDULNLZGRMQS4RIZPAZEHYMV4FYHAPW563HNRAOERP7C';
|
|
const fqdn = 'test.nodes.vac.dev';
|
|
const enrTree = `enrtree://${publicKey}@${fqdn}`;
|
|
const ipTestRegex = /^\d+\.\d+\.\d+\.\d+$/;
|
|
const maxQuantity = 3;
|
|
|
|
before(function () {
|
|
if (process.env.CI && !process.env.LIVE_DATA_TESTS) {
|
|
this.skip();
|
|
}
|
|
});
|
|
|
|
it(`should retrieve ${maxQuantity} PeerInfos for test.nodes.vac.dev`, async function () {
|
|
this.timeout(5000);
|
|
// Google's dns server address. Needs to be set explicitly to run in CI
|
|
const dnsNodeDiscovery = DnsNodeDiscovery.dnsOverHttp();
|
|
const peers = await dnsNodeDiscovery.getPeers(maxQuantity, [enrTree]);
|
|
|
|
expect(peers.length).to.eq(maxQuantity);
|
|
|
|
// TODO: Test multiaddrs entry
|
|
console.log(peers.map((peer) => peer.multiaddrs));
|
|
|
|
const seen: string[] = [];
|
|
for (const peer of peers) {
|
|
expect(peer!.ip!).to.match(ipTestRegex);
|
|
expect(seen).to.not.include(peer!.ip!);
|
|
seen.push(peer!.ip!);
|
|
}
|
|
});
|
|
});
|