diff --git a/packages/taco/test/conditions/base/json.test.ts b/packages/taco/test/conditions/base/json-api.test.ts similarity index 96% rename from packages/taco/test/conditions/base/json.test.ts rename to packages/taco/test/conditions/base/json-api.test.ts index de2ad685..e388a494 100644 --- a/packages/taco/test/conditions/base/json.test.ts +++ b/packages/taco/test/conditions/base/json-api.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'; import { JsonApiCondition, + JsonApiConditionProps, jsonApiConditionSchema, JsonApiConditionType, } from '../../../src/conditions/base/json-api'; @@ -88,7 +89,7 @@ describe('JsonApiCondition', () => { }); it('accepts conditions without parameters', () => { - const { query, ...noParamsObj } = testJsonApiConditionObj; + const { parameters, ...noParamsObj } = testJsonApiConditionObj; const result = JsonApiCondition.validate( jsonApiConditionSchema, noParamsObj, @@ -101,7 +102,7 @@ describe('JsonApiCondition', () => { describe('context variables', () => { it('allow context variables for various values including as substring', () => { - const jsonApiConditionObj = { + const jsonApiConditionObj: JsonApiConditionProps = { conditionType: JsonApiConditionType, endpoint: 'https://api.coingecko.com/api/:version/simple/:endpointPath', @@ -116,6 +117,7 @@ describe('JsonApiCondition', () => { value: ':expectedPrice', }, }; + const result = JsonApiCondition.validate( jsonApiConditionSchema, jsonApiConditionObj, diff --git a/packages/taco/test/conditions/base/json-rpc.test.ts b/packages/taco/test/conditions/base/json-rpc.test.ts new file mode 100644 index 00000000..3cb9b552 --- /dev/null +++ b/packages/taco/test/conditions/base/json-rpc.test.ts @@ -0,0 +1,147 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { describe, expect, it } from 'vitest'; + +import { + JsonRpcCondition, + JsonRpcConditionProps, + jsonRpcConditionSchema, + JsonRpcConditionType, +} from '../../../src/conditions/base/json-rpc'; +import { testJsonRpcConditionObj } from '../../test-utils'; + +describe('JsonRpcCondition', () => { + describe('validation', () => { + it('accepts a valid schema', () => { + const result = JsonRpcCondition.validate( + jsonRpcConditionSchema, + testJsonRpcConditionObj, + ); + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(testJsonRpcConditionObj); + }); + + it.each(['unsafe-url', 'http://http-url.com'])( + 'rejects an invalid schema', + () => { + const badJsonRpcObj = { + ...testJsonRpcConditionObj, + endpoint: 'unsafe-url', + }; + + const result = JsonRpcCondition.validate( + jsonRpcConditionSchema, + badJsonRpcObj, + ); + + expect(result.error).toBeDefined(); + expect(result.data).toBeUndefined(); + const errorMessages = result.error?.errors.map((err) => err.message); + expect( + errorMessages?.includes('Invalid url - must start with https://'), + ).toBeTruthy(); + }, + ); + + describe('authorizationToken', () => { + it('accepts context variable', () => { + const result = JsonRpcCondition.validate(jsonRpcConditionSchema, { + ...testJsonRpcConditionObj, + authorizationToken: ':authToken', + }); + expect(result.error).toBeUndefined(); + expect(result.data).toEqual({ + ...testJsonRpcConditionObj, + authorizationToken: ':authToken', + }); + }); + it.each([ + 'authToken', + 'ABCDEF1234567890', + ':authToken?', + '$:authToken', + ':auth-Token', + ])('rejects invalid context variable', (contextVar) => { + const result = JsonRpcCondition.validate(jsonRpcConditionSchema, { + ...testJsonRpcConditionObj, + authorizationToken: `${contextVar}`, + }); + expect(result.error).toBeDefined(); + expect(result.data).toBeUndefined(); + expect(result.error?.format()).toMatchObject({ + authorizationToken: { + _errors: ['Invalid'], + }, + }); + }); + }); + + describe('properties', () => { + it('accepts conditions without query path', () => { + const { query, ...noQueryObj } = testJsonRpcConditionObj; + const result = JsonRpcCondition.validate( + jsonRpcConditionSchema, + noQueryObj, + ); + + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(noQueryObj); + }); + + it('accepts conditions without params', () => { + const { params, ...noParamsObj } = testJsonRpcConditionObj; + const result = JsonRpcCondition.validate( + jsonRpcConditionSchema, + noParamsObj, + ); + + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(noParamsObj); + }); + + it('accepts conditions with params as dictionary', () => { + const result = JsonRpcCondition.validate(jsonRpcConditionSchema, { + ...testJsonRpcConditionObj, + params: { + value1: 42, + value2: 23, + }, + }); + expect(result.error).toBeUndefined(); + expect(result.data).toEqual({ + ...testJsonRpcConditionObj, + params: { + value1: 42, + value2: 23, + }, + }); + }); + }); + + describe('context variables', () => { + it('allow context variables for various values including as substring', () => { + const testJsonRpcConditionObjWithContextVars: JsonRpcConditionProps = { + conditionType: JsonRpcConditionType, + endpoint: 'https://math.example.com/:version/simple', + method: ':methodContextVar', + params: { + value1: 42, + value2: ':value2', + }, + query: '$.:queryKey', + authorizationToken: ':authToken', + returnValueTest: { + comparator: '==', + value: ':expectedResult', + }, + }; + + const result = JsonRpcCondition.validate( + jsonRpcConditionSchema, + testJsonRpcConditionObjWithContextVars, + ); + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(testJsonRpcConditionObjWithContextVars); + }); + }); + }); +}); diff --git a/packages/taco/test/conditions/condition-expr.test.ts b/packages/taco/test/conditions/condition-expr.test.ts index 66bf4829..3ba7a93f 100644 --- a/packages/taco/test/conditions/condition-expr.test.ts +++ b/packages/taco/test/conditions/condition-expr.test.ts @@ -10,6 +10,7 @@ import { ContractConditionProps, } from '../../src/conditions/base/contract'; import { JsonApiCondition } from '../../src/conditions/base/json-api'; +import { JsonRpcCondition } from '../../src/conditions/base/json-rpc'; import { RpcCondition, RpcConditionType } from '../../src/conditions/base/rpc'; import { TimeCondition, @@ -22,6 +23,7 @@ import { testContractConditionObj, testFunctionAbi, testJsonApiConditionObj, + testJsonRpcConditionObj, testReturnValueTest, testRpcConditionObj, testTimeConditionObj, @@ -59,6 +61,7 @@ describe('condition set', () => { const rpcCondition = new RpcCondition(testRpcConditionObj); const timeCondition = new TimeCondition(testTimeConditionObj); const jsonApiCondition = new JsonApiCondition(testJsonApiConditionObj); + const jsonRpcCondition = new JsonRpcCondition(testJsonRpcConditionObj); const compoundCondition = new CompoundCondition({ operator: 'and', operands: [ @@ -424,6 +427,28 @@ describe('condition set', () => { expect(conditionExprFromJson.condition).toBeInstanceOf(JsonApiCondition); }); + it('json rpc condition serialization', () => { + const conditionExpr = new ConditionExpression(jsonRpcCondition); + + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('endpoint'); + expect(conditionExprJson).toContain('https://math.example.com/'); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain('subtract'); + expect(conditionExprJson).toContain('params'); + expect(conditionExprJson).toContain('[42,23]'); + + expect(conditionExprJson).toContain('query'); + expect(conditionExprJson).toContain('$.mathresult'); + expect(conditionExprJson).toContain('returnValueTest'); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(JsonRpcCondition); + }); + it('compound condition serialization', () => { const conditionExpr = new ConditionExpression(compoundCondition); const compoundConditionObj = compoundCondition.toObj(); diff --git a/packages/taco/test/conditions/lingo.test.ts b/packages/taco/test/conditions/lingo.test.ts index bf3c3771..8b85a06e 100644 --- a/packages/taco/test/conditions/lingo.test.ts +++ b/packages/taco/test/conditions/lingo.test.ts @@ -58,6 +58,17 @@ describe('check that valid lingo in python is valid in typescript', () => { value: 2, }, }; + const jsonRpcConditionProps = { + conditionType: 'json-rpc', + endpoint: 'https://math.example.com/', + method: 'subtract', + params: [42, 23], + query: '$.value', + returnValueTest: { + comparator: '==', + value: 2, + }, + }; const sequentialConditionProps = { conditionType: 'sequential', conditionVariables: [ @@ -81,7 +92,7 @@ describe('check that valid lingo in python is valid in typescript', () => { }; const ifThenElseConditionProps = { conditionType: 'if-then-else', - ifCondition: rpcConditionProps, + ifCondition: jsonRpcConditionProps, thenCondition: jsonApiConditionProps, elseCondition: timeConditionProps, }; @@ -107,6 +118,7 @@ describe('check that valid lingo in python is valid in typescript', () => { timeConditionProps, contractConditionProps, jsonApiConditionProps, + jsonRpcConditionProps, compoundConditionProps, sequentialConditionProps, ifThenElseConditionProps, diff --git a/packages/taco/test/taco.test.ts b/packages/taco/test/taco.test.ts index 1906c34a..e3527264 100644 --- a/packages/taco/test/taco.test.ts +++ b/packages/taco/test/taco.test.ts @@ -172,4 +172,51 @@ describe('taco', () => { new Set([':userId', ':userAddress', ':authToken']), ); }); + // test json api condition exposes requested parameters + it('jsonrpc condition exposes requested parameters', async () => { + const mockedDkg = fakeDkgFlow(FerveoVariant.precomputed, 0, 4, 4); + const mockedDkgRitual = fakeDkgRitual(mockedDkg); + const provider = fakeProvider(aliceSecretKeyBytes); + const signer = provider.getSigner(); + const getFinalizedRitualSpy = mockGetActiveRitual(mockedDkgRitual); + + const jsonRpcCondition = new conditions.base.jsonRpc.JsonRpcCondition({ + endpoint: 'https://math.example.com/:version/simple', + method: ':methodContextVar', + params: { + value1: 42, + value2: ':value2', + }, + query: '$.:queryKey', + authorizationToken: ':authToken', + returnValueTest: { + comparator: '==', + value: ':expectedResult', + }, + }); + const messageKit = await taco.encrypt( + provider, + domains.DEVNET, + message, + jsonRpcCondition, + mockedDkg.ritualId, + signer, + ); + expect(getFinalizedRitualSpy).toHaveBeenCalled(); + + const conditionContext = ConditionContext.fromMessageKit(messageKit); + const requestedParameters = conditionContext.requestedContextParameters; + + // Verify all context parameters are detected + expect(requestedParameters).toEqual( + new Set([ + ':version', + ':methodContextVar', + ':value2', + ':queryKey', + ':authToken', + ':expectedResult', + ]), + ); + }); }); diff --git a/packages/taco/test/test-utils.ts b/packages/taco/test/test-utils.ts index 5622d27d..f1c7b19b 100644 --- a/packages/taco/test/test-utils.ts +++ b/packages/taco/test/test-utils.ts @@ -39,7 +39,10 @@ import { ContractConditionType, FunctionAbiProps, } from '../src/conditions/base/contract'; -import { JsonApiConditionType } from '../src/conditions/base/json-api'; +import { + JsonApiConditionProps, + JsonApiConditionType, +} from '../src/conditions/base/json-api'; import { RpcConditionProps, RpcConditionType, @@ -55,6 +58,10 @@ import { } from '../src/conditions/compound-condition'; import { ConditionExpression } from '../src/conditions/condition-expr'; import { ERC721Balance } from '../src/conditions/predefined/erc721'; +import { + JsonRpcConditionProps, + JsonRpcConditionType, +} from '../src/conditions/schemas/json-rpc'; import { SequentialConditionProps, SequentialConditionType, @@ -232,7 +239,7 @@ export const testTimeConditionObj: TimeConditionProps = { chain: TEST_CHAIN_ID, }; -export const testJsonApiConditionObj = { +export const testJsonApiConditionObj: JsonApiConditionProps = { conditionType: JsonApiConditionType, endpoint: 'https://_this_would_totally_fail.com', parameters: { @@ -243,6 +250,15 @@ export const testJsonApiConditionObj = { returnValueTest: testReturnValueTest, }; +export const testJsonRpcConditionObj: JsonRpcConditionProps = { + conditionType: JsonRpcConditionType, + endpoint: 'https://math.example.com/', + method: 'subtract', + params: [42, 23], + query: '$.mathresult', + returnValueTest: testReturnValueTest, +}; + export const testRpcConditionObj: RpcConditionProps = { conditionType: RpcConditionType, chain: TEST_CHAIN_ID,