diff --git a/packages/request-client.js/src/api/request-network.ts b/packages/request-client.js/src/api/request-network.ts index 169ec5d5cf..cdadae3e79 100644 --- a/packages/request-client.js/src/api/request-network.ts +++ b/packages/request-client.js/src/api/request-network.ts @@ -4,10 +4,12 @@ import { RequestLogic } from '@requestnetwork/request-logic'; import { TransactionManager } from '@requestnetwork/transaction-manager'; import { AdvancedLogicTypes, + ClientTypes, CurrencyTypes, DataAccessTypes, DecryptionProviderTypes, EncryptionTypes, + ExtensionTypes, IdentityTypes, PaymentTypes, RequestLogicTypes, @@ -20,6 +22,7 @@ import * as Types from '../types'; import ContentDataExtension from './content-data-extension'; import Request from './request'; import localUtils from './utils'; +import { NoPersistHttpDataAccess } from '../no-persist-http-data-access'; /** * Entry point of the request-client.js library. Create requests, get requests, manipulate requests. @@ -31,6 +34,7 @@ export default class RequestNetwork { private requestLogic: RequestLogicTypes.IRequestLogic; private transaction: TransactionTypes.ITransactionManager; private advancedLogic: AdvancedLogicTypes.IAdvancedLogic; + private dataAccess: DataAccessTypes.IDataAccess; private contentData: ContentDataExtension; private currencyManager: CurrencyTypes.ICurrencyManager; @@ -55,6 +59,7 @@ export default class RequestNetwork { paymentOptions?: Partial; }) { this.currencyManager = currencyManager || CurrencyManager.getDefault(); + this.dataAccess = dataAccess; this.advancedLogic = new AdvancedLogic(this.currencyManager); this.transaction = new TransactionManager(dataAccess, decryptionProvider); this.requestLogic = new RequestLogic(this.transaction, signatureProvider, this.advancedLogic); @@ -85,21 +90,27 @@ export default class RequestNetwork { topics, ); + const transactionData = requestLogicCreateResult.meta?.transactionManagerMeta.transactionData; + const requestId = requestLogicCreateResult.result.requestId; + const isSkippingPersistence = this.dataAccess instanceof NoPersistHttpDataAccess; // create the request object - const request = new Request( - requestLogicCreateResult.result.requestId, - this.requestLogic, - this.currencyManager, - { - contentDataExtension: this.contentData, - paymentNetwork, - requestLogicCreateResult, - skipPaymentDetection: parameters.disablePaymentDetection, - disableEvents: parameters.disableEvents, - }, - ); + const request = new Request(requestId, this.requestLogic, this.currencyManager, { + contentDataExtension: this.contentData, + paymentNetwork, + requestLogicCreateResult, + skipPaymentDetection: parameters.disablePaymentDetection, + disableEvents: parameters.disableEvents, + // inMemoryInfo is only used when skipPersistence is enabled + inMemoryInfo: isSkippingPersistence + ? { + topics: requestLogicCreateResult.meta.transactionManagerMeta?.topics, + transactionData: transactionData, + requestData: this.prepareRequestDataForPayment(transactionData, requestId), + } + : null, + }); - if (!options?.skipRefresh) { + if (!options?.skipRefresh && !isSkippingPersistence) { // refresh the local request data await request.refresh(); } @@ -107,6 +118,38 @@ export default class RequestNetwork { return request; } + /** + * Persists an in-memory request to the data-access layer. + * + * This method is used to persist requests that were initially created with skipPersistence enabled. + * + * @param request The Request object to persist. This must be a request that was created with skipPersistence enabled. + * @returns A promise that resolves to the result of the persist transaction operation. + * @throws {Error} If the request's `inMemoryInfo` is not provided, indicating it wasn't created with skipPersistence. + * @throws {Error} If the current data access instance does not support persistence (e.g., NoPersistHttpDataAccess). + */ + public async persistRequest( + request: Request, + ): Promise { + if (!request.inMemoryInfo) { + throw new Error('Cannot persist request without inMemoryInfo.'); + } + + if (this.dataAccess instanceof NoPersistHttpDataAccess) { + throw new Error( + 'Cannot persist request when skipPersistence is enabled. To persist the request, create a new instance of RequestNetwork without skipPersistence being set to true.', + ); + } + const result: DataAccessTypes.IReturnPersistTransaction = + await this.dataAccess.persistTransaction( + request.inMemoryInfo.transactionData, + request.requestId, + request.inMemoryInfo.topics, + ); + + return result; + } + /** * Creates an encrypted request. * @@ -129,21 +172,27 @@ export default class RequestNetwork { topics, ); + const transactionData = requestLogicCreateResult.meta?.transactionManagerMeta.transactionData; + const requestId = requestLogicCreateResult.result.requestId; + const isSkippingPersistence = this.dataAccess instanceof NoPersistHttpDataAccess; + // create the request object - const request = new Request( - requestLogicCreateResult.result.requestId, - this.requestLogic, - this.currencyManager, - { - contentDataExtension: this.contentData, - paymentNetwork, - requestLogicCreateResult, - skipPaymentDetection: parameters.disablePaymentDetection, - disableEvents: parameters.disableEvents, - }, - ); + const request = new Request(requestId, this.requestLogic, this.currencyManager, { + contentDataExtension: this.contentData, + paymentNetwork, + requestLogicCreateResult, + skipPaymentDetection: parameters.disablePaymentDetection, + disableEvents: parameters.disableEvents, + inMemoryInfo: isSkippingPersistence + ? { + topics: requestLogicCreateResult.meta.transactionManagerMeta?.topics, + transactionData: transactionData, + requestData: this.prepareRequestDataForPayment(transactionData, requestId), + } + : null, + }); - if (!options?.skipRefresh) { + if (!options?.skipRefresh && !isSkippingPersistence) { // refresh the local request data await request.refresh(); } @@ -437,4 +486,77 @@ export default class RequestNetwork { return { requestParameters: copiedRequestParameters, topics, paymentNetwork }; } + + /** + * Prepares a payment request structure from transaction data. + * + * This method is used to create a request structure similar to a persisted request, + * allowing users to pay before the request is persisted. This is useful in scenarios + * where a request is created, paid, and then persisted, as opposed to the normal flow + * of creating, persisting, and then paying the request. + * + * @param transactionData The transaction data containing the request information + * @param requestId The ID of the request + * @returns The prepared payment request structure or undefined if transaction data is missing + */ + private prepareRequestDataForPayment( + transactionData: DataAccessTypes.ITransaction, + requestId: string, + ): ClientTypes.IRequestData { + const requestData = JSON.parse(transactionData.data as string).data; + const originalExtensionsData = requestData.parameters.extensionsData; + const newExtensions: RequestLogicTypes.IExtensionStates = {}; + + for (const extension of originalExtensionsData) { + if (extension.id !== ExtensionTypes.OTHER_ID.CONTENT_DATA) { + newExtensions[extension.id] = { + events: [ + { + name: extension.action, + parameters: { + paymentAddress: extension.parameters.paymentAddress, + salt: extension.parameters.salt, + }, + timestamp: requestData.parameters.timestamp, + }, + ], + id: extension.id, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + salt: extension.parameters.salt, + receivedPaymentAmount: '0', + receivedRefundAmount: '0', + sentPaymentAmount: '0', + sentRefundAmount: '0', + paymentAddress: extension.parameters.paymentAddress, + }, + version: extension.version, + }; + } + } + + return { + requestId: requestId, + currency: requestData.parameters.currency.type, + meta: null, + balance: null, + expectedAmount: requestData.parameters.expectedAmount, + contentData: requestData.parameters.extensionsData.find( + (ext: ExtensionTypes.IAction) => ext.id === ExtensionTypes.OTHER_ID.CONTENT_DATA, + )?.parameters.content, + currencyInfo: { + type: requestData.parameters.currency.type, + network: requestData.parameters.currency.network, + value: requestData.parameters.currency.value || '', + }, + pending: null, + extensions: newExtensions, + extensionsData: requestData.parameters.extensionsData, + timestamp: requestData.parameters.timestamp, + version: requestData.parameters.version, + creator: requestData.parameters.creator, + state: requestData.parameters.state, + events: requestData.parameters.events, + }; + } } diff --git a/packages/request-client.js/src/api/request.ts b/packages/request-client.js/src/api/request.ts index 2763889bb1..bfaf15ead6 100644 --- a/packages/request-client.js/src/api/request.ts +++ b/packages/request-client.js/src/api/request.ts @@ -78,6 +78,17 @@ export default class Request { */ private currencyManager: CurrencyTypes.ICurrencyManager; + /** + * Information for an in-memory request, including transaction data, topics, and payment request data. + * This is used for requests that haven't been persisted yet, allowing for operations like payments + * before the request is stored in the data access layer. + * + * @property transactionData - Transaction data necessary for persisting the request later on. + * @property topics - Topics of the request, used for indexing and retrieval when persisting. + * @property requestData - Structured data primarily used for processing payments before the request is persisted. + */ + public readonly inMemoryInfo: RequestLogicTypes.IInMemoryInfo | null = null; + /** * Creates an instance of Request * @@ -98,6 +109,7 @@ export default class Request { requestLogicCreateResult?: RequestLogicTypes.IReturnCreateRequest; skipPaymentDetection?: boolean; disableEvents?: boolean; + inMemoryInfo?: RequestLogicTypes.IInMemoryInfo | null; }, ) { this.requestLogic = requestLogic; @@ -108,6 +120,7 @@ export default class Request { this.skipPaymentDetection = options?.skipPaymentDetection || false; this.disableEvents = options?.disableEvents || false; this.currencyManager = currencyManager; + this.inMemoryInfo = options?.inMemoryInfo || null; if (options?.requestLogicCreateResult && !this.disableEvents) { const originalEmitter = options.requestLogicCreateResult; diff --git a/packages/request-client.js/src/http-request-network.ts b/packages/request-client.js/src/http-request-network.ts index bb4ad85fac..4092876fdc 100644 --- a/packages/request-client.js/src/http-request-network.ts +++ b/packages/request-client.js/src/http-request-network.ts @@ -11,6 +11,7 @@ import RequestNetwork from './api/request-network'; import HttpDataAccess, { NodeConnectionConfig } from './http-data-access'; import { MockDataAccess } from '@requestnetwork/data-access'; import { MockStorage } from './mock-storage'; +import { NoPersistHttpDataAccess } from './no-persist-http-data-access'; /** * Exposes RequestNetwork module configured to use http-data-access. @@ -21,10 +22,11 @@ export default class HttpRequestNetwork extends RequestNetwork { * * @param options.httpConfig Http config that will be used by the underlying data-access. @see ClientTypes.IHttpDataAccessConfig for available options. * @param options.nodeConnectionConfig Configuration options to connect to the node. - * @param options.useMockStorage When true, will use a mock storage in memory. Meant to simplify local development and should never be used in production. + * @param options.useMockStorage When true, will use a mock storage in memory. Meant to simplify local development and should never be used in production. Overrides `skipPersistence` when both are true. * @param options.signatureProvider Module to handle the signature. If not given it will be impossible to create new transaction (it requires to sign). - * @param options.currencies custom currency list - * @param options.currencyManager custom currency manager (will override `currencies`) + * @param options.currencies custom currency list. + * @param options.currencyManager custom currency manager (will override `currencies`). + * @param options.skipPersistence allows creating a transaction without immediate persistence. */ constructor( { @@ -35,6 +37,7 @@ export default class HttpRequestNetwork extends RequestNetwork { useMockStorage, currencyManager, paymentOptions, + skipPersistence, }: { decryptionProvider?: DecryptionProviderTypes.IDecryptionProvider; httpConfig?: Partial; @@ -43,13 +46,20 @@ export default class HttpRequestNetwork extends RequestNetwork { useMockStorage?: boolean; currencyManager?: CurrencyTypes.ICurrencyManager; paymentOptions?: Partial; + skipPersistence?: boolean; } = { httpConfig: {}, useMockStorage: false, + skipPersistence: false, }, ) { const dataAccess: DataAccessTypes.IDataAccess = useMockStorage ? new MockDataAccess(new MockStorage()) + : skipPersistence + ? new NoPersistHttpDataAccess({ + httpConfig, + nodeConnectionConfig, + }) : new HttpDataAccess({ httpConfig, nodeConnectionConfig }); if (!currencyManager) { diff --git a/packages/request-client.js/src/no-persist-http-data-access.ts b/packages/request-client.js/src/no-persist-http-data-access.ts new file mode 100644 index 0000000000..d2b3a4769a --- /dev/null +++ b/packages/request-client.js/src/no-persist-http-data-access.ts @@ -0,0 +1,48 @@ +import HttpDataAccess, { NodeConnectionConfig } from './http-data-access'; +import { ClientTypes, DataAccessTypes, StorageTypes } from '@requestnetwork/types'; +import { EventEmitter } from 'events'; + +export class NoPersistHttpDataAccess extends HttpDataAccess { + constructor( + { + httpConfig, + nodeConnectionConfig, + }: { + httpConfig?: Partial; + nodeConnectionConfig?: Partial; + } = { + httpConfig: {}, + nodeConnectionConfig: {}, + }, + ) { + super({ httpConfig, nodeConnectionConfig }); + } + + async persistTransaction( + transactionData: DataAccessTypes.ITransaction, + channelId: string, + topics?: string[], + ): Promise { + const data: DataAccessTypes.IReturnPersistTransactionRaw = { + meta: { + topics: topics || [], + transactionStorageLocation: '', + storageMeta: { + state: StorageTypes.ContentState.PENDING, + timestamp: Date.now() / 1000, + }, + }, + result: {}, + }; + + const result: DataAccessTypes.IReturnPersistTransaction = Object.assign( + new EventEmitter() as DataAccessTypes.PersistTransactionEmitter, + data, + ); + + // Emit confirmation instantly since data is not going to be persisted + result.emit('confirmed', result); + + return result; + } +} diff --git a/packages/request-client.js/test/http-request-network.test.ts b/packages/request-client.js/test/http-request-network.test.ts index 5f4c7bb51d..40d0a0be57 100644 --- a/packages/request-client.js/test/http-request-network.test.ts +++ b/packages/request-client.js/test/http-request-network.test.ts @@ -18,7 +18,7 @@ afterAll(() => { }); afterEach(() => { - mockServer.restoreHandlers(); + mockServer.resetHandlers(); }); describe('HttpRequestNetwork', () => { diff --git a/packages/request-client.js/test/in-memory-request.test.ts b/packages/request-client.js/test/in-memory-request.test.ts new file mode 100644 index 0000000000..e2cf5164fc --- /dev/null +++ b/packages/request-client.js/test/in-memory-request.test.ts @@ -0,0 +1,120 @@ +import { RequestNetwork } from '../src/index'; +import * as TestData from './data-test'; + +import { http, HttpResponse } from 'msw'; +import { setupServer, SetupServer } from 'msw/node'; +import config from '../src/http-config-defaults'; + +describe('handle in-memory request', () => { + let requestNetwork: RequestNetwork; + let spyPersistTransaction: jest.Mock; + let mockServer: SetupServer; + + beforeAll(() => { + spyPersistTransaction = jest.fn(); + + mockServer = setupServer( + http.post('*/persistTransaction', ({ request }) => { + if (!request.headers.get(config.requestClientVersionHeader)) { + throw new Error('Missing version header'); + } + return HttpResponse.json(spyPersistTransaction()); + }), + http.get('*/getTransactionsByChannelId', () => + HttpResponse.json({ + result: { transactions: [TestData.timestampedTransactionWithoutPaymentInfo] }, + }), + ), + http.post('*/ipfsAdd', () => HttpResponse.json({})), + http.get('*/getConfirmedTransaction', () => HttpResponse.json({ result: {} })), + ); + mockServer.listen({ onUnhandledRequest: 'bypass' }); + spyPersistTransaction.mockReturnValue({}); + }); + + afterAll(async () => { + mockServer.close(); + mockServer.resetHandlers(); + }); + + const requestCreationParams = { + paymentNetwork: TestData.declarativePaymentNetworkNoPaymentInfo, + requestInfo: TestData.parametersWithoutExtensionsData, + signer: TestData.payee.identity, + }; + + it('creates a request without persisting it.', async () => { + requestNetwork = new RequestNetwork({ + skipPersistence: true, + signatureProvider: TestData.fakeSignatureProvider, + }); + + const request = await requestNetwork.createRequest(requestCreationParams); + + expect(request).toBeDefined(); + expect(request.requestId).toBeDefined(); + expect(request.inMemoryInfo).toBeDefined(); + expect(request.inMemoryInfo?.requestData).toBeDefined(); + expect(request.inMemoryInfo?.topics).toBeDefined(); + expect(request.inMemoryInfo?.transactionData).toBeDefined(); + expect(spyPersistTransaction).not.toHaveBeenCalled(); + }); + + it('throws an error when trying to persist a request with skipPersistence as true', async () => { + requestNetwork = new RequestNetwork({ + skipPersistence: true, + signatureProvider: TestData.fakeSignatureProvider, + }); + + const request = await requestNetwork.createRequest(requestCreationParams); + + expect(request.inMemoryInfo).toBeDefined(); + expect(request.inMemoryInfo?.requestData).toBeDefined(); + expect(request.inMemoryInfo?.topics).toBeDefined(); + expect(request.inMemoryInfo?.transactionData).toBeDefined(); + expect(request.requestId).toBeDefined(); + + await expect(requestNetwork.persistRequest(request)).rejects.toThrow( + 'Cannot persist request when skipPersistence is enabled. To persist the request, create a new instance of RequestNetwork without skipPersistence being set to true.', + ); + }); + + it('throw an error when trying to persist a request without inMemoryInfo', async () => { + requestNetwork = new RequestNetwork({ + signatureProvider: TestData.fakeSignatureProvider, + }); + + const request = await requestNetwork.createRequest(requestCreationParams); + await request.waitForConfirmation(); + expect(request.inMemoryInfo).toBeNull(); + + await expect(requestNetwork.persistRequest(request)).rejects.toThrow( + 'Cannot persist request without inMemoryInfo', + ); + }); + + it('persists a previously created in-memory request', async () => { + requestNetwork = new RequestNetwork({ + skipPersistence: true, + signatureProvider: TestData.fakeSignatureProvider, + }); + + const request = await requestNetwork.createRequest(requestCreationParams); + + expect(request.inMemoryInfo).toBeDefined(); + expect(request.inMemoryInfo?.requestData).toBeDefined(); + expect(request.inMemoryInfo?.topics).toBeDefined(); + expect(request.inMemoryInfo?.transactionData).toBeDefined(); + expect(request.requestId).toBeDefined(); + + const newRequestNetwork = new RequestNetwork({ + signatureProvider: TestData.fakeSignatureProvider, + useMockStorage: true, + }); + + const persistResult = await newRequestNetwork.persistRequest(request); + + expect(persistResult).toBeDefined(); + expect(spyPersistTransaction).toHaveBeenCalled(); + }); +}); diff --git a/packages/transaction-manager/src/transaction-manager.ts b/packages/transaction-manager/src/transaction-manager.ts index d693c80aac..81cded9559 100644 --- a/packages/transaction-manager/src/transaction-manager.ts +++ b/packages/transaction-manager/src/transaction-manager.ts @@ -109,6 +109,8 @@ export default class TransactionManager implements TransactionTypes.ITransaction meta: { dataAccessMeta: persistResult.meta, encryptionMethod: channelEncryptionMethod, + transactionData: transaction, + topics: topics.concat([hash]), }, result: {}, }); diff --git a/packages/transaction-manager/test/index.test.ts b/packages/transaction-manager/test/index.test.ts index 2e863f5712..2c2e1be486 100644 --- a/packages/transaction-manager/test/index.test.ts +++ b/packages/transaction-manager/test/index.test.ts @@ -97,10 +97,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual({}); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, - encryptionMethod: undefined, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, + encryptionMethod: undefined, + }), + ); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledWith( await TransactionsFactory.createClearTransaction(data), channelId, @@ -120,10 +122,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual({}); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, - encryptionMethod: 'ecies-aes256-gcm', - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, + encryptionMethod: 'ecies-aes256-gcm', + }), + ); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledTimes(1); }); @@ -153,10 +157,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual({}); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, - encryptionMethod: undefined, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, + encryptionMethod: undefined, + }), + ); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledWith( await TransactionsFactory.createClearTransaction(data), channelId, @@ -177,10 +183,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual({}); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, - encryptionMethod: undefined, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, + encryptionMethod: undefined, + }), + ); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledWith( await TransactionsFactory.createClearTransaction(data2), channelId, @@ -230,10 +238,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual({}); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, - encryptionMethod: 'ecies-aes256-gcm', - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, + encryptionMethod: 'ecies-aes256-gcm', + }), + ); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledTimes(1); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledWith( @@ -319,10 +329,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual({}); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, - encryptionMethod: 'ecies-aes256-gcm', - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessPersistReturn.meta, + encryptionMethod: 'ecies-aes256-gcm', + }), + ); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledTimes(1); expect(fakeDataAccess.persistTransaction).toHaveBeenCalledWith( @@ -348,10 +360,12 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual(fakeMetaDataAccessGetReturn.result); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturn.meta, - ignoredTransactions: [null, null], - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturn.meta, + ignoredTransactions: [null, null], + }), + ); expect(fakeDataAccess.getTransactionsByChannelId).toHaveBeenCalledWith(channelId, undefined); }); @@ -384,17 +398,20 @@ describe('index', () => { const ret = await transactionManager.getTransactionsByChannelId(channelId); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnFirstHashWrong.meta, - ignoredTransactions: [ - { - reason: 'as first transaction, the hash of the transaction do not match the channelId', - transaction: txWrongHash, - }, - null, - null, - ], - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnFirstHashWrong.meta, + ignoredTransactions: [ + { + reason: + 'as first transaction, the hash of the transaction do not match the channelId', + transaction: txWrongHash, + }, + null, + null, + ], + }), + ); // 'ret.result is wrong' expect(ret.result).toEqual({ @@ -432,17 +449,19 @@ describe('index', () => { const ret = await transactionManager.getTransactionsByChannelId(channelId); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnFirstHashWrong.meta, - ignoredTransactions: [ - { - reason: 'Impossible to JSON parse the transaction', - transaction: txWrongHash, - }, - null, - null, - ], - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnFirstHashWrong.meta, + ignoredTransactions: [ + { + reason: 'Impossible to JSON parse the transaction', + transaction: txWrongHash, + }, + null, + null, + ], + }), + ); // 'ret.result is wrong' expect(ret.result).toEqual({ @@ -1099,12 +1118,14 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual(fakeMetaDataAccessGetChannelsReturn.result); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetChannelsReturn.meta, - ignoredTransactions: { - '01a98f126de3fab2b5130af5161998bf6e59b2c380deafeff938ff3f798281bf23': [null, null], - }, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetChannelsReturn.meta, + ignoredTransactions: { + '01a98f126de3fab2b5130af5161998bf6e59b2c380deafeff938ff3f798281bf23': [null, null], + }, + }), + ); expect(fakeDataAccess.getChannelsByTopic).toHaveBeenCalledWith(extraTopics[0], undefined); }); @@ -1160,12 +1181,14 @@ describe('index', () => { }, }); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, - ignoredTransactions: { - [channelId]: [null], - }, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, + ignoredTransactions: { + [channelId]: [null], + }, + }), + ); expect(fakeDataAccess.getChannelsByTopic).toHaveBeenCalledWith(extraTopics[0], undefined); }); @@ -1218,21 +1241,23 @@ describe('index', () => { }, }); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, - ignoredTransactions: { - [channelId]: [ - { - reason: 'No decryption provider given', - transaction: { - state: TransactionTypes.TransactionState.PENDING, - timestamp: 1, - transaction: encryptedTx, + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, + ignoredTransactions: { + [channelId]: [ + { + reason: 'No decryption provider given', + transaction: { + state: TransactionTypes.TransactionState.PENDING, + timestamp: 1, + transaction: encryptedTx, + }, }, - }, - ], - }, - }); + ], + }, + }), + ); expect(fakeDataAccess.getChannelsByTopic).toHaveBeenCalledWith(extraTopics[0], undefined); }); @@ -1297,22 +1322,24 @@ describe('index', () => { }, }); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, - ignoredTransactions: { - [channelId]: [ - { - reason: 'No decryption provider given', - transaction: { - state: TransactionTypes.TransactionState.PENDING, - timestamp: 1, - transaction: encryptedTx, + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, + ignoredTransactions: { + [channelId]: [ + { + reason: 'No decryption provider given', + transaction: { + state: TransactionTypes.TransactionState.PENDING, + timestamp: 1, + transaction: encryptedTx, + }, }, - }, - null, - ], - }, - }); + null, + ], + }, + }), + ); expect(fakeDataAccess.getChannelsByTopic).toHaveBeenCalledWith(extraTopics[0], undefined); }); @@ -1350,20 +1377,22 @@ describe('index', () => { transactions: { [channelId]: [null, tx, tx2] }, }); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnFirstHashWrong.meta, - ignoredTransactions: { - [channelId]: [ - { - reason: - 'as first transaction, the hash of the transaction do not match the channelId', - transaction: txWrongHash, - }, - null, - null, - ], - }, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnFirstHashWrong.meta, + ignoredTransactions: { + [channelId]: [ + { + reason: + 'as first transaction, the hash of the transaction do not match the channelId', + transaction: txWrongHash, + }, + null, + null, + ], + }, + }), + ); expect(fakeDataAccess.getChannelsByTopic).toHaveBeenCalledWith(extraTopics[0], undefined); }); @@ -1428,13 +1457,15 @@ describe('index', () => { }, }); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, - ignoredTransactions: { - [channelId]: [null], - [channelId2]: [null], - }, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetReturnWithEncryptedTransaction.meta, + ignoredTransactions: { + [channelId]: [null], + [channelId2]: [null], + }, + }), + ); expect(fakeDataAccess.getChannelsByTopic).toHaveBeenCalledWith(extraTopics[0], undefined); }); }); @@ -1448,12 +1479,14 @@ describe('index', () => { // 'ret.result is wrong' expect(ret.result).toEqual(fakeMetaDataAccessGetChannelsReturn.result); // 'ret.meta is wrong' - expect(ret.meta).toEqual({ - dataAccessMeta: fakeMetaDataAccessGetChannelsReturn.meta, - ignoredTransactions: { - '01a98f126de3fab2b5130af5161998bf6e59b2c380deafeff938ff3f798281bf23': [null, null], - }, - }); + expect(ret.meta).toEqual( + expect.objectContaining({ + dataAccessMeta: fakeMetaDataAccessGetChannelsReturn.meta, + ignoredTransactions: { + '01a98f126de3fab2b5130af5161998bf6e59b2c380deafeff938ff3f798281bf23': [null, null], + }, + }), + ); // eslint-disable-next-line @typescript-eslint/unbound-method expect(fakeDataAccess.getChannelsByMultipleTopics).toHaveBeenCalledWith( [extraTopics[0]], diff --git a/packages/types/src/request-logic-types.ts b/packages/types/src/request-logic-types.ts index 3aea78052b..2caabf596b 100644 --- a/packages/types/src/request-logic-types.ts +++ b/packages/types/src/request-logic-types.ts @@ -5,6 +5,8 @@ import * as Extension from './extension-types'; import * as Identity from './identity-types'; import * as Signature from './signature-types'; import * as Transaction from './transaction-types'; +import * as DataAccess from './data-access-types'; +import * as Client from './client-types'; import { CurrencyTypes } from './index'; /** Request Logic layer */ @@ -212,6 +214,13 @@ export interface IAcceptParameters { extensionsData?: any[]; } +// ** Parameters added to the request when it is created with skipping persistence */ +export interface IInMemoryInfo { + transactionData: DataAccess.ITransaction; + topics: string[]; + requestData: Client.IRequestData; +} + /** Parameters to cancel a request */ export interface ICancelParameters { requestId: RequestId;