diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts index d18a30a7c..08544834f 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -4,11 +4,12 @@ import { createTestClient } from 'apollo-server-testing' import createServer from '@/server/createServer' import { Community as DbCommunity } from '@entity/Community' import CONFIG from '@/config' +import { Connection } from '@dbTools/typeorm' let query: any // to do: We need a setup for the tests that closes the connection -let con: any +let con: Connection CONFIG.FEDERATION_API = '1_0' diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 7b580b240..ce01a0f85 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -9,6 +9,11 @@ import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { Connection } from '@dbTools/typeorm' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import Decimal from 'decimal.js-light' +import { calculateRecipientBalance } from '../util/calculateRecipientBalance' +import { PendingTransactionState } from '../enum/PendingTransactionState' +import { TransactionTypeId } from '../enum/TransactionTypeId' let mutate: ApolloServerTestClient['mutate'], con: Connection // let query: ApolloServerTestClient['query'] @@ -89,6 +94,29 @@ describe('SendCoinsResolver', () => { } ` + const settleSendCoinsMutation = ` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + settleSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` describe('voteForSendCoins', () => { let homeCom: DbCommunity let foreignCom: DbCommunity @@ -353,4 +381,146 @@ describe('SendCoinsResolver', () => { }) }) }) + + describe('settleSendCoins', () => { + let homeCom: DbCommunity + let foreignCom: DbCommunity + let sendUser: DbUser + let recipUser: DbUser + let pendingTx: DbPendingTransaction + const creationDate = new Date() + + beforeEach(async () => { + await cleanDB() + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCom-url' + homeCom.name = 'homeCom-Name' + homeCom.description = 'homeCom-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCom-publicKey') + homeCom.communityUuid = 'homeCom-UUID' + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCom-url' + foreignCom.name = 'foreignCom-Name' + foreignCom.description = 'foreignCom-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCom-publicKey') + foreignCom.communityUuid = 'foreignCom-UUID' + await DbCommunity.insert(foreignCom) + + sendUser = DbUser.create() + sendUser.alias = 'sendUser-alias' + sendUser.firstName = 'sendUser-FirstName' + sendUser.gradidoID = 'sendUser-GradidoID' + sendUser.lastName = 'sendUser-LastName' + await DbUser.insert(sendUser) + + recipUser = DbUser.create() + recipUser.alias = 'recipUser-alias' + recipUser.firstName = 'recipUser-FirstName' + recipUser.gradidoID = 'recipUser-GradidoID' + recipUser.lastName = 'recipUser-LastName' + await DbUser.insert(recipUser) + + pendingTx = DbPendingTransaction.create() + pendingTx.amount = new Decimal(100) + pendingTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + pendingTx.linkedUserId = sendUser.id + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + pendingTx.linkedUserGradidoID = sendUser.gradidoID + pendingTx.state = PendingTransactionState.NEW + pendingTx.typeId = TransactionTypeId.RECEIVE + pendingTx.memo = 'X-Com-TX memo' + pendingTx.userId = recipUser.id + pendingTx.userCommunityUuid = homeCom.communityUuid + pendingTx.userGradidoID = recipUser.gradidoID + await DbPendingTransaction.insert(pendingTx) + }) + + describe('unknown recipient community', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: 'invalid foreignCom', + userReceiverIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('settleSendCoins with wrong communityReceiverIdentifier')], + }), + ) + }) + }) + + describe('unknown recipient user', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: homeCom.communityUuid, + userReceiverIdentifier: 'invalid recipient', + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError( + 'settleSendCoins with unknown userReceiverIdentifier in the community=', + ), + ], + }), + ) + }) + }) + + describe('valid X-Com-TX settled', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: homeCom.communityUuid, + userReceiverIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + data: { + settleSendCoins: true, + }, + }), + ) + }) + }) + }) }) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index c83c074b4..39c6f1e95 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -12,7 +12,7 @@ import { calculateRecipientBalance } from '../util/calculateRecipientBalance' import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' -import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' +// import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' @Resolver() @@ -176,50 +176,70 @@ export class SendCoinsResolver { userSenderName, }: SendCoinsArgs, ): Promise { - logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) - try { - const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, - state: PendingTransactionState.NEW, - typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + logger.debug( + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${communityReceiverIdentifier}, userGradidoID=${userReceiverIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${communitySenderIdentifier}, userSenderIdentifier=${userSenderIdentifier}, userSenderName=${userSenderName}`, + ) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `settleSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) + if (!receiverUser) { + throw new LogError( + `settleSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + // try { + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.NEW, + typeId: TransactionTypeId.RECEIVE, + balanceDate: new Date(creationDate), + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) + if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, }) - logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { - logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) - return true - } else { - logger.debug( - 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', - ) - throw new LogError( - `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, - PendingTransactionState.NEW, - TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, - ) - } + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) + return true + } else { + logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') + throw new LogError( + `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.NEW, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } +/* } catch (err) { throw new LogError(`Error in settlePendingReceiveTransaction: `, err) } +*/ } @Mutation(() => Boolean) @@ -236,64 +256,66 @@ export class SendCoinsResolver { userSenderName, }: SendCoinsArgs, ): Promise { - try { - logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) - // first check if receiver community is correct - const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, - }) - if (!homeCom) { - throw new LogError( - `revertSettledSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, - ) + // try { + logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `revertSettledSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) + if (!receiverUser) { + throw new LogError( + `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.SETTLED, + typeId: TransactionTypeId.RECEIVE, + balanceDate: new Date(creationDate), + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') + try { + await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: revertSettledSendCoins pendingTX successfully') + } catch (err) { + throw new LogError('Error in revertSettledSendCoins of receiver: ', err) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { - throw new LogError( - `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, - homeCom.name, - ) - } - const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, - state: PendingTransactionState.SETTLED, - typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, - }) - logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { - logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') - try { - await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug('XCom: revertSettledSendCoins pendingTX successfully') - } catch (err) { - throw new LogError('Error in revertSettledSendCoins of receiver: ', err) - } - } else { - logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') - throw new LogError( - `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, - PendingTransactionState.SETTLED, - TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, - ) - } - logger.debug(`revertSendCoins()-1_0... successfull`) - return true + } else { + logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') + throw new LogError( + `Can't find in revertSettledSendCoins the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.SETTLED, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } + logger.debug(`revertSendCoins()-1_0... successfull`) + return true +/* } catch (err) { throw new LogError(`Error in revertSendCoins: `, err) } +*/ } } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 0823e7d41..586c57998 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -15,7 +15,7 @@ import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' -import { calculateRecepientBalance } from './calculateRecepientBalance' +import { calculateRecipientBalance } from './calculateRecipientBalance' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -52,7 +52,7 @@ export async function settlePendingReceiveTransaction( const lastTransaction = await getLastTransaction(receiverUser.id) - if (lastTransaction?.id !== pendingTx.previous) { + if (lastTransaction === undefined && lastTransaction.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) @@ -69,7 +69,7 @@ export async function settlePendingReceiveTransaction( transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID transactionReceive.linkedUserName = pendingTx.linkedUserName transactionReceive.amount = pendingTx.amount - const receiveBalance = await calculateRecepientBalance( + const receiveBalance = await calculateRecipientBalance( receiverUser.id, pendingTx.amount, pendingTx.balanceDate, diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts index 3c118ec43..3b05edf4d 100644 --- a/federation/test/helpers.ts +++ b/federation/test/helpers.ts @@ -4,45 +4,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-return */ +import { entities } from '@entity/index' +import { createTestClient } from 'apollo-server-testing' -/* <<<<<<< HEAD import { createServer } from '@/server/createServer' -import { entities } from '@entity/index' -import { createTestClient } from 'apollo-server-testing' -import { logger } from './testSetup' - -// import { createServer } from '@/server/createServer' - -// import { i18n, logger } from './testSetup' - -export const headerPushMock = jest.fn((t) => { - context.token = t.value -}) - -const context = { - token: '', - setHeaders: { - push: headerPushMock, - forEach: jest.fn(), - }, - clientTimezoneOffset: 0, -} - -export const cleanDB = async () => { - // this only works as long we do not have foreign key constraints - for (const entity of entities) { - await resetEntity(entity) - } -} - -export const testEnvironment = async (testLogger = logger) => { - // , testI18n = i18n) => { -======= -*/ -import { entities } from '@entity/index' -import { createTestClient } from 'apollo-server-testing' - -import createServer from '@/server/createServer' import { logger } from './testSetup' @@ -67,7 +32,6 @@ export const cleanDB = async () => { } export const testEnvironment = async (testLogger = logger) => { - // >>>>>>> refs/remotes/origin/master const server = await createServer(testLogger) // context, testLogger, testI18n) const con = server.con const testClient = createTestClient(server.apollo)