Skip to content
Merged
Changes from all commits
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
74 changes: 66 additions & 8 deletions lib/governanceRpc.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,29 +87,87 @@ test('malformed current-vote data is not certified as an empty tally', async ()
code: 'governance_rpc_unavailable',
});
});
test('actual HTTP timeout after receiving vote is unknown and never automatically retried', async () => {
test('deadline after the real server receives a vote is unknown and never automatically retried', async () => {
let count = 0;
let received;
const server = http.createServer((req) => {
count++;
req.resume();
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => received(JSON.parse(Buffer.concat(chunks).toString())));
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const actualSetTimeout = global.setTimeout;
const transport = axios.create();
// Keep the real HTTP connection, while controlling which deadline fires.
// Axios's own timeout is disabled here so the server receipt is observed
// before the RPC's 30 ms deadline callback is invoked deterministically.
const instance = {
post: jest.fn((url, body, options) => {
expect(options.timeout).toBe(30);
return transport.post(url, body, { ...options, timeout: 0 });
}),
};
const rpc = createGovernanceRpc(
{
url: `http://127.0.0.1:${server.address().port}`,
instance: axios.create(),
},
{ url: `http://127.0.0.1:${server.address().port}`, instance },
{ timeoutMs: 30 }
);
try {
await expect(rpc.voteRaw(...args)).rejects.toMatchObject({
async function afterReceipt(call, expectedMethod, expectedParams) {
const requestReceived = new Promise((resolve) => { received = resolve; });
let expire;
const spy = jest.spyOn(global, 'setTimeout').mockImplementationOnce((fn, ms) => {
expect(ms).toBe(30);
expire = fn;
// Return a real, cleared timer handle for the RPC's normal cleanup.
return actualSetTimeout(() => {}, ms);
});
let result;
try {
result = call();
} finally {
spy.mockRestore();
}
const body = await requestReceived;
expect(body).toMatchObject({ method: expectedMethod, params: expectedParams });
expire();
return result;
}
await expect(afterReceipt(() => rpc.voteRaw(...args), 'voteraw', args)).rejects.toMatchObject({
code: 'submission_unknown',
uncertain: true,
});
expect(count).toBe(1);
await expect(rpc.getCurrentVotes(args[2])).rejects.toMatchObject({
expect(instance.post).toHaveBeenCalledTimes(1);
await expect(afterReceipt(() => rpc.getCurrentVotes(args[2]), 'gobject_getcurrentvotes', [args[2]])).rejects.toMatchObject({
code: 'governance_rpc_unavailable',
});
expect(count).toBe(2);
expect(instance.post).toHaveBeenCalledTimes(2);
} finally {
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
}
});
test('real HTTP timeout is uncertain even when delivery cannot be established', async () => {
let count = 0;
const server = http.createServer((req) => {
count++;
req.resume();
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const instance = axios.create();
const post = jest.spyOn(instance, 'post');
const rpc = createGovernanceRpc(
{ url: `http://127.0.0.1:${server.address().port}`, instance },
{ timeoutMs: 30 }
);
try {
await expect(rpc.voteRaw(...args)).rejects.toMatchObject({
code: 'submission_unknown', uncertain: true,
});
expect(post).toHaveBeenCalledTimes(1);
expect(count).toBeLessThanOrEqual(1);
} finally {
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
Expand Down
Loading