From 977dbe0023f4fc0afc81730341a4e6c7adeb2b30 Mon Sep 17 00:00:00 2001 From: Coby Date: Thu, 21 Nov 2024 18:02:05 -0500 Subject: [PATCH] Updating GraphQL resolver tests --- .github/workflows/run-tests.yaml | 1 + package.json | 2 +- run-tests.sh | 11 + tests/resolvers.test.ts | 346 ++++++++++++++++++++----------- 4 files changed, 239 insertions(+), 121 deletions(-) create mode 100755 run-tests.sh diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index c230f21..c4efbf2 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -20,6 +20,7 @@ jobs: - 5432:5432 - 8080:8080 - 8181:8181 + - 8282:8282 steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/package.json b/package.json index ee4ba94..ed0074b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "npm run clean && npx tsc", "start": "node build/src/index.js", "dev": "cross-env NODE_NO_WARNINGS=1 npx nodemon src/index.ts", - "test": "npx tsc && for testfile in $(find build/tests -type f -name '*.test.js'); do node $testfile; done", + "test": "./run-tests.sh", "clean": "rimraf ./build", "gen-test-mocks": "cross-env NODE_NO_WARNINGS=1 node --loader ts-node/esm tests/mocked_sql/generate_mock_data.ts", "benchmark": "cross-env NODE_NO_WARNINGS=1 node --loader ts-node/esm benchmark/setup.ts && npx artillery run benchmark/graphql.yaml --output benchmark/report.json", diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..e727545 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e +shopt -s globstar # to expand '**' into nested directories./ + +npm run build + +# find all unit tests in build and run them +for f in ./build/**/*test.js; do + echo "Running $f" + node --enable-source-maps --stack-trace-limit=1000 --test $f; +done diff --git a/tests/resolvers.test.ts b/tests/resolvers.test.ts index e41c82e..7b0fea0 100644 --- a/tests/resolvers.test.ts +++ b/tests/resolvers.test.ts @@ -3,7 +3,11 @@ import assert from 'node:assert'; import { createYoga, createSchema } from 'graphql-yoga'; import { loadSchemaSync } from '@graphql-tools/load'; import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; -import { buildHTTPExecutor } from '@graphql-tools/executor-http'; +import { + buildHTTPExecutor, + HTTPExecutorOptions, +} from '@graphql-tools/executor-http'; +import { AsyncExecutor } from '@graphql-tools/utils'; import { parse } from 'graphql'; import { PrivateKey, Lightnet } from 'o1js'; import { resolvers } from '../src/resolvers.js'; @@ -17,6 +21,35 @@ import { Keypair, } from '../zkapp/utils.js'; import { HelloWorld } from '../zkapp/contract.js'; +import { + ActionData, + ActionOutput, + EventData, + EventOutput, + Maybe, +} from 'src/resolvers-types.js'; + +interface ExecutorResult { + data: + | { + events: Array; + } + | { + actions: Array; + }; +} + +interface EventQueryResult extends ExecutorResult { + data: { + events: Array; + }; +} + +interface ActionQueryResult extends ExecutorResult { + data: { + actions: Array; + }; +} const eventsQuery = ` query getEvents($input: EventFilterOptionsInput!) { @@ -79,179 +112,252 @@ query getActions($input: ActionFilterOptionsInput!) { const PG_CONN = 'postgresql://postgres:postgres@localhost:5432/archive '; describe('Query Resolvers', async () => { - let executor: any; + let executor: AsyncExecutor; let senderKeypair: Keypair; let zkAppKeypair: Keypair; let zkApp: HelloWorld; + async function executeActionsQuery(variableInput: { + address: string; + }): Promise { + return (await executor({ + variables: { + input: variableInput, + }, + document: parse(`${actionsQuery}`), + })) as ActionQueryResult; + } + + async function executeEventsQuery(variableInput: { + address: string; + }): Promise { + return (await executor({ + variables: { + input: variableInput, + }, + document: parse(`${eventsQuery}`), + })) as EventQueryResult; + } + before(async () => { - setNetworkConfig(); + try { + setNetworkConfig(); - const schema = createSchema({ - typeDefs: loadSchemaSync('./schema.graphql', { - loaders: [new GraphQLFileLoader()], - }), - resolvers, - }); - const context = await buildContext(PG_CONN); - const yoga = createYoga({ schema, context }); - executor = buildHTTPExecutor({ - fetch: yoga.fetch, - }); + const schema = createSchema({ + typeDefs: loadSchemaSync('./schema.graphql', { + loaders: [new GraphQLFileLoader()], + }), + resolvers, + }); + const context = await buildContext(PG_CONN); + const yoga = createYoga({ schema, context }); + executor = buildHTTPExecutor({ + fetch: yoga.fetch, + }); - zkAppKeypair = await Lightnet.acquireKeyPair(); - senderKeypair = await Lightnet.acquireKeyPair(); - zkApp = await deployContract( - zkAppKeypair, - senderKeypair, - /* fundNewAccount = */ false - ); + zkAppKeypair = await Lightnet.acquireKeyPair(); + senderKeypair = await Lightnet.acquireKeyPair(); + zkApp = await deployContract( + zkAppKeypair, + senderKeypair, + /* fundNewAccount = */ false + ); + } catch (error) { + console.error(error); + } }); after(async () => { + process.on('uncaughtException', (err) => { + console.error('Uncaught exception:', err); + process.exit(1); + }); + + process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled rejection at:', promise, 'reason:', reason); + process.exit(1); + }); + process.exit(0); }); describe('Events', async () => { + let eventsResponse: EventOutput[]; + let lastBlockEvents: Maybe[]; + let results: EventQueryResult; + test('Fetching events with a valid address but no emitted events should not throw', async () => { assert.doesNotThrow(async () => { - await executor({ - variables: { - input: { address: zkAppKeypair.publicKey.toBase58() }, - }, - document: parse(`${eventsQuery}`), + await executeEventsQuery({ + address: zkAppKeypair.publicKey.toBase58(), }); }); }); test('Fetching events with a empty address should return empty list', async () => { - const results = await executor({ - variables: { - input: { - address: '', - }, - }, - document: parse(`${eventsQuery}`), + results = await executeEventsQuery({ + address: '', }); assert.strictEqual(results.data.events.length, 0); }); - test('Emitting an event with single field should return a single event with the correct data', async () => { - await emitSingleEvent(zkApp, senderKeypair); - const results = await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${eventsQuery}`), + describe('After emitting an event with a single field once', async () => { + before(async () => { + await emitSingleEvent(zkApp, senderKeypair); + results = await executeEventsQuery({ + address: zkApp.address.toBase58(), + }); + eventsResponse = results.data.events; + lastBlockEvents = eventsResponse[eventsResponse.length - 1].eventData!; + }); + test('GQL response contains one event in the latest block', async () => { + assert.strictEqual(lastBlockEvents.length, 1); + }); + test('The event has the correct data', async () => { + const eventData = lastBlockEvents[0]!; + assert.deepStrictEqual(eventData.data, ['0', '2']); // event type enum = 0 and event data = 2 }); - - const events = results.data.events; - const lastEvent = events[events.length - 1]; - assert.strictEqual(lastEvent.eventData.length, 1); }); - test('Emitting multiple events with a single field should return multiple events with the correct data', async () => { - await emitSingleEvent(zkApp, senderKeypair, { numberOfEmits: 3 }); - const results = await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${eventsQuery}`), + describe('After emitting an event with a single field multiple times', async () => { + let results: EventQueryResult; + const numberOfEmits = 3; + before(async () => { + await emitSingleEvent(zkApp, senderKeypair, { numberOfEmits }); + results = await executeEventsQuery({ + address: zkApp.address.toBase58(), + }); + eventsResponse = results.data.events; + lastBlockEvents = eventsResponse[eventsResponse.length - 1].eventData!; + }); + test('GQL response contains multiple events in the latest block', async () => { + assert.strictEqual(lastBlockEvents.length, numberOfEmits); + }); + test('the events have the correct data', async () => { + for (let i = 0; i < numberOfEmits; i++) { + const eventData = lastBlockEvents[i]!; + assert.deepStrictEqual(eventData.data, ['0', '2']); // event type enum = 0 and event data = 2 + } }); - const events = results.data.events; - const lastEvent = events[events.length - 1]; - assert.strictEqual(lastEvent.eventData.length, 3); }); - test('Emitting an event with multiple fields should return an event with multiple values', async () => { - await emitMultipleFieldsEvent(zkApp, senderKeypair); - const results = await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${eventsQuery}`), + describe('After emitting an event with multiple fields once', async () => { + let results: EventQueryResult; + before(async () => { + await emitMultipleFieldsEvent(zkApp, senderKeypair); + results = await executeEventsQuery({ + address: zkApp.address.toBase58(), + }); + eventsResponse = results.data.events; + lastBlockEvents = eventsResponse[eventsResponse.length - 1].eventData!; + }); + + test('GQL response contains one event in the latest block', async () => { + assert.strictEqual(lastBlockEvents.length, 1); + }); + + test('The event has the correct data', async () => { + const eventData = lastBlockEvents[0]!; + // The event type is 1 and the event data is 2, 1 (Bool(true)), 1 and the zkapp address + assert.deepStrictEqual(eventData.data, [ + '1', + '2', + '1', + '1', + ...zkApp.address.toFields().map((f) => f.toString()), + ]); }); - const events = results.data.events; - const lastEvent = events[events.length - 1]; - assert.strictEqual(lastEvent.eventData.length, 1); }); - test('Emitting multiple events with multiple fields should return multiple events with the correct data', async () => { - await emitMultipleFieldsEvent(zkApp, senderKeypair, { numberOfEmits: 3 }); - const results = await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${eventsQuery}`), + describe('After emitting an event with multiple fields multiple times', async () => { + let results: EventQueryResult; + const numberOfEmits = 3; + before(async () => { + await emitMultipleFieldsEvent(zkApp, senderKeypair, { numberOfEmits }); + results = await executeEventsQuery({ + address: zkApp.address.toBase58(), + }); + eventsResponse = results.data.events; + lastBlockEvents = eventsResponse[eventsResponse.length - 1].eventData!; + }); + test('GQL response contains multiple events in the latest block', async () => { + assert.strictEqual(lastBlockEvents.length, numberOfEmits); + }); + test('the events have the correct data', async () => { + for (let i = 0; i < numberOfEmits; i++) { + const eventData = lastBlockEvents[i]!; + // The event type is 1 and the event data is 2, 1 (Bool(true)), and the zkapp address + assert.deepStrictEqual(eventData.data, [ + '1', + '2', + '1', + '1', + ...zkApp.address.toFields().map((f) => f.toString()), + ]); + } }); - const events = results.data.events; - const lastEvent = events[events.length - 1]; - assert.strictEqual(lastEvent.eventData.length, 3); }); }); describe('Actions', async () => { + let actionsResponse: ActionOutput[]; + let lastBlockActions: Maybe[]; + let results: ActionQueryResult; + test('Fetching actions with a valid address should not throw', async () => { assert.doesNotThrow(async () => { - await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${actionsQuery}`), + await executeActionsQuery({ + address: zkAppKeypair.publicKey.toBase58(), }); }); }); test('Fetching actions with a empty address should return empty list', async () => { - const results = await executor({ - variables: { - input: { - address: '', - }, - }, - document: parse(`${actionsQuery}`), + results = await executeActionsQuery({ + address: '', }); assert.strictEqual(results.data.actions.length, 0); }); - test('Emitting an action should return a single action with the correct data', async () => { - await emitAction(zkApp, senderKeypair); - const results = await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${actionsQuery}`), + describe('After emitting an action', async () => { + before(async () => { + await emitAction(zkApp, senderKeypair); + results = await executeActionsQuery({ + address: zkApp.address.toBase58(), + }); + actionsResponse = results.data.actions; + lastBlockActions = + actionsResponse[actionsResponse.length - 1].actionData!; + }); + test('GQL response contains one action', async () => { + assert.strictEqual(lastBlockActions.length, 1); + }); + test('The action has the correct data', async () => { + const actionData = lastBlockActions[0]!; + assert.deepStrictEqual(actionData.data, [ + '2', + '1', + '1', + ...zkApp.address.toFields().map((f) => f.toString()), + ]); }); - const actions = results.data.actions; - const lastAction = actions[actions.length - 1]; - assert.strictEqual(lastAction.actionData.length, 1); }); - test('Emitting multiple actions should return multiple actions with the correct data', async () => { - await emitAction(zkApp, senderKeypair, { numberOfEmits: 3 }); - const results = await executor({ - variables: { - input: { - address: zkAppKeypair.publicKey.toBase58(), - }, - }, - document: parse(`${actionsQuery}`), + describe('After emitting multiple actions', async () => { + const numberOfEmits = 3; + before(async () => { + await emitAction(zkApp, senderKeypair, { numberOfEmits }); + results = await executeActionsQuery({ + address: zkApp.address.toBase58(), + }); + actionsResponse = results.data.actions; + lastBlockActions = + actionsResponse[actionsResponse.length - 1].actionData!; + }); + + test('Emitting multiple actions should return multiple actions with the correct data', async () => { + assert.strictEqual(lastBlockActions.length, numberOfEmits); }); - const actions = results.data.actions; - const lastAction = actions[actions.length - 1]; - assert.strictEqual(lastAction.actionData.length, 3); }); }); });