Skip to content

Commit

Permalink
chore(casting): export replayIO testing tool
Browse files Browse the repository at this point in the history
  • Loading branch information
dckc committed Jun 20, 2023
1 parent bbb920f commit d7ffad5
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 47 deletions.
80 changes: 74 additions & 6 deletions packages/casting/test/net-access-fixture.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
const { stringify: jq } = JSON;

/**
* @file to regenerate
* 1. set RECORDING=true in test-interpose-net-access.js
* 2. run: yarn test test/test-test-interpose-net-access.js --update-snapshots
* 3. for each map in test-test-interpose-net-access.js.md, copy it and
* 4. replace all occurences of => with : and paste as args to Object.fromEntries()
* 5. change RECORDING back to false
*/
export const web1 = new Map([
[
jq([
'https://emerynet.rpc.agoric.net/',
{
method: 'POST',
body: jq({
id: 1,
id: 1208387614,
method: 'no-such-method',
params: [],
jsonrpc: '2.0',
Expand All @@ -31,7 +39,7 @@ export const web1 = new Map([
method: 'POST',
body: jq({
jsonrpc: '2.0',
id: 2,
id: 797030719,
method: 'abci_query',
params: {
path: '/cosmos.bank.v1beta1.Query/Balance',
Expand Down Expand Up @@ -70,7 +78,7 @@ export const web2 = new Map([
method: 'POST',
body: jq({
jsonrpc: '2.0',
id: 1,
id: 1757612624,
method: 'abci_query',
params: {
path: '/agoric.vstorage.Query/Children',
Expand Down Expand Up @@ -103,18 +111,56 @@ export const web2 = new Map([
]);

/**
* capture JSON RPC IO traffic
* @param {string} str
* ack: https://stackoverflow.com/a/7616484
*/
const hashCode = str => {
let hash = 0;
let i;
let chr;
if (str.length === 0) return hash;
for (i = 0; i < str.length; i += 1) {
chr = str.charCodeAt(i);
// eslint-disable-next-line no-bitwise
hash = (hash << 5) - hash + chr;
// eslint-disable-next-line no-bitwise
hash |= 0; // Convert to 32bit integer
}
return hash;
};

/**
* Normalize JSON RPC request ID
*
* This was used to generate the fixtures above.
* tendermint-rpc generates ids using ambient access to Math.random()
* So we normalize them to a hash of the rest of the JSON.
*
* Earlier, we tried a sequence number, but it was non-deterministic
* with multiple interleaved requests.
*
* @param {string} argsKey
*/
const normalizeID = argsKey => {
// arbitrary string unlikely to occur in a request. from `pwgen 16 -1`
const placeholder = 'Ajaz1chei7ohnguv';

const noid = argsKey.replace(/\\"id\\":\d+/, `\\"id\\":${placeholder}`);
const id = Math.abs(hashCode(noid));
return noid.replace(placeholder, `${id}`);
};

/**
* Wrap `fetch` to capture JSON RPC IO traffic.
*
* @param {typeof window.fetch} fetch
* returns wraped fetch along with a .web map for use with {@link replayIO}
*/
export const captureIO = fetch => {
const web = new Map();
/** @type {typeof window.fetch} */
// @ts-expect-error mock
const f = async (...args) => {
const key = JSON.stringify(args);
const key = normalizeID(JSON.stringify(args));
const resp = await fetch(...args);
return {
json: async () => {
Expand All @@ -126,3 +172,25 @@ export const captureIO = fetch => {
};
return { fetch: f, web };
};

/**
* Replay captured JSON RPC IO.
*
* @param {Map<string, any>} web map from
* JSON-stringified fetch args to fetched JSON data.
*/
export const replayIO = web => {
/** @type {typeof window.fetch} */
// @ts-expect-error mock
const f = async (...args) => {
const key = normalizeID(JSON.stringify(args));
const data = web.get(key);
if (!data) {
throw Error(`no data for ${key}`);
}
return {
json: async () => data,
};
};
return f;
};
52 changes: 11 additions & 41 deletions packages/casting/test/test-interpose-net-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,12 @@ import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import { QueryClientImpl } from '@agoric/cosmic-proto/vstorage/query.js';

import { makeHttpClient } from '../src/makeHttpClient.js';
import { captureIO, web1, web2 } from './net-access-fixture.js';
import { captureIO, replayIO, web1, web2 } from './net-access-fixture.js';

/** @type {import('ava').TestFn<Awaited<ReturnType<typeof makeTestContext>>>} */
const test = /** @type {any} */ (anyTest);

/** @param {Map<string, any>} web */
const replayIO = web => {
// tendermint-rpc generates ids using ambient access to Math.random()
// So we normalize them to sequence numbers.
let nextID = 0;
const normalizeID = data =>
data.replace(/\\"id\\":\d+/, `\\"id\\":${nextID}`);

/** @type {typeof window.fetch} */
// @ts-expect-error mock
const f = async (...args) => {
nextID += 1;
const key = normalizeID(JSON.stringify(args));
const data = web.get(key);
if (!data) throw Error(`no data for ${key}`);
return {
json: async () => data,
};
};
return f;
};
const RECORDING = false;

const makeTestContext = async () => {
return { fetch: globalThis.fetch };
Expand Down Expand Up @@ -93,8 +73,12 @@ const scenario2 = {
],
};

test('vstorage query: Children', async t => {
const fetchMock = replayIO(web2);
test(`vstorage query: Children (RECORDING: ${RECORDING})`, async t => {
const { context: io } = t;

const { fetch: fetchMock, web } = io.recording
? captureIO(io.fetch)
: { fetch: replayIO(web2), web: new Map() };
const rpcClient = makeHttpClient(scenario2.endpoint, fetchMock);

const tmClient = await Tendermint34Client.create(rpcClient);
Expand All @@ -103,25 +87,11 @@ test('vstorage query: Children', async t => {
const queryService = new QueryClientImpl(rpc);

const children = await queryService.Children({ path: '' });
if (io.recording) {
t.snapshot(web);
}
t.deepEqual(children, {
children: scenario2.children,
pagination: undefined,
});
});

// Fixtures for the tests above were captured via integration testing like...
test.skip('vstorage query: Data (capture IO)', async t => {
const { context: io } = t;
const { fetch: fetchMock, web } = captureIO(io.fetch);
const rpcClient = makeHttpClient(scenario2.endpoint, fetchMock);

const tmClient = await Tendermint34Client.create(rpcClient);
const qClient = new QueryClient(tmClient);
const rpc = createProtobufRpcClient(qClient);
const queryService = new QueryClientImpl(rpc);

const data = await queryService.Data({ path: '' });
t.deepEqual(data, { value: '' });

t.snapshot(web);
});

0 comments on commit d7ffad5

Please sign in to comment.