From 8a135bd9835dd65590b8bf4a1024643c3914a989 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Sat, 27 Jan 2024 15:42:14 +0100 Subject: [PATCH] add test for transmitToIota context, move some test init code into seeding --- dlt-connector/jest.config.js | 2 +- dlt-connector/src/data/Account.logic.ts | 35 ++++ dlt-connector/src/data/Transaction.logic.ts | 23 ++- .../src/graphql/input/CommunityDraft.ts | 3 +- .../CreateTransactionRecipe.context.test.ts | 99 ++--------- .../transaction/TransactionRecipe.role.ts | 5 +- .../AbstractTransactionRecipe.role.ts | 8 +- .../TransmitToIota.context.test.ts | 167 ++++++++++++++++++ .../transmitToIota/TransmitToIota.context.ts | 17 +- .../src/logging/TransactionLogging.view.ts | 15 +- dlt-connector/test/seeding/Community.seed.ts | 28 +++ dlt-connector/test/seeding/UserSet.seed.ts | 55 ++++++ 12 files changed, 355 insertions(+), 102 deletions(-) create mode 100644 dlt-connector/src/data/Account.logic.ts create mode 100644 dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.test.ts create mode 100644 dlt-connector/test/seeding/Community.seed.ts create mode 100644 dlt-connector/test/seeding/UserSet.seed.ts diff --git a/dlt-connector/jest.config.js b/dlt-connector/jest.config.js index 69bc64bb2..2de18cf50 100644 --- a/dlt-connector/jest.config.js +++ b/dlt-connector/jest.config.js @@ -6,7 +6,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 66, + lines: 71, }, }, setupFiles: ['/test/testSetup.ts'], diff --git a/dlt-connector/src/data/Account.logic.ts b/dlt-connector/src/data/Account.logic.ts new file mode 100644 index 000000000..9cff66070 --- /dev/null +++ b/dlt-connector/src/data/Account.logic.ts @@ -0,0 +1,35 @@ +import { Account } from '@entity/Account' + +import { LogError } from '@/server/LogError' + +import { KeyPair } from './KeyPair' +import { UserLogic } from './User.logic' + +export class AccountLogic { + // eslint-disable-next-line no-useless-constructor + public constructor(private self: Account) {} + + /** + * calculate account key pair starting from community key pair => derive user key pair => derive account key pair + * @param communityKeyPair + */ + public calculateKeyPair(communityKeyPair: KeyPair): KeyPair { + if (!this.self.user) { + throw new LogError('missing user') + } + const userLogic = new UserLogic(this.self.user) + const accountKeyPair = userLogic + .calculateKeyPair(communityKeyPair) + .derive([this.self.derivationIndex]) + + if ( + this.self.derive2Pubkey && + this.self.derive2Pubkey.compare(accountKeyPair.publicKey) !== 0 + ) { + throw new LogError( + 'The freshly derived public key does not correspond to the stored public key', + ) + } + return accountKeyPair + } +} diff --git a/dlt-connector/src/data/Transaction.logic.ts b/dlt-connector/src/data/Transaction.logic.ts index 62f7c3732..9ca6330ba 100644 --- a/dlt-connector/src/data/Transaction.logic.ts +++ b/dlt-connector/src/data/Transaction.logic.ts @@ -77,14 +77,14 @@ export class TransactionLogic { logger.info('id is the same, it is the same transaction!') return false } + if ( this.self.signingAccountId !== otherTransaction.signingAccountId || this.self.recipientAccountId !== otherTransaction.recipientAccountId || this.self.communityId !== otherTransaction.communityId || this.self.otherCommunityId !== otherTransaction.otherCommunityId || - this.self.amount !== otherTransaction.amount || this.self.accountBalanceOnCreation !== otherTransaction.accountBalanceOnCreation || - this.self.createdAt !== otherTransaction.createdAt + this.self.createdAt.getTime() !== otherTransaction.createdAt.getTime() ) { logger.debug('transaction a and b are not pairs', { a: new TransactionLoggingView(this.self), @@ -135,6 +135,25 @@ export class TransactionLogic { logger.info(`TransactionType ${type} couldn't be a CrossGroup Transaction`) return false } + if ( + [ + TransactionType.GRADIDO_CREATION, + TransactionType.GRADIDO_TRANSFER, + TransactionType.GRADIDO_DEFERRED_TRANSFER, + ].includes(type) + ) { + if (!this.self.amount || !otherTransaction.amount) { + logger.info('missing amount') + return false + } + if (this.self.amount.cmp(otherTransaction.amount.toString())) { + logger.info('amounts mismatch', { + a: this.self.amount.toString(), + b: otherTransaction.amount.toString(), + }) + return false + } + } if (body.otherGroup === otherBody.otherGroup) { logger.info('otherGroups are the same', { a: new TransactionBodyLoggingView(body), diff --git a/dlt-connector/src/graphql/input/CommunityDraft.ts b/dlt-connector/src/graphql/input/CommunityDraft.ts index 665e10b75..0b26e68d0 100644 --- a/dlt-connector/src/graphql/input/CommunityDraft.ts +++ b/dlt-connector/src/graphql/input/CommunityDraft.ts @@ -1,10 +1,9 @@ // https://www.npmjs.com/package/@apollo/protobufjs +import { isValidDateString } from '@validator/DateString' import { IsBoolean, IsUUID } from 'class-validator' import { Field, InputType } from 'type-graphql' -import { isValidDateString } from '@validator/DateString' - @InputType() export class CommunityDraft { @Field(() => String) diff --git a/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts b/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts index 03474ff28..9ddbebd06 100644 --- a/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts +++ b/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts @@ -1,33 +1,25 @@ import 'reflect-metadata' import { Account } from '@entity/Account' -import { Community } from '@entity/Community' -import { User } from '@entity/User' import { TestDB } from '@test/TestDB' import { Decimal } from 'decimal.js-light' import { v4 } from 'uuid' import { CONFIG } from '@/config' -import { AccountFactory } from '@/data/Account.factory' import { KeyPair } from '@/data/KeyPair' import { Mnemonic } from '@/data/Mnemonic' import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType' import { TransactionType } from '@/data/proto/3_3/enum/TransactionType' import { TransactionBody } from '@/data/proto/3_3/TransactionBody' -import { UserFactory } from '@/data/User.factory' -import { UserLogic } from '@/data/User.logic' -import { AccountType } from '@/graphql/enum/AccountType' import { InputTransactionType } from '@/graphql/enum/InputTransactionType' -import { CommunityDraft } from '@/graphql/input/CommunityDraft' import { TransactionDraft } from '@/graphql/input/TransactionDraft' -import { UserAccountDraft } from '@/graphql/input/UserAccountDraft' -import { UserIdentifier } from '@/graphql/input/UserIdentifier' -import { TransactionBodyLoggingView } from '@/logging/TransactionBodyLogging.view' -import { TransactionLoggingView } from '@/logging/TransactionLogging.view' - -import { AddCommunityContext } from '../community/AddCommunity.context' +import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' import { CreateTransactionRecipeContext } from './CreateTransationRecipe.context' -import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' + +// eslint-disable-next-line import/order +import { communitySeed } from '@test/seeding/Community.seed' +// eslint-disable-next-line import/order +import { createUserSet, UserSet } from '@test/seeding/UserSet.seed' jest.mock('@typeorm/DataSource', () => ({ getDataSource: jest.fn(() => TestDB.instance.dbConnect), @@ -37,58 +29,11 @@ CONFIG.IOTA_HOME_COMMUNITY_SEED = '034b0229a2ba4e98e1cc5e8767dca886279b484303ffa const homeCommunityUuid = v4() const foreignCommunityUuid = v4() -type UserSet = { - identifier: UserIdentifier - user: User - account: Account -} - -function createUserIdentifier(userUuid: string, communityUuid: string): UserIdentifier { - const user = new UserIdentifier() - user.uuid = userUuid - user.communityUuid = communityUuid - return user -} - const keyPair = new KeyPair(new Mnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED)) const foreignKeyPair = new KeyPair( new Mnemonic('5d4e163c078cc6b51f5c88f8422bc8f21d1d59a284515ab1ea79e1c176ebec50'), ) -function createUserAndAccount(userIdentifier: UserIdentifier): Account { - const accountDraft = new UserAccountDraft() - accountDraft.user = userIdentifier - accountDraft.createdAt = new Date().toISOString() - accountDraft.accountType = AccountType.COMMUNITY_HUMAN - let _keyPair: KeyPair - if (userIdentifier.communityUuid === homeCommunityUuid) { - _keyPair = keyPair - } else { - _keyPair = foreignKeyPair - } - const user = UserFactory.create(accountDraft, _keyPair) - const userLogic = new UserLogic(user) - const account = AccountFactory.createAccountFromUserAccountDraft( - accountDraft, - userLogic.calculateKeyPair(_keyPair), - ) - account.user = user - return account -} - -function createUserSet(userUuid: string, communityUuid: string): UserSet { - const identifier = createUserIdentifier(userUuid, communityUuid) - const account = createUserAndAccount(identifier) - if (!account.user) { - throw Error('user missing') - } - return { - identifier, - account, - user: account.user, - } -} - let moderator: UserSet let firstUser: UserSet let secondUser: UserSet @@ -100,29 +45,13 @@ const foreignTopic = iotaTopicFromCommunityUUID(foreignCommunityUuid) describe('interactions/backendToDb/transaction/Create Transaction Recipe Context Test', () => { beforeAll(async () => { await TestDB.instance.setupTestDB() - const homeCommunityDraft = new CommunityDraft() - homeCommunityDraft.uuid = homeCommunityUuid - homeCommunityDraft.foreign = false - homeCommunityDraft.createdAt = '2024-01-25T13:09:55.339Z' - let addCommunityContext = new AddCommunityContext(homeCommunityDraft) - await addCommunityContext.run() + await communitySeed(homeCommunityUuid, false) + await communitySeed(foreignCommunityUuid, true, foreignKeyPair) - const foreignCommunityDraft = new CommunityDraft() - foreignCommunityDraft.uuid = foreignCommunityUuid - foreignCommunityDraft.foreign = true - foreignCommunityDraft.createdAt = '2024-01-25T13:34:28.020Z' - addCommunityContext = new AddCommunityContext(foreignCommunityDraft) - await addCommunityContext.run() - - const foreignCommunity = await Community.findOneOrFail({ where: { foreign: true } }) - // that isn't entirely correct, normally only the public key from foreign community is know, and will be come form blockchain - foreignKeyPair.fillInCommunityKeys(foreignCommunity) - foreignCommunity.save() - - moderator = createUserSet('ff8bbdcb-fc8b-4b5d-98e3-8bd7e1afcdbb', homeCommunityUuid) - firstUser = createUserSet('8e47e32e-0182-4099-b94d-0cac567d1392', homeCommunityUuid) - secondUser = createUserSet('9c8611dd-ee93-4cdb-a600-396c2ca91cc7', homeCommunityUuid) - foreignUser = createUserSet('b0155716-5219-4c50-b3d3-0757721ae0d2', foreignCommunityUuid) + moderator = createUserSet(homeCommunityUuid, keyPair) + firstUser = createUserSet(homeCommunityUuid, keyPair) + secondUser = createUserSet(homeCommunityUuid, keyPair) + foreignUser = createUserSet(foreignCommunityUuid, foreignKeyPair) await Account.save([ moderator.account, @@ -197,7 +126,6 @@ describe('interactions/backendToDb/transaction/Create Transaction Recipe Context sendTransactionDraft.linkedUser = secondUser.identifier sendTransactionDraft.user = firstUser.identifier sendTransactionDraft.type = InputTransactionType.SEND - sendTransactionDraft.targetDate = new Date().toISOString() const context = new CreateTransactionRecipeContext(sendTransactionDraft) await context.run() const transaction = context.getTransactionRecipe() @@ -251,7 +179,6 @@ describe('interactions/backendToDb/transaction/Create Transaction Recipe Context recvTransactionDraft.linkedUser = firstUser.identifier recvTransactionDraft.user = secondUser.identifier recvTransactionDraft.type = InputTransactionType.RECEIVE - recvTransactionDraft.targetDate = new Date().toISOString() const context = new CreateTransactionRecipeContext(recvTransactionDraft) await context.run() const transaction = context.getTransactionRecipe() @@ -304,7 +231,6 @@ describe('interactions/backendToDb/transaction/Create Transaction Recipe Context crossGroupSendTransactionDraft.linkedUser = foreignUser.identifier crossGroupSendTransactionDraft.user = firstUser.identifier crossGroupSendTransactionDraft.type = InputTransactionType.SEND - crossGroupSendTransactionDraft.targetDate = new Date().toISOString() const context = new CreateTransactionRecipeContext(crossGroupSendTransactionDraft) await context.run() const transaction = context.getTransactionRecipe() @@ -361,7 +287,6 @@ describe('interactions/backendToDb/transaction/Create Transaction Recipe Context crossGroupRecvTransactionDraft.linkedUser = firstUser.identifier crossGroupRecvTransactionDraft.user = foreignUser.identifier crossGroupRecvTransactionDraft.type = InputTransactionType.RECEIVE - crossGroupRecvTransactionDraft.targetDate = new Date().toISOString() const context = new CreateTransactionRecipeContext(crossGroupRecvTransactionDraft) await context.run() const transaction = context.getTransactionRecipe() diff --git a/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts b/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts index d36aa98cc..7c6d3015d 100644 --- a/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts +++ b/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts @@ -1,5 +1,6 @@ import { Transaction } from '@entity/Transaction' +import { AccountLogic } from '@/data/Account.logic' import { KeyPair } from '@/data/KeyPair' import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder' import { TransactionBuilder } from '@/data/Transaction.builder' @@ -57,9 +58,11 @@ export class TransactionRecipeRole { await this.transactionBuilder.setOtherCommunityFromRecipientUser(recipientUser) } const transaction = this.transactionBuilder.getTransaction() + const communityKeyPair = new KeyPair(this.transactionBuilder.getCommunity()) + const accountLogic = new AccountLogic(signingAccount) // sign this.transactionBuilder.setSignature( - new KeyPair(this.transactionBuilder.getCommunity()).sign(transaction.bodyBytes), + accountLogic.calculateKeyPair(communityKeyPair).sign(transaction.bodyBytes), ) return this } diff --git a/dlt-connector/src/interactions/transmitToIota/AbstractTransactionRecipe.role.ts b/dlt-connector/src/interactions/transmitToIota/AbstractTransactionRecipe.role.ts index 21285705e..23fd9d275 100644 --- a/dlt-connector/src/interactions/transmitToIota/AbstractTransactionRecipe.role.ts +++ b/dlt-connector/src/interactions/transmitToIota/AbstractTransactionRecipe.role.ts @@ -11,14 +11,14 @@ import { GradidoTransactionLoggingView } from '@/logging/GradidoTransactionLoggi import { logger } from '@/logging/logger' export abstract class AbstractTransactionRecipeRole { - protected transactionBody: TransactionBody | undefined - // eslint-disable-next-line no-useless-constructor - public constructor(protected self: Transaction) {} + protected transactionBody: TransactionBody + public constructor(protected self: Transaction) { + this.transactionBody = TransactionBody.fromBodyBytes(this.self.bodyBytes) + } public abstract transmitToIota(): Promise protected getGradidoTransaction(): GradidoTransaction { - this.transactionBody = TransactionBody.fromBodyBytes(this.self.bodyBytes) const transaction = new GradidoTransaction(this.transactionBody) if (!this.self.signature) { throw new TransactionError( diff --git a/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.test.ts b/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.test.ts new file mode 100644 index 000000000..8e1ba4f33 --- /dev/null +++ b/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.test.ts @@ -0,0 +1,167 @@ +import 'reflect-metadata' +import { Account } from '@entity/Account' +import { TestDB } from '@test/TestDB' +import { Decimal } from 'decimal.js-light' +import { v4 } from 'uuid' + +import { CONFIG } from '@/config' +import { KeyPair } from '@/data/KeyPair' +import { Mnemonic } from '@/data/Mnemonic' +import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType' +import { TransactionBody } from '@/data/proto/3_3/TransactionBody' +import { InputTransactionType } from '@/graphql/enum/InputTransactionType' +import { TransactionDraft } from '@/graphql/input/TransactionDraft' +import { logger } from '@/logging/logger' + +import { CreateTransactionRecipeContext } from '../backendToDb/transaction/CreateTransationRecipe.context' + +import { TransmitToIotaContext } from './TransmitToIota.context' + +// eslint-disable-next-line import/order +import { communitySeed } from '@test/seeding/Community.seed' +// eslint-disable-next-line import/order +import { createUserSet, UserSet } from '@test/seeding/UserSet.seed' + +jest.mock('@typeorm/DataSource', () => ({ + getDataSource: jest.fn(() => TestDB.instance.dbConnect), +})) + +jest.mock('@/client/IotaClient', () => { + return { + sendMessage: jest.fn().mockReturnValue({ + messageId: '5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710', + }), + } +}) + +CONFIG.IOTA_HOME_COMMUNITY_SEED = '034b0229a2ba4e98e1cc5e8767dca886279b484303ffa73546bd5f5bf0b71285' +const homeCommunityUuid = v4() +const foreignCommunityUuid = v4() + +const keyPair = new KeyPair(new Mnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED)) +const foreignKeyPair = new KeyPair( + new Mnemonic('5d4e163c078cc6b51f5c88f8422bc8f21d1d59a284515ab1ea79e1c176ebec50'), +) + +let moderator: UserSet +let firstUser: UserSet +let secondUser: UserSet +let foreignUser: UserSet + +const now = new Date() + +describe('interactions/transmitToIota/TransmitToIotaContext', () => { + beforeAll(async () => { + await TestDB.instance.setupTestDB() + await communitySeed(homeCommunityUuid, false) + await communitySeed(foreignCommunityUuid, true, foreignKeyPair) + + moderator = createUserSet(homeCommunityUuid, keyPair) + firstUser = createUserSet(homeCommunityUuid, keyPair) + secondUser = createUserSet(homeCommunityUuid, keyPair) + foreignUser = createUserSet(foreignCommunityUuid, foreignKeyPair) + + await Account.save([ + moderator.account, + firstUser.account, + secondUser.account, + foreignUser.account, + ]) + }) + + afterAll(async () => { + await TestDB.instance.teardownTestDB() + }) + + it('LOCAL transaction', async () => { + const creationTransactionDraft = new TransactionDraft() + creationTransactionDraft.amount = new Decimal('2000') + creationTransactionDraft.backendTransactionId = 1 + creationTransactionDraft.createdAt = new Date().toISOString() + creationTransactionDraft.linkedUser = moderator.identifier + creationTransactionDraft.user = firstUser.identifier + creationTransactionDraft.type = InputTransactionType.CREATION + creationTransactionDraft.targetDate = new Date().toISOString() + const transactionRecipeContext = new CreateTransactionRecipeContext(creationTransactionDraft) + await transactionRecipeContext.run() + const transaction = transactionRecipeContext.getTransactionRecipe() + + const context = new TransmitToIotaContext(transaction) + const debugSpy = jest.spyOn(logger, 'debug') + await context.run() + expect( + transaction.iotaMessageId?.compare( + Buffer.from('5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710', 'hex'), + ), + ).toBe(0) + expect(debugSpy).toHaveBeenNthCalledWith( + 3, + expect.stringContaining('transmit LOCAL transaction to iota'), + expect.objectContaining({}), + ) + }) + + it('OUTBOUND transaction', async () => { + const crossGroupSendTransactionDraft = new TransactionDraft() + crossGroupSendTransactionDraft.amount = new Decimal('100') + crossGroupSendTransactionDraft.backendTransactionId = 4 + crossGroupSendTransactionDraft.createdAt = now.toISOString() + crossGroupSendTransactionDraft.linkedUser = foreignUser.identifier + crossGroupSendTransactionDraft.user = firstUser.identifier + crossGroupSendTransactionDraft.type = InputTransactionType.SEND + const transactionRecipeContext = new CreateTransactionRecipeContext( + crossGroupSendTransactionDraft, + ) + await transactionRecipeContext.run() + const transaction = transactionRecipeContext.getTransactionRecipe() + await transaction.save() + const body = TransactionBody.fromBodyBytes(transaction.bodyBytes) + expect(body.type).toBe(CrossGroupType.OUTBOUND) + const context = new TransmitToIotaContext(transaction) + const debugSpy = jest.spyOn(logger, 'debug') + await context.run() + expect( + transaction.iotaMessageId?.compare( + Buffer.from('5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710', 'hex'), + ), + ).toBe(0) + expect(debugSpy).toHaveBeenNthCalledWith( + 5, + expect.stringContaining('transmit OUTBOUND transaction to iota'), + expect.objectContaining({}), + ) + }) + + it('INBOUND transaction', async () => { + const crossGroupRecvTransactionDraft = new TransactionDraft() + crossGroupRecvTransactionDraft.amount = new Decimal('100') + crossGroupRecvTransactionDraft.backendTransactionId = 5 + crossGroupRecvTransactionDraft.createdAt = now.toISOString() + crossGroupRecvTransactionDraft.linkedUser = firstUser.identifier + crossGroupRecvTransactionDraft.user = foreignUser.identifier + crossGroupRecvTransactionDraft.type = InputTransactionType.RECEIVE + const transactionRecipeContext = new CreateTransactionRecipeContext( + crossGroupRecvTransactionDraft, + ) + await transactionRecipeContext.run() + const transaction = transactionRecipeContext.getTransactionRecipe() + await transaction.save() + // console.log(new TransactionLoggingView(transaction)) + const body = TransactionBody.fromBodyBytes(transaction.bodyBytes) + expect(body.type).toBe(CrossGroupType.INBOUND) + + const context = new TransmitToIotaContext(transaction) + const debugSpy = jest.spyOn(logger, 'debug') + await context.run() + expect( + transaction.iotaMessageId?.compare( + Buffer.from('5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710', 'hex'), + ), + ).toBe(0) + expect(debugSpy).toHaveBeenNthCalledWith( + 7, + expect.stringContaining('transmit INBOUND transaction to iota'), + expect.objectContaining({}), + ) + }) +}) diff --git a/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.ts b/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.ts index 8e0b32c6c..f29d1742a 100644 --- a/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.ts +++ b/dlt-connector/src/interactions/transmitToIota/TransmitToIota.context.ts @@ -2,7 +2,10 @@ import { Transaction } from '@entity/Transaction' import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType' import { TransactionBody } from '@/data/proto/3_3/TransactionBody' +import { logger } from '@/logging/logger' +import { TransactionLoggingView } from '@/logging/TransactionLogging.view' import { LogError } from '@/server/LogError' +import { getDataSource } from '@/typeorm/DataSource' import { AbstractTransactionRecipeRole } from './AbstractTransactionRecipe.role' import { InboundTransactionRecipeRole } from './InboundTransactionRecipe.role' @@ -36,7 +39,19 @@ export class TransmitToIotaContext { public async run(): Promise { const transaction = await this.transactionRecipeRole.transmitToIota() + logger.debug('transaction sended via iota', new TransactionLoggingView(transaction)) // store changes in db - await transaction.save() + // prevent endless loop + const paringTransaction = transaction.paringTransaction + if (paringTransaction) { + transaction.paringTransaction = undefined + await getDataSource().transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.save(transaction) + await transactionalEntityManager.save(paringTransaction) + }) + } else { + await transaction.save() + } + logger.info('sended transaction successfully updated in db') } } diff --git a/dlt-connector/src/logging/TransactionLogging.view.ts b/dlt-connector/src/logging/TransactionLogging.view.ts index 38443024d..d04d46556 100644 --- a/dlt-connector/src/logging/TransactionLogging.view.ts +++ b/dlt-connector/src/logging/TransactionLogging.view.ts @@ -18,7 +18,7 @@ export class TransactionLoggingView extends AbstractLoggingView { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - public toJSON(showBackendTransactions = true): any { + public toJSON(showBackendTransactions = true, deep = 1): any { return { id: this.self.id, nr: this.self.nr, @@ -31,16 +31,23 @@ export class TransactionLoggingView extends AbstractLoggingView { community: new CommunityLoggingView(this.self.community).toJSON(), otherCommunity: this.self.otherCommunity ? new CommunityLoggingView(this.self.otherCommunity) - : undefined, + : { id: this.self.otherCommunityId }, iotaMessageId: this.self.iotaMessageId ? this.self.iotaMessageId.toString(this.bufferStringFormat) : undefined, signingAccount: this.self.signingAccount ? new AccountLoggingView(this.self.signingAccount) - : undefined, + : { id: this.self.signingAccountId }, recipientAccount: this.self.recipientAccount ? new AccountLoggingView(this.self.recipientAccount) - : undefined, + : { id: this.self.recipientAccountId }, + pairingTransaction: + this.self.paringTransaction && deep === 1 + ? new TransactionLoggingView(this.self.paringTransaction).toJSON( + showBackendTransactions, + deep + 1, + ) + : { id: this.self.paringTransactionId }, amount: this.decimalToString(this.self.amount), accountBalanceOnCreation: this.decimalToString(this.self.accountBalanceOnCreation), accountBalanceOnConfirmation: this.decimalToString(this.self.accountBalanceOnConfirmation), diff --git a/dlt-connector/test/seeding/Community.seed.ts b/dlt-connector/test/seeding/Community.seed.ts new file mode 100644 index 000000000..a1b042ef2 --- /dev/null +++ b/dlt-connector/test/seeding/Community.seed.ts @@ -0,0 +1,28 @@ +import { Community } from '@entity/Community' + +import { KeyPair } from '@/data/KeyPair' +import { CommunityDraft } from '@/graphql/input/CommunityDraft' +import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context' +import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' + +export const communitySeed = async ( + uuid: string, + foreign: boolean, + keyPair: KeyPair | undefined = undefined, +): Promise => { + const homeCommunityDraft = new CommunityDraft() + homeCommunityDraft.uuid = uuid + homeCommunityDraft.foreign = foreign + homeCommunityDraft.createdAt = new Date().toISOString() + const iotaTopic = iotaTopicFromCommunityUUID(uuid) + const addCommunityContext = new AddCommunityContext(homeCommunityDraft, iotaTopic) + await addCommunityContext.run() + + const community = await Community.findOneOrFail({ where: { iotaTopic } }) + if (foreign && keyPair) { + // that isn't entirely correct, normally only the public key from foreign community is know, and will be come form blockchain + keyPair.fillInCommunityKeys(community) + await community.save() + } + return community +} diff --git a/dlt-connector/test/seeding/UserSet.seed.ts b/dlt-connector/test/seeding/UserSet.seed.ts new file mode 100644 index 000000000..933b386ca --- /dev/null +++ b/dlt-connector/test/seeding/UserSet.seed.ts @@ -0,0 +1,55 @@ +import { Account } from '@entity/Account' +import { User } from '@entity/User' +import { v4 } from 'uuid' + +import { AccountFactory } from '@/data/Account.factory' +import { KeyPair } from '@/data/KeyPair' +import { UserFactory } from '@/data/User.factory' +import { UserLogic } from '@/data/User.logic' +import { AccountType } from '@/graphql/enum/AccountType' +import { UserAccountDraft } from '@/graphql/input/UserAccountDraft' +import { UserIdentifier } from '@/graphql/input/UserIdentifier' + +export type UserSet = { + identifier: UserIdentifier + user: User + account: Account +} + +export const createUserIdentifier = (userUuid: string, communityUuid: string): UserIdentifier => { + const user = new UserIdentifier() + user.uuid = userUuid + user.communityUuid = communityUuid + return user +} + +export const createUserAndAccount = ( + userIdentifier: UserIdentifier, + communityKeyPair: KeyPair, +): Account => { + const accountDraft = new UserAccountDraft() + accountDraft.user = userIdentifier + accountDraft.createdAt = new Date().toISOString() + accountDraft.accountType = AccountType.COMMUNITY_HUMAN + const user = UserFactory.create(accountDraft, communityKeyPair) + const userLogic = new UserLogic(user) + const account = AccountFactory.createAccountFromUserAccountDraft( + accountDraft, + userLogic.calculateKeyPair(communityKeyPair), + ) + account.user = user + return account +} + +export const createUserSet = (communityUuid: string, communityKeyPair: KeyPair): UserSet => { + const identifier = createUserIdentifier(v4(), communityUuid) + const account = createUserAndAccount(identifier, communityKeyPair) + if (!account.user) { + throw Error('user missing') + } + return { + identifier, + account, + user: account.user, + } +}