Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
test: added tests for network joining
[ci skip]
  • Loading branch information
aryanjassal committed Jul 24, 2025
commit 1b13fec949da28fbb281c091a3d37a01887ed3d3
17 changes: 6 additions & 11 deletions src/nodes/CommandJoin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type PolykeyClient from 'polykey/PolykeyClient.js';
import type { Hostname } from 'polykey/network/types.js';
import type { NodeId, NodeAddress } from 'polykey/nodes/types.js';
import type { NodeIdEncoded, NodeAddress } from 'polykey/nodes/types.js';
import CommandPolykey from '../CommandPolykey.js';
import * as binUtils from '../utils/index.js';
import * as binOptions from '../utils/options.js';
Expand Down Expand Up @@ -48,17 +48,12 @@ class CommandJoin extends CommandPolykey {
},
logger: this.logger.getChild(PolykeyClient.name),
});
const seedNodes = await nodesUtils.resolveSeednodes(network as Hostname);
const seedNodeEntries = Object.entries(seedNodes);
const initialNodes = seedNodeEntries.map(
([nodeIdEncoded, nodeAddress]) => {
const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded);
if (nodeId == null) {
utils.never(`failed to decode NodeId "${nodeIdEncoded}"`);
}
return [nodeId, nodeAddress] as [NodeId, NodeAddress];
},
const seedNodes = await nodesUtils.resolveSeednodes(
network as Hostname,
);
const initialNodes = Object.entries(seedNodes) as Array<
[NodeIdEncoded, NodeAddress]
>;
await binUtils.retryAuthentication(
(auth) =>
pkClient.rpcClient.methods.nodesSyncGraph({
Expand Down
125 changes: 125 additions & 0 deletions tests/nodes/join.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type { NodeId } from 'polykey/ids/types.js';
import type { SeedNodes } from 'polykey/nodes/types.js';
import path from 'node:path';
import fs from 'node:fs';
import Logger, { LogLevel, StreamHandler } from '@matrixai/logger';
import { encodeNodeId, decodeNodeId } from 'polykey/nodes/utils.js';
import { verifyClaimNetworkAuthority } from 'polykey/claims/payloads/claimNetworkAuthority.js';
import { jest } from '@jest/globals';
import PolykeyAgent from 'polykey/PolykeyAgent.js';
import * as keysUtils from 'polykey/keys/utils/index.js';
import * as testUtils from '../utils/index.js';

let seedNodeId: NodeId;
let seedNodeIdEncoded = '';
let seedNodeHost = '';
let seedNodePort = 0;
jest.unstable_mockModule('polykey/nodes/utils.js', () => {
return {
__esModule: true,
decodeNodeId,
resolveSeednodes: jest
.fn<() => Promise<SeedNodes>>()
.mockImplementation(async () => {
const nodes: SeedNodes = {};
nodes[seedNodeIdEncoded] = [seedNodeHost, seedNodePort];
return nodes;
}),
};
});

describe('join', () => {
const logger = new Logger('join test', LogLevel.WARN, [new StreamHandler()]);
const password = 'helloworld';
let dataDir: string;
let nodePath: string;
let polykeyAgent: PolykeyAgent;
let seedNode: PolykeyAgent;
let seedNodeClaimNetworkAuthority;
let networkKeyPair;
let networkNodeId;
let network = 'test.network.com';
beforeEach(async () => {
dataDir = await fs.promises.mkdtemp(
path.join(globalThis.tmpDir, 'polykey-test-'),
);
networkKeyPair = keysUtils.generateKeyPair();
networkNodeId = keysUtils.publicKeyToNodeId(networkKeyPair.publicKey);
network = 'test.network.com';
nodePath = path.join(dataDir, 'keynode');
polykeyAgent = await PolykeyAgent.createPolykeyAgent({
password,
options: {
seedNodes: {}, // Explicitly no seed nodes on startup
nodePath,
agentServiceHost: '127.0.0.1',
clientServiceHost: '127.0.0.1',
keys: {
passwordOpsLimit: keysUtils.passwordOpsLimits.min,
passwordMemLimit: keysUtils.passwordMemLimits.min,
strictMemoryLock: false,
},
},
logger,
});
// Setting up a remote seednode
seedNode = await PolykeyAgent.createPolykeyAgent({
password,
options: {
nodePath: path.join(dataDir, 'seednode'),
agentServiceHost: '127.0.0.1',
clientServiceHost: '127.0.0.1',
keys: {
passwordOpsLimit: keysUtils.passwordOpsLimits.min,
passwordMemLimit: keysUtils.passwordMemLimits.min,
strictMemoryLock: false,
},
},
logger,
});
[, seedNodeClaimNetworkAuthority] =
await seedNode.nodeManager.createClaimNetworkAuthority(
networkNodeId,
network,
false,
async (claim) => {
claim.signWithPrivateKey(networkKeyPair.privateKey);
return claim;
},
);
await seedNode.nodeManager.createSelfSignedClaimNetworkAccess(
seedNodeClaimNetworkAuthority,
);
await testUtils.nodesConnect(polykeyAgent, seedNode);
seedNodeId = seedNode.keyRing.getNodeId();
seedNodeHost = seedNode.agentServiceHost;
seedNodePort = seedNode.agentServicePort;
seedNodeIdEncoded = encodeNodeId(seedNodeId);
});
afterEach(async () => {
jest.restoreAllMocks();
await polykeyAgent.stop();
await seedNode.stop();
await fs.promises.rm(dataDir, {
force: true,
recursive: true,
});
});
test('should connect to a seednode', async () => {
const command = ['nodes', 'join', '-np', nodePath, network];
const result = await testUtils.pkStdio(command, {
env: { PK_PASSWORD: password },
cwd: dataDir,
});
expect(result.exitCode).toBe(0);

expect(() =>
verifyClaimNetworkAuthority(
networkNodeId,
seedNodeId,
network,
seedNodeClaimNetworkAuthority,
),
).not.toThrow();
});
});