From ba13144ad3ebad781dc4999372e4c3beaf908bb5 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Fri, 22 Dec 2023 15:19:21 +0100 Subject: [PATCH] add backendTransaction table, update dltConnectorClient code and test --- backend/log.txt | 2533 +++++++++++++++++ backend/src/apis/DltConnectorClient.test.ts | 36 +- backend/src/apis/DltConnectorClient.ts | 20 +- .../sendTransactionsToDltConnector.test.ts | 43 +- .../util/sendTransactionsToDltConnector.ts | 21 +- .../src/data/BackendTransaction.factory.ts | 13 + .../src/data/BackendTransaction.repository.ts | 7 + dlt-connector/src/data/Transaction.builder.ts | 11 +- .../src/data/Transaction.repository.ts | 21 +- .../graphql/resolver/TransactionsResolver.ts | 34 +- .../transaction/TransactionRecipe.role.ts | 4 +- .../BackendTransaction.ts | 45 + .../Transaction.ts | 11 +- dlt-database/entity/BackendTransaction.ts | 1 + dlt-database/entity/index.ts | 2 + .../0003-refactor_transaction_recipe.ts | 18 +- 16 files changed, 2717 insertions(+), 103 deletions(-) create mode 100644 backend/log.txt create mode 100644 dlt-connector/src/data/BackendTransaction.factory.ts create mode 100644 dlt-connector/src/data/BackendTransaction.repository.ts create mode 100644 dlt-database/entity/0003-refactor_transaction_recipe/BackendTransaction.ts create mode 100644 dlt-database/entity/BackendTransaction.ts diff --git a/backend/log.txt b/backend/log.txt new file mode 100644 index 000000000..b496d4791 --- /dev/null +++ b/backend/log.txt @@ -0,0 +1,2533 @@ + FAIL  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/ContributionResolver.test.tssrc/graphql/resolver/ContributionResolver.test.ts]8;; (9.447 s) + ContributionResolver + createContribution + unauthenticated + ✓ returns an error (8 ms) + authenticated with valid user + input not valid + ✓ throws error when memo length smaller than 5 chars (7 ms) + ✓ throws error when memo length greater than 255 chars (5 ms) + ✓ throws error when creationDate not-valid (4 ms) + ✓ throws error when creationDate 3 month behind (13 ms) + ✓ logs the error "No information for available creations for the given date" again (1 ms) + valid input + ✓ creates contribution + ✓ stores the CONTRIBUTION_CREATE event in the database (4 ms) + updateContribution + unauthenticated + ✓ returns an error (2 ms) + authenticated + Memo length smaller than 5 chars + ✓ throws error (5 ms) + Memo length greater than 255 chars + ✓ throws error (6 ms) + wrong contribution id + ✓ throws an error (8 ms) + ✓ logs the error "Contribution not found" + wrong user tries to update the contribution + ✓ throws an error (10 ms) + ✓ logs the error "Can not update contribution of another user" (1 ms) + admin tries to update a user contribution + ✕ throws an error (24 ms) + ✕ logs the error "An admin is not allowed to update an user contribution" (1 ms) + contribution has wrong status + ✓ throws an error (9 ms) + ✓ logs the error "Contribution can not be updated due to status" (1 ms) + update too much so that the limit is exceeded + ✓ throws an error (10 ms) + ✓ logs the error "The amount to be created exceeds the amount still available for this month" (1 ms) + update creation to a date that is older than 3 months + ✓ throws an error (6 ms) + ✓ logs the error "Month of contribution can not be changed" (1 ms) + valid input + ✓ updates contribution (19 ms) + ✓ stores the CONTRIBUTION_UPDATE event in the database (97 ms) + denyContribution + unauthenticated + ✓ returns an error (2 ms) + authenticated without admin rights + ✓ returns an error (6 ms) + authenticated with admin rights + wrong contribution id + ✓ throws an error (6 ms) + ✓ logs the error "Contribution not found" (1 ms) + deny contribution that is already confirmed + ✓ throws an error (218 ms) + ✓ logs the error "Contribution not found" + deny contribution that is already deleted + ✓ throws an error (216 ms) + ✓ logs the error "Contribution not found" (1 ms) + deny contribution that is already denied + ✓ throws an error (211 ms) + ✓ logs the error "Contribution not found" (1 ms) + valid input + ✓ deny contribution (103 ms) + ✓ stores the ADMIN_CONTRIBUTION_DENY event in the database (2 ms) + ✓ calls sendContributionDeniedEmail + deleteContribution + unauthenticated + ✓ returns an error (1 ms) + authenticated + wrong contribution id + ✓ returns an error (7 ms) + ✓ logs the error "Contribution not found" (1 ms) + other user sends a deleteContribution + ✓ returns an error (5 ms) + ✓ logs the error "Can not delete contribution of another user" (1 ms) + User deletes own contribution + ✓ deletes successfully (20 ms) + ✓ stores the CONTRIBUTION_DELETE event in the database (2 ms) + User deletes already confirmed contribution + ✓ throws an error (199 ms) + ✓ logs the error "A confirmed contribution can not be deleted" (1 ms) + listContributions + unauthenticated + ✓ returns an error (2 ms) + authenticated + no status filter + ✕ returns creations (15 ms) + with status filter [PENDING, IN_PROGRESS, DENIED, DELETED] + ✕ returns only unconfirmed creations (9 ms) + listAllContribution + unauthenticated + ✓ returns an error (1 ms) + authenticated + ✓ throws an error with "NOT_VALID" in statusFilter (6 ms) + ✓ throws an error with a null in statusFilter (2 ms) + ✓ throws an error with null and "NOT_VALID" in statusFilter (2 ms) + ✓ returns all contributions without statusFilter (8 ms) + ✓ returns all contributions for statusFilter = null (8 ms) + ✓ returns all contributions for statusFilter = [] (7 ms) + ✓ returns all CONFIRMED contributions (6 ms) + ✓ returns all PENDING contributions (6 ms) + ✓ returns all IN_PROGRESS Creation (6 ms) + ✓ returns all DENIED Creation (6 ms) + ✓ does not return any DELETED Creation (6 ms) + ✓ returns all CONFIRMED and PENDING Creation (7 ms) + contributions + unauthenticated + adminCreateContribution + ✓ returns an error (2 ms) + adminUpdateContribution + ✓ returns an error (1 ms) + adminDeleteContribution + ✓ returns an error (1 ms) + confirmContribution + ✓ returns an error (1 ms) + authenticated + without admin rights + adminCreateContribution + ✓ returns an error (7 ms) + adminUpdateContribution + ✓ returns an error (4 ms) + adminDeleteContribution + ✓ returns an error (3 ms) + confirmContribution + ✓ returns an error (3 ms) + with admin rights + adminCreateContribution + user to create for does not exist + ✓ throws an error (7 ms) + ✓ logs the error "Could not find user" (1 ms) + user to create for is deleted + ✓ throws an error (6 ms) + ✓ logs the error "Cannot create contribution since the user was deleted" (1 ms) + user to create for has email not confirmed + ✓ throws an error (7 ms) + ✓ logs the error "Cannot create contribution since the users email is not activated" (1 ms) + valid user to create for + date of creation is not a date string + ✓ throws an error (4 ms) + date of creation is four months ago + ✓ throws an error (7 ms) + ✓ logs the error "No information for available creations for the given date" + date of creation is in the future + ✓ throws an error (7 ms) + ✓ logs the error "No information for available creations for the given date" + amount of creation is too high + ✓ throws an error (7 ms) + ✓ logs the error "The amount to be created exceeds the amount still available for this month" (1 ms) + creation is valid + ✓ returns an array of the open creations for the last three months (18 ms) + ✓ stores the ADMIN_CONTRIBUTION_CREATE event in the database (5 ms) + user tries to update admin contribution + ✓ logs and throws "Cannot update contribution of moderator" error (8 ms) + second creation surpasses the available amount + ✓ returns an array of the open creations for the last three months (9 ms) + ✓ logs the error "The amount to be created exceeds the amount still available for this month" (1 ms) + adminUpdateContribution + creation does not exist + ✓ throws an error (5 ms) + ✓ logs the error "Contribution not found" + creation update is not valid + ✓ throws an error (8 ms) + ✓ logs the error "The amount to be created exceeds the amount still available for this month" (1 ms) + creation update is successful changing month + ○ skipped returns update creation object + ○ skipped stores the ADMIN_CONTRIBUTION_UPDATE event in the database + creation update is successful without changing month + ✓ returns update creation object (16 ms) + ✓ stores the ADMIN_CONTRIBUTION_UPDATE event in the database (3 ms) + adminDeleteContribution + creation id does not exist + ✓ throws an error (8 ms) + ✓ logs the error "Contribution not found" + admin deletes own user contribution + ✓ throws an error (5 ms) + creation id does exist + ✓ returns true (25 ms) + ✓ stores the ADMIN_CONTRIBUTION_DELETE event in the database (4 ms) + ✓ calls sendContributionDeletedEmail + creation already confirmed + ✓ throws an error (350 ms) + confirmContribution + creation does not exits + ✓ throws an error (5 ms) + ✓ logs the error "Contribution not found" (1 ms) + confirm own creation + ✓ thows an error (5 ms) + ✓ logs the error "Moderator can not confirm own contribution" (1 ms) + confirm creation for other user + ✓ returns true (21 ms) + ✓ stores the ADMIN_CONTRIBUTION_CONFIRM event in the database (4 ms) + ✓ creates a transaction (3 ms) + ✓ calls sendContributionConfirmedEmail + ✓ stores the EMAIL_CONFIRMATION event in the database (4 ms) + ✓ logs the error "Contribution already confirmed" (1 ms) + confirm same contribution again + ✓ throws an error (6 ms) + confirm two creations one after the other quickly + ✓ throws no error for the second confirmation (43 ms) + adminListContributions + unauthenticated + ✓ returns an error (2 ms) + authenticated as user + ✓ returns an error (4 ms) + authenticated as admin + ✓ throw error for invalid ContributionStatus in statusFilter array (1 ms) + ✕ returns 18 creations in total (20 ms) + ✓ returns two pending creations with page size set to 2 (9 ms) + with user query + ✕ returns only contributions of the queried user (10 ms) + ✕ returns only contributions of the queried user without hashtags (10 ms) + ✓ returns only contributions with #firefighter (7 ms) + ✓ returns no contributions with #firefighter and no hashtag (6 ms) + ✓ returns only contributions of the queried user email (8 ms) + + ● ContributionResolver › updateContribution › authenticated › admin tries to update a user contribution › throws an error + + expect(received).toEqual(expected) // deep equality + + Expected: [[GraphQLError: An admin is not allowed to update an user contribution]] + Received: undefined + +   509 | }, +   510 | }) + > 511 | expect(errorObjects).toEqual([ +   | ^ +   512 | new GraphQLError('An admin is not allowed to update an user contribution'), +   513 | ]) +   514 | }) + + at src/graphql/resolver/ContributionResolver.test.ts:511:32 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › updateContribution › authenticated › admin tries to update a user contribution › logs the error "An admin is not allowed to update an user contribution" + + expect(jest.fn()).toBeCalledWith(...expected) + + Expected: "An admin is not allowed to update an user contribution" + + Number of calls: 0 + +   515 | +   516 | it('logs the error "An admin is not allowed to update an user contribution"', () => { + > 517 | expect(logger.error).toBeCalledWith( +   | ^ +   518 | 'An admin is not allowed to update an user contribution', +   519 | ) +   520 | }) + + at Object. (src/graphql/resolver/ContributionResolver.test.ts:517:32) + + ● ContributionResolver › listContributions › authenticated › no status filter › returns creations + + expect(received).toMatchObject(expected) + + - Expected - 16 + + Received + 64 + +  Object { +  "contributionCount": 6, + - "contributionList": ArrayContaining [ + - ObjectContaining { + + "contributionList": Array [ + + Object { +  "amount": "100", + - "id": 25, + - "memo": "Test contribution to confirm", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, + + "id": 27, + + "memo": "Test contribution to delete", +  "messagesCount": 0, + - }, + - ObjectContaining { + - "amount": "10", + - "id": 23, + - "memo": "Test PENDING contribution update", + - "messagesCount": 1, + + "status": "DELETED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "id": 26, +  "memo": "Test contribution to deny", +  "messagesCount": 0, + + "status": "DENIED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + - "id": 27, + - "memo": "Test contribution to delete", + + "confirmedAt": "2023-11-06T08:36:49.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, + + "id": 25, + + "memo": "Test contribution to confirm", +  "messagesCount": 0, + + "status": "CONFIRMED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, +  "id": 24, +  "memo": "Test IN_PROGRESS contribution", +  "messagesCount": 1, + + "status": "IN_PROGRESS", +  }, + - ObjectContaining { + + Object { + + "amount": "10", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:47.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, + + "id": 23, + + "memo": "Test PENDING contribution update", + + "messagesCount": 2, + + "status": "PENDING", + + }, + + Object { +  "amount": "1000", + + "confirmedAt": "2023-11-06T08:36:46.000Z", + + "confirmedBy": 283, + + "contributionDate": "2022-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, +  "id": 22, +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, + + "status": "CONFIRMED", +  }, +  ], +  } + +   1103 | }, +   1104 | }) + > 1105 | expect(contributionListResult).toMatchObject({ +   | ^ +   1106 | contributionCount: 6, +   1107 | contributionList: expect.arrayContaining([ +   1108 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:1105:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › listContributions › authenticated › with status filter [PENDING, IN_PROGRESS, DENIED, DELETED] › returns only unconfirmed creations + + expect(received).toMatchObject(expected) + + - Expected - 19 + + Received + 44 + +  Object { +  "contributionCount": 4, + - "contributionList": ArrayContaining [ + - ObjectNotContaining { + - "status": "CONFIRMED", + + "contributionList": Array [ + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, + + "id": 27, + + "memo": "Test contribution to delete", + + "messagesCount": 0, + + "status": "DELETED", +  }, + - ObjectContaining { + - "amount": "10", + - "id": 23, + - "memo": "Test PENDING contribution update", + - "messagesCount": 1, + - "status": "PENDING", + - }, + - ObjectContaining { + + Object { +  "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "id": 26, +  "memo": "Test contribution to deny", +  "messagesCount": 0, +  "status": "DENIED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + - "id": 27, + - "memo": "Test contribution to delete", + - "messagesCount": 0, + - "status": "DELETED", + - }, + - ObjectContaining { + - "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, +  "id": 24, +  "memo": "Test IN_PROGRESS contribution", +  "messagesCount": 1, +  "status": "IN_PROGRESS", + + }, + + Object { + + "amount": "10", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:47.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, + + "id": 23, + + "memo": "Test PENDING contribution update", + + "messagesCount": 2, + + "status": "PENDING", +  }, +  ], +  } + +   1161 | }, +   1162 | }) + > 1163 | expect(contributionListResult).toMatchObject({ +   | ^ +   1164 | contributionCount: 4, +   1165 | contributionList: expect.arrayContaining([ +   1166 | expect.not.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:1163:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › adminListContributions › authenticated as admin › returns 18 creations in total + + expect(received).toMatchObject(expected) + + - Expected - 57 + + Received + 165 + + @@ -1,165 +1,273 @@ +  Object { +  "contributionCount": 18, + - "contributionList": ArrayContaining [ + - ObjectContaining { + - "amount": decimalEqual<100>, + + "contributionList": Array [ + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:51.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 39, +  "lastName": "Lustig", +  "memo": "#firefighters", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<50>, + + Object { + + "amount": "50", + + "confirmedAt": "2023-11-06T08:36:51.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-09-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 38, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido liebe Bibi!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<50>, + + Object { + + "amount": "50", + + "confirmedAt": "2023-11-06T08:36:51.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-09-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 37, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido liebe Bibi!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<450>, + + Object { + + "amount": "450", + + "confirmedAt": "2023-11-06T08:36:51.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-09-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 36, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido liebe Bibi!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<400>, + + Object { + + "amount": "400", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 35, +  "lastName": "Lustig", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": "2023-11-06T08:36:50.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bob", + - "id": Any, + + "id": 34, +  "lastName": "der Baumeister", +  "memo": "Confirmed Contribution", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 33, +  "lastName": "Lustig", +  "memo": "Test env contribution", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 32, +  "lastName": "Bloxberg", +  "memo": "Aktives Grundeinkommen", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 31, +  "lastName": "Lustig", +  "memo": "Das war leider zu Viel!", + - "messagesCount": 0, + + "messagesCount": 1, +  "status": "DELETED", +  }, + - ObjectContaining { + - "amount": decimalEqual<166>, + + Object { + + "amount": "166", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:48.000Z", + + "createdAt": "2023-11-06T08:36:48.000Z", + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "firstName": "Räuber", + - "id": Any, + + "id": 30, +  "lastName": "Hotzenplotz", +  "memo": "Whatever contribution", +  "messagesCount": 0, +  "status": "DENIED", +  }, + - ObjectContaining { + - "amount": decimalEqual<166>, + + Object { + + "amount": "166", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:48.000Z", + + "createdAt": "2023-11-06T08:36:48.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Räuber", + - "id": Any, + + "id": 29, +  "lastName": "Hotzenplotz", +  "memo": "Whatever contribution", +  "messagesCount": 0, +  "status": "DELETED", +  }, + - ObjectContaining { + - "amount": decimalEqual<166>, + + Object { + + "amount": "166", + + "confirmedAt": "2023-11-06T08:36:48.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T08:36:48.000Z", + + "createdAt": "2023-11-06T08:36:48.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Räuber", + - "id": Any, + + "id": 28, +  "lastName": "Hotzenplotz", +  "memo": "Whatever contribution", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 27, +  "lastName": "Bloxberg", +  "memo": "Test contribution to delete", +  "messagesCount": 0, +  "status": "DELETED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "firstName": "Bibi", + - "id": Any, + + "id": 26, +  "lastName": "Bloxberg", +  "memo": "Test contribution to deny", +  "messagesCount": 0, +  "status": "DENIED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": "2023-11-06T08:36:49.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 25, +  "lastName": "Bloxberg", +  "memo": "Test contribution to confirm", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 24, +  "lastName": "Bloxberg", +  "memo": "Test IN_PROGRESS contribution", +  "messagesCount": 1, +  "status": "IN_PROGRESS", +  }, + - ObjectContaining { + - "amount": decimalEqual<10>, + + Object { + + "amount": "10", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:47.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 23, +  "lastName": "Bloxberg", +  "memo": "Test PENDING contribution update", + - "messagesCount": 2, + + "messagesCount": 3, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<1000>, + + Object { + + "amount": "1000", + + "confirmedAt": "2023-11-06T08:36:46.000Z", + + "confirmedBy": 283, + + "contributionDate": "2022-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 22, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + +   2827 | // console.log('17 contributions: %s', JSON.stringify(contributionListObject, null, 2)) +   2828 | expect(contributionListObject.contributionList).toHaveLength(18) + > 2829 | expect(contributionListObject).toMatchObject({ +   | ^ +   2830 | contributionCount: 18, +   2831 | contributionList: expect.arrayContaining([ +   2832 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:2829:40 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › adminListContributions › authenticated as admin › with user query › returns only contributions of the queried user + + expect(received).toMatchObject(expected) + + - Expected - 14 + + Received + 38 + +  Object { +  "contributionCount": 4, + - "contributionList": ArrayContaining [ + - ObjectContaining { + - "amount": decimalEqual<100>, + + "contributionList": Array [ + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:51.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 39, +  "lastName": "Lustig", +  "memo": "#firefighters", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<400>, + + Object { + + "amount": "400", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 35, +  "lastName": "Lustig", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 33, +  "lastName": "Lustig", +  "memo": "Test env contribution", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 31, +  "lastName": "Lustig", +  "memo": "Das war leider zu Viel!", + - "messagesCount": 0, + + "messagesCount": 1, +  "status": "DELETED", +  }, +  ], +  } + +   3057 | }) +   3058 | expect(contributionListObject.contributionList).toHaveLength(4) + > 3059 | expect(contributionListObject).toMatchObject({ +   | ^ +   3060 | contributionCount: 4, +   3061 | contributionList: expect.arrayContaining([ +   3062 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:3059:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › adminListContributions › authenticated as admin › with user query › returns only contributions of the queried user without hashtags + + expect(received).toMatchObject(expected) + + - Expected - 11 + + Received + 29 + +  Object { +  "contributionCount": 3, + - "contributionList": ArrayContaining [ + - ObjectContaining { + - "amount": decimalEqual<400>, + + "contributionList": Array [ + + Object { + + "amount": "400", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 35, +  "lastName": "Lustig", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 33, +  "lastName": "Lustig", +  "memo": "Test env contribution", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 31, +  "lastName": "Lustig", +  "memo": "Das war leider zu Viel!", + - "messagesCount": 0, + + "messagesCount": 1, +  "status": "DELETED", +  }, +  ], +  } + +   3111 | }) +   3112 | expect(contributionListObject.contributionList).toHaveLength(3) + > 3113 | expect(contributionListObject).toMatchObject({ +   | ^ +   3114 | contributionCount: 3, +   3115 | contributionList: expect.arrayContaining([ +   3116 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:3113:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/UserResolver.test.tssrc/graphql/resolver/UserResolver.test.ts]8;; (12.687 s) + UserResolver + createUser + ✓ returns success (2 ms) + valid input data + ✓ stores the USER_REGISTER event in the database (5 ms) + filling all tables + ✓ saves the user in users table (1 ms) + ✓ creates an email contact + account activation email + ✓ sends an account activation email (1 ms) + ✓ stores the EMAIL_CONFIRMATION event in the database (2 ms) + user already exists + ✓ logs an info (1 ms) + ✓ sends an account multi registration email (1 ms) + ✓ results with partly faked user with random "id" + ✓ stores the EMAIL_ACCOUNT_MULTIREGISTRATION event in the database (7 ms) + unknown language + ✓ sets "de" as default language (63 ms) + no publisher id + ✓ sets publisher id to 0 (55 ms) + redeem codes + contribution link + ✓ sets the contribution link id (4 ms) + ✓ stores the USER_ACTIVATE_ACCOUNT event in the database (2 ms) + ✓ stores the USER_REGISTER_REDEEM event in the database (3 ms) + transaction link + ✓ sets the referrer id to bob baumeister id (3 ms) + ✓ stores the USER_REGISTER_REDEEM event in the database (2 ms) + setPassword + valid optin code and valid password + ✓ sets email checked to true (1 ms) + ✓ updates the password (76 ms) + ✓ calls the klicktipp API + ✓ returns true (1 ms) + no valid password + ✓ throws an error (21 ms) + ✓ logs the error thrown (1 ms) + no valid optin code + ✓ throws an error (2 ms) + ✓ logs the error found + login + no users in database + ✓ throws an error (6 ms) + ✓ logs the error found (1 ms) + user is in database and correct login data + ✓ returns the user object (1 ms) + ✓ sets the token in the header (1 ms) + ✓ stores the USER_LOGIN event in the database (4 ms) + user is in database and wrong password + ✓ returns an error (1 ms) + ✓ logs the error thrown (1 ms) + user is in database but deleted + ✓ returns an error (1 ms) + ✓ logs the error thrown + user is in database but email not confirmed + ✓ returns an error (1 ms) + ✓ logs the error thrown + user is in database but password is not set + ○ skipped returns an error + ○ skipped logs the error thrown + logout + unauthenticated + ✓ throws an error (4 ms) + authenticated + ✓ returns true (10 ms) + ✓ stores the USER_LOGOUT event in the database (6 ms) + verifyLogin + unauthenticated + ✓ throws an error (1 ms) + user exists but is not logged in + ✓ throws an error (1 ms) + authenticated + ✓ returns user object (5 ms) + ✓ stores the USER_LOGIN event in the database (4 ms) + forgotPassword + user is not in DB + duration not expired + ✓ returns true (3 ms) + user exists in DB + duration not expired + ✓ throws an error (7 ms) + duration reset to 0 + ✓ returns true (36 ms) + ✓ sends reset password email (1 ms) + ✓ stores the EMAIL_FORGOT_PASSWORD event in the database (5 ms) + request reset password again + ✓ thows an error (6 ms) + ✓ logs the error found + queryOptIn + wrong optin code + ✓ throws an error (8 ms) + correct optin code + ✓ returns true (2 ms) + updateUserInfos + unauthenticated + ✓ throws an error (2 ms) + authenticated + ✓ returns true (13 ms) + first-name, last-name and language + ✓ updates the fields in DB (14 ms) + ✓ stores the USER_INFO_UPDATE event in the database (7 ms) + alias + valid alias + ✓ updates the user in DB (17 ms) + language is not valid + ✓ throws an error (5 ms) + ✓ logs the error found + password + wrong old password + ✓ throws an error (81 ms) + ✓ logs the error found + invalid new password + ✓ throws an error (5 ms) + ✓ logs the error found (1 ms) + correct old and new password + ✓ returns true (166 ms) + ✓ can login with new password (89 ms) + ✓ cannot login with old password (82 ms) + ✓ logs the error thrown (1 ms) + searchAdminUsers + unauthenticated + ✓ throws an error (3 ms) + authenticated + ✓ finds peter@lustig.de (8 ms) + password encryption type + user just registered + ✓ has password type gradido id (79 ms) + user has encryption type email + ✓ changes to gradidoID on login (259 ms) + ✓ can login after password change (93 ms) + set user role + unauthenticated + ✓ returns an error (2 ms) + authenticated + with user rights + ✓ returns an error (4 ms) + with moderator rights + ✓ returns an error (4 ms) + with admin rights + ✓ returns user with new moderator-role (18 ms) + user to get a new role does not exist + ✓ throws an error (8 ms) + ✓ logs the error thrown (1 ms) + change role with success + user gets new role + to admin + ✓ returns admin-rolename (17 ms) + ✓ stores the ADMIN_USER_ROLE_SET event in the database (5 ms) + to moderator + ✓ returns date string (18 ms) + ✓ stores the ADMIN_USER_ROLE_SET event in the database (2 ms) + to usual user + ✓ returns null (16 ms) + change role with error + his own role + ✓ throws an error (7 ms) + ✓ logs the error thrown + to not allowed role + ✓ throws an error (4 ms) + user has already role to be set + to admin + ✓ throws an error (26 ms) + ✓ logs the error thrown (1 ms) + to moderator + ✓ throws an error (25 ms) + ✓ logs the error thrown (1 ms) + to usual user + ✓ throws an error (23 ms) + ✓ logs the error thrown + delete user + unauthenticated + ✓ returns an error (2 ms) + authenticated + without admin rights + ✓ returns an error (4 ms) + with admin rights + user to be deleted does not exist + ✓ throws an error (6 ms) + ✓ logs the error thrown + delete self + ✓ throws an error (7 ms) + ✓ logs the error thrown + delete with success + ✓ returns date string (19 ms) + ✓ stores the ADMIN_USER_DELETE event in the database (8 ms) + delete deleted user + ✓ throws an error (6 ms) + ✓ logs the error thrown + sendActivationEmail + unauthenticated + ✓ returns an error (2 ms) + authenticated + without admin rights + ✓ returns an error (3 ms) + with admin rights + user does not exist + ✓ throws an error (6 ms) + ✓ logs the error thrown + user is deleted + ✓ throws an error (155 ms) + ✓ logs the error thrown + sendActivationEmail with success + ✓ returns true (36 ms) + ✓ sends an account activation email (4 ms) + ✓ stores the EMAIL_ADMIN_CONFIRMATION event in the database (7 ms) + unDelete user + unauthenticated + ✓ returns an error (2 ms) + authenticated + without admin rights + ✓ returns an error (4 ms) + with admin rights + user to be undelete does not exist + ✓ throws an error (5 ms) + ✓ logs the error thrown + user to undelete is not deleted + ✓ throws an error (6 ms) + ✓ logs the error thrown + undelete deleted user + ✓ returns null (14 ms) + ✓ stores the ADMIN_USER_UNDELETE event in the database (8 ms) + search users + unauthenticated + ✓ returns an error (4 ms) + authenticated + without admin rights + ✓ returns an error (4 ms) + with admin rights + without any filters + ✓ finds all users (18 ms) + all filters are null + ✓ finds all users (14 ms) + filter by unchecked email + ✓ finds only users with unchecked email (10 ms) + filter by deleted users + ✓ finds only users with deleted account (9 ms) + filter by deleted account and unchecked email + ✓ finds no users (6 ms) + user + unauthenticated + ✓ throws and logs "401 Unauthorized" error (2 ms) + authenticated + identifier is no gradido ID, no email and no alias + ✓ throws and logs "Unknown identifier type" error (8 ms) + identifier is not found + ✓ throws and logs "No user found to given identifier" error (8 ms) + identifier is found via email, but not matching community + ✓ returns user (6 ms) + identifier is found via email + ✓ returns user (9 ms) + identifier is found via gradidoID + ✓ returns user (7 ms) + identifier is found via alias + ✓ returns user (7 ms) + check username + reserved alias + ✓ returns false (2 ms) + valid alias + ✓ returns true (3 ms) + printTimeDuration + ✓ works with 10 minutes (1 ms) + ✓ works with 1440 minutes (1 ms) + ✓ works with 1410 minutes + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/TransactionLinkResolver.test.tssrc/graphql/resolver/TransactionLinkResolver.test.ts]8;; (5.288 s) + TransactionLinkResolver + createTransactionLink + unauthenticated + ✓ throws an error (9 ms) + authenticated + ✓ throws error when amount is zero (6 ms) + ✓ throws error when amount is negative (4 ms) + ✓ throws error when memo text is too short (5 ms) + ✓ throws error when memo text is too long (7 ms) + ✓ throws error when user has not enough GDD (13 ms) + ✓ logs the error "User has not enough GDD" + redeemTransactionLink + unauthenticated + ✓ throws an error (1 ms) + authenticated + contributionLink + input not valid + ✓ throws error when link does not exists (14 ms) + ✓ logs the error "No contribution link found to given code" (1 ms) + ✓ throws error when link is not valid yet (31 ms) + ✓ logs the error "Contribution link is not valid yet" (1 ms) + ✓ throws error when contributionLink cycle is invalid (26 ms) + ✓ logs the error "Contribution link has unknown cycle" + ✓ throws error when link is no longer valid (24 ms) + ✓ logs the error "Contribution link is no longer valid" (1 ms) + redeem daily Contribution Link + ✓ has a daily contribution link in the database (3 ms) + user has pending contribution of 1000 GDD + ✓ does not allow the user to redeem the contribution link (11 ms) + ✓ logs the error "Creation from contribution link was not successful" (1 ms) + user has no pending contributions that would not allow to redeem the link + ✓ allows the user to redeem the contribution link (24 ms) + ✓ stores the CONTRIBUTION_LINK_REDEEM event in the database (10 ms) + ✓ does not allow the user to redeem the contribution link a second time on the same day (8 ms) + ✓ logs the error "Creation from contribution link was not successful" + after one day + ✓ allows the user to redeem the contribution link again (25 ms) + ✓ does not allow the user to redeem the contribution link a second time on the same day (15 ms) + ✓ logs the error "Creation from contribution link was not successful" + transaction link + link does not exits + ✓ throws and logs the error (6 ms) + link exists + ✓ stores the TRANSACTION_LINK_CREATE event in the database (5 ms) + own link + ✓ throws and logs an error (13 ms) + ✓ delete own link (16 ms) + ✓ stores the TRANSACTION_LINK_DELETE event in the database (9 ms) + other link + ✓ successfully redeems link (99 ms) + ✓ stores the TRANSACTION_LINK_REDEEM event in the database (16 ms) + listTransactionLinksAdmin + unauthenticated + ✓ returns an error (5 ms) + authenticated + without admin rights + ✓ returns an error (4 ms) + with admin rights + ✓ throws error when user does not exists (6 ms) + ✓ logs the error "Could not find requested User" + without any filters + ✓ finds 6 open transaction links and no deleted or redeemed (14 ms) + all filters are null + ✓ finds 6 open transaction links and no deleted or redeemed (7 ms) + filter with deleted + ✓ finds 6 open transaction links, 1 deleted, and no redeemed (7 ms) + filter by expired + ✓ finds 5 open transaction links, 1 expired, and no redeemed (10 ms) + filter by redeemed + ○ skipped finds 6 open transaction links, 1 deleted, and no redeemed + transactionLinkCode + ✓ returns a string of length 24 + ✓ returns a string that ends with the hex value of date + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/ContributionMessageResolver.test.tssrc/graphql/resolver/ContributionMessageResolver.test.ts]8;; + ContributionMessageResolver + adminCreateContributionMessage + unauthenticated + ✓ returns an error (12 ms) + authenticated + input not valid + ✓ throws error when contribution does not exist (11 ms) + ✓ logs the error "ContributionMessage was not sent successfully: Error: Contribution not found" (1 ms) + ✓ throws error when contribution.userId equals user.id (116 ms) + ✓ logs the error "ContributionMessage was not sent successfully: Error: Admin can not answer on his own contribution" (1 ms) + contribution message type MODERATOR + ✓ creates ContributionMessage (17 ms) + ✓ does not call sendAddedContributionMessageEmail (1 ms) + ✓ does not change contribution status (2 ms) + valid input + ✓ creates ContributionMessage (52 ms) + ✓ calls sendAddedContributionMessageEmail (1 ms) + ✓ changes contribution status (4 ms) + ✓ stores the ADMIN_CONTRIBUTION_MESSAGE_CREATE event in the database (3 ms) + createContributionMessage + unauthenticated + ✓ returns an error (2 ms) + authenticated + input not valid + ✓ throws error when contribution does not exist (11 ms) + ✓ logs the error "ContributionMessage was not sent successfully: Error: Contribution not found" + ✓ throws error when other user tries to send createContributionMessage (99 ms) + ✓ logs the error "ContributionMessage was not sent successfully: Error: Can not send message to contribution of another user" (1 ms) + valid input + ✓ creates ContributionMessage (15 ms) + ✓ stores the CONTRIBUTION_MESSAGE_CREATE event in the database (2 ms) + listContributionMessages + unauthenticated + ✓ returns an error (2 ms) + authenticated + ✓ returns a list of contributionmessages without type MODERATOR (11 ms) + adminListContributionMessages + unauthenticated + ✓ returns an error (2 ms) + authenticated as user + ✓ returns an error (5 ms) + authenticated as admin + ✓ returns a list of contributionmessages with type MODERATOR (11 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/TransactionResolver.test.tssrc/graphql/resolver/TransactionResolver.test.ts]8;; + send coins + unknown recipient + ✓ throws an error (106 ms) + ✓ logs the error thrown (1 ms) + deleted recipient + ✓ throws an error (94 ms) + ✓ logs the error thrown (1 ms) + recipient account not activated + ✓ throws an error (92 ms) + ✓ logs the error thrown (1 ms) + errors in the transaction itself + sender and recipient are the same + ✓ throws an error (27 ms) + ✓ logs the error thrown (1 ms) + memo text is too short + ✓ throws an error (4 ms) + memo text is too long + ✓ throws an error (6 ms) + user has not enough GDD + ✓ throws an error (14 ms) + ✓ logs the error thrown + user has some GDD + trying to send negative amount + ✓ throws an error (4 ms) + good transaction + ✓ sends the coins (37 ms) + ✓ stores the TRANSACTION_SEND event in the database (35 ms) + ✓ stores the TRANSACTION_RECEIVE event in the database (6 ms) + sendTransactionsToDltConnector + ✓ has wait till sendTransactionsToDltConnector created all dlt-transactions (1 ms) + send coins via gradido ID + ✓ sends the coins (45 ms) + send coins via alias + ✓ sends the coins (38 ms) + peter's transactions + ✓ has all expected transactions (173 ms) + X-Com send coins via gradido ID + ✓ sends the coins (52 ms) + more transactions to test semaphore + ✓ sends the coins four times in a row (251 ms) + transactionList + unauthenticated + ✓ throws an error (9 ms) + authenticated + no transactions + ✓ has no transactions and balance 0 (82 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/util/creations.test.tssrc/graphql/resolver/util/creations.test.ts]8;; + util/creation + getUserCreations + ✓ has the correct data setup (4 ms) + call getUserCreation now + ✓ returns the expected open contributions (4 ms) + run forward in time one hour before next month + ✓ has the clock set correctly (1 ms) + call getUserCreation with UTC + ✓ returns the expected open contributions (2 ms) + call getUserCreation with JST (GMT+0900) + ✓ returns the expected open contributions (2 ms) + call getUserCreation with PST (GMT-0800) + ✓ returns the expected open contributions (2 ms) + run two hours forward to be in the next month in UTC + ✓ has the clock set correctly + call getUserCreation with UTC + ✓ returns the expected open contributions (2 ms) + call getUserCreation with JST (GMT+0900) + ✓ returns the expected open contributions (2 ms) + call getUserCreation with PST (GMT-0800) + ✓ returns the expected open contributions (3 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/ContributionLinkResolver.test.tssrc/graphql/resolver/ContributionLinkResolver.test.ts]8;; + Contribution Links + unauthenticated + createContributionLink + ✓ returns an error (11 ms) + listContributionLinks + ✓ returns an error (2 ms) + updateContributionLink + ✓ returns an error (3 ms) + deleteContributionLink + ✓ returns an error (2 ms) + authenticated + without admin rights + createContributionLink + ✓ returns an error (6 ms) + listContributionLinks + ✓ returns an empty object (7 ms) + updateContributionLink + ✓ returns an error (7 ms) + deleteContributionLink + ✓ returns an error (4 ms) + with admin rights + createContributionLink + ✓ returns a contribution link object (18 ms) + ✓ has a contribution link stored in db (4 ms) + ✓ stores the ADMIN_CONTRIBUTION_LINK_CREATE event in the database (5 ms) + ✓ returns an error if missing startDate (8 ms) + ✓ logs the error "A Start-Date must be set" (1 ms) + ✓ returns an error if missing endDate (4 ms) + ✓ logs the error "An End-Date must be set" (1 ms) + ✓ returns an error if endDate is before startDate (4 ms) + ✓ logs the error "The value of validFrom must before or equals the validTo" + ✓ returns an error if name is shorter than 5 characters (4 ms) + ✓ returns an error if name is longer than 100 characters (7 ms) + ✓ returns an error if memo is shorter than 5 characters (4 ms) + ✓ returns an error if memo is longer than 255 characters (4 ms) + ✓ returns an error if amount is not positive (4 ms) + listContributionLinks + one link in DB + ✓ returns the link and count 1 (6 ms) + updateContributionLink + ✓ logs the error "Contribution Link not found" + no valid id + ✓ returns an error (8 ms) + valid id + ✓ returns updated contribution link object (15 ms) + ✓ updated the DB record (2 ms) + ✓ stores the ADMIN_CONTRIBUTION_LINK_UPDATE event in the database (2 ms) + deleteContributionLink + no valid id + ✓ returns an error (5 ms) + ✓ logs the error "Contribution Link not found" + valid id + ✓ returns true (18 ms) + ✓ stores the ADMIN_CONTRIBUTION_LINK_DELETE event in the database (5 ms) + ✓ does not list this contribution link anymore (8 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/KlicktippResolver.test.tssrc/graphql/resolver/KlicktippResolver.test.ts]8;; + KlicktippResolver + subscribeNewsletter + unauthenticated + ✓ returns an error (9 ms) + authenticated + ✓ calls API (11 ms) + ✓ stores the NEWSLETTER_SUBSCRIBE event in the database (5 ms) + unsubscribeNewsletter + unauthenticated + ✓ returns an error (2 ms) + authenticated + ✓ calls API (11 ms) + ✓ stores the NEWSLETTER_UNSUBSCRIBE event in the database (5 ms) + +(node:16141) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit +(Use `node --trace-warnings ...` to show where the warning was created) + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/util/klicktipp.test.tssrc/util/klicktipp.test.ts]8;; + klicktipp + exportEventDataToKlickTipp + ✓ calls the KlicktippController (13 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/semaphore.test.tssrc/graphql/resolver/semaphore.test.ts]8;; + semaphore + ✓ creates a lot of transactions without errors (772 ms) + redeem transaction link twice + ✓ does throw error on second redeem call (107 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/util/validateAlias.test.tssrc/graphql/resolver/util/validateAlias.test.ts]8;; + validate alias + alias too short + ✓ throws and logs an error (1 ms) + alias too long + ✓ throws and logs an error (1 ms) + alias contains invalid characters + ✓ throws and logs an error + alias is a reserved word + ✓ throws and logs an error (1 ms) + alias is a reserved word with uppercase characters + ✓ throws and logs an error (1 ms) + hyphens and underscore + alias starts with underscore + ✓ throws and logs an error (3 ms) + alias contains two following hyphens + ✓ throws and logs an error (1 ms) + test against existing alias in database + alias exists in database + ✓ throws and logs an error (3 ms) + alias exists in database with in lower-case + ✓ throws and logs an error (3 ms) + valid alias + ✓ resolves to true (2 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/CommunityResolver.test.tssrc/graphql/resolver/CommunityResolver.test.ts]8;; + CommunityResolver + getCommunities + with empty list + ✓ returns no community entry (13 ms) + only home-communities entries + ✓ returns 3 home-community entries (50 ms) + plus foreign-communities entries + ✓ returns 3 home community and 3 foreign community entries (17 ms) + communities + with empty list + ✓ returns no community entry (33 ms) + with one home-community entry + ✓ returns 1 home-community entry (29 ms) + returns 2 filtered communities even with 3 existing entries + ✓ returns 2 community entries (43 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.tssrc/graphql/resolver/util/sendTransactionsToDltConnector.test.ts]8;; + create and send Transactions to DltConnector + with 3 creations but inactive dlt-connector + ✓ found 3 dlt-transactions (102 ms) + with 3 creations and active dlt-connector + ✓ found 3 dlt-transactions (104 ms) + with 3 verified creations, 1 sendCoins and active dlt-connector + ✓ found 3 dlt-transactions (114 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/EmailOptinCodes.test.tssrc/graphql/resolver/EmailOptinCodes.test.ts]8;; + EmailOptinCodes + queryOptIn + ✓ has a valid optin code (4 ms) + run time forward until code must be expired + ✓ throws an error (23 ms) + ✓ does not allow to set password (11 ms) + forgotPassword + ✓ throws an error (11 ms) + run time forward until code can be resent + ✓ cann send email again (17 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/federation/validateCommunities.test.tssrc/federation/validateCommunities.test.ts]8;; + validate Communities + start validation logic without loop + ✓ logs zero communities found (13 ms) + with one Community of api 1_0 but missing pubKey response + ✓ logs one community found (25 ms) + ✓ logs requestGetPublicKey missing response data (12 ms) + with one Community of api 1_0 and not matching pubKey + ✓ logs one community found (22 ms) + ✓ logs requestGetPublicKey for community api 1_0 (14 ms) + ✓ logs not matching publicKeys (14 ms) + with one Community of api 1_0 and matching pubKey + ✓ logs one community found (36 ms) + ✓ logs requestGetPublicKey for community api 1_0 (24 ms) + ✓ logs community pubKey verified (65 ms) + with two Communities of api 1_0 and 1_1 + ✓ logs two communities found (18 ms) + ✓ logs requestGetPublicKey for community api 1_0 (19 ms) + ✓ logs requestGetPublicKey for community api 1_1 (21 ms) + with three Communities of api 1_0, 1_1 and 2_0 + ✓ logs three community found (26 ms) + ✓ logs requestGetPublicKey for community api 1_0 (28 ms) + ✓ logs requestGetPublicKey for community api 1_1 (17 ms) + ✓ logs unsupported api for community with api 2_0 (19 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/apis/DltConnectorClient.test.tssrc/apis/DltConnectorClient.test.ts]8;; + undefined DltConnectorClient + ✓ invalid url (2 ms) + ✓ DLT_CONNECTOR is false (1 ms) + transmitTransaction + ✓ invalid transaction type (2 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/emails/sendEmailVariants.test.tssrc/emails/sendEmailVariants.test.ts]8;; + sendEmailVariants + sendAddedContributionMessageEmail + calls "sendEmailTranslated" + ✓ with expected parameters (2 ms) + result + ✓ is the expected object (1 ms) + ✓ has the correct html as snapshot (1 ms) + sendAccountActivationEmail + calls "sendEmailTranslated" + ✓ with expected parameters (1 ms) + result + ✓ is the expected object (1 ms) + ✓ has the correct html as snapshot (1 ms) + sendAccountMultiRegistrationEmail + calls "sendEmailTranslated" + ✓ with expected parameters + result + ✓ is the expected object + ✓ has the correct html as snapshot (1 ms) + sendContributionConfirmedEmail + calls "sendEmailTranslated" + ✓ with expected parameters (1 ms) + result + ✓ is the expected object + ✓ has the correct html as snapshot + sendContributionDeniedEmail + calls "sendEmailTranslated" + ✓ with expected parameters + result + ✓ has expected result (1 ms) + ✓ has the correct html as snapshot (1 ms) + sendContributionDeletedEmail + calls "sendEmailTranslated" + ✓ with expected parameters (1 ms) + result + ✓ is the expected object (1 ms) + ✓ has the correct html as snapshot + sendResetPasswordEmail + calls "sendEmailTranslated" + ✓ with expected parameters (1 ms) + result + ✓ is the expected object (1 ms) + ✓ has the correct html as snapshot + sendTransactionLinkRedeemedEmail + calls "sendEmailTranslated" + ✓ with expected parameters (1 ms) + result + ✓ is the expected object (1 ms) + ✓ has the correct html as snapshot + sendTransactionReceivedEmail + calls "sendEmailTranslated" + ✓ with expected parameters (1 ms) + result + ✓ is the expected object (1 ms) + ✓ has the correct html as snapshot + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/test/helpers.test.tstest/helpers.test.ts]8;; + contributionDateFormatter + ✓ formats the date correctly (2 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/emails/sendEmailTranslated.test.tssrc/emails/sendEmailTranslated.test.ts]8;; + sendEmailTranslated + config email is false + ✓ logs warning (2 ms) + ✓ returns false + config email is true + ✓ calls the transporter (104 ms) + ○ skipped calls "i18n.setLocale" with "en" + ○ skipped calls "i18n.__" for translation + call of "sendEmailTranslated" + ✓ has expected result (50 ms) + with email EMAIL_TEST_MODUS true + ✓ call of "sendEmailTranslated" with faked "to" (39 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/server/LogError.test.tssrc/server/LogError.test.ts]8;; + LogError + ✓ logs an Error when created (2 ms) + ✓ logs an Error including additional data when created (1 ms) + ✓ does not contain additional data in Error object when thrown (3 ms) + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/util/decay.test.tssrc/util/decay.test.ts]8;; + utils/decay + ✓ has base 0.99999997802044727 + ✓ returns input amount when from and to is the same (1 ms) + decayFormula + ✓ has base 0.99999997802044727 (1 ms) + ✓ has correct backward calculation + ○ skipped has correct forward calculation + + PASS  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/config/index.test.tssrc/config/index.test.ts]8;; + config/index + decay start block + ✓ has the correct date set (2 ms) + +Running coverage on untested files...Browserslist: caniuse-lite is outdated. Please run: + npx browserslist@latest --update-db + Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating +Summary of all failing tests + FAIL  ]8;;file:///home/einhornimmond/code/gradido_apollo/backend/src/graphql/resolver/ContributionResolver.test.tssrc/graphql/resolver/ContributionResolver.test.ts]8;; (9.447 s) + ● ContributionResolver › updateContribution › authenticated › admin tries to update a user contribution › throws an error + + expect(received).toEqual(expected) // deep equality + + Expected: [[GraphQLError: An admin is not allowed to update an user contribution]] + Received: undefined + +   509 | }, +   510 | }) + > 511 | expect(errorObjects).toEqual([ +   | ^ +   512 | new GraphQLError('An admin is not allowed to update an user contribution'), +   513 | ]) +   514 | }) + + at src/graphql/resolver/ContributionResolver.test.ts:511:32 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › updateContribution › authenticated › admin tries to update a user contribution › logs the error "An admin is not allowed to update an user contribution" + + expect(jest.fn()).toBeCalledWith(...expected) + + Expected: "An admin is not allowed to update an user contribution" + + Number of calls: 0 + +   515 | +   516 | it('logs the error "An admin is not allowed to update an user contribution"', () => { + > 517 | expect(logger.error).toBeCalledWith( +   | ^ +   518 | 'An admin is not allowed to update an user contribution', +   519 | ) +   520 | }) + + at Object. (src/graphql/resolver/ContributionResolver.test.ts:517:32) + + ● ContributionResolver › listContributions › authenticated › no status filter › returns creations + + expect(received).toMatchObject(expected) + + - Expected - 16 + + Received + 64 + +  Object { +  "contributionCount": 6, + - "contributionList": ArrayContaining [ + - ObjectContaining { + + "contributionList": Array [ + + Object { +  "amount": "100", + - "id": 25, + - "memo": "Test contribution to confirm", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, + + "id": 27, + + "memo": "Test contribution to delete", +  "messagesCount": 0, + - }, + - ObjectContaining { + - "amount": "10", + - "id": 23, + - "memo": "Test PENDING contribution update", + - "messagesCount": 1, + + "status": "DELETED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "id": 26, +  "memo": "Test contribution to deny", +  "messagesCount": 0, + + "status": "DENIED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + - "id": 27, + - "memo": "Test contribution to delete", + + "confirmedAt": "2023-11-06T08:36:49.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, + + "id": 25, + + "memo": "Test contribution to confirm", +  "messagesCount": 0, + + "status": "CONFIRMED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, +  "id": 24, +  "memo": "Test IN_PROGRESS contribution", +  "messagesCount": 1, + + "status": "IN_PROGRESS", +  }, + - ObjectContaining { + + Object { + + "amount": "10", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:47.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, + + "id": 23, + + "memo": "Test PENDING contribution update", + + "messagesCount": 2, + + "status": "PENDING", + + }, + + Object { +  "amount": "1000", + + "confirmedAt": "2023-11-06T08:36:46.000Z", + + "confirmedBy": 283, + + "contributionDate": "2022-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, +  "id": 22, +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, + + "status": "CONFIRMED", +  }, +  ], +  } + +   1103 | }, +   1104 | }) + > 1105 | expect(contributionListResult).toMatchObject({ +   | ^ +   1106 | contributionCount: 6, +   1107 | contributionList: expect.arrayContaining([ +   1108 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:1105:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › listContributions › authenticated › with status filter [PENDING, IN_PROGRESS, DENIED, DELETED] › returns only unconfirmed creations + + expect(received).toMatchObject(expected) + + - Expected - 19 + + Received + 44 + +  Object { +  "contributionCount": 4, + - "contributionList": ArrayContaining [ + - ObjectNotContaining { + - "status": "CONFIRMED", + + "contributionList": Array [ + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, + + "id": 27, + + "memo": "Test contribution to delete", + + "messagesCount": 0, + + "status": "DELETED", +  }, + - ObjectContaining { + - "amount": "10", + - "id": 23, + - "memo": "Test PENDING contribution update", + - "messagesCount": 1, + - "status": "PENDING", + - }, + - ObjectContaining { + + Object { +  "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "id": 26, +  "memo": "Test contribution to deny", +  "messagesCount": 0, +  "status": "DENIED", +  }, + - ObjectContaining { + + Object { +  "amount": "100", + - "id": 27, + - "memo": "Test contribution to delete", + - "messagesCount": 0, + - "status": "DELETED", + - }, + - ObjectContaining { + - "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, +  "id": 24, +  "memo": "Test IN_PROGRESS contribution", +  "messagesCount": 1, +  "status": "IN_PROGRESS", + + }, + + Object { + + "amount": "10", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:47.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deletedAt": null, + + "deniedAt": null, + + "deniedBy": null, + + "id": 23, + + "memo": "Test PENDING contribution update", + + "messagesCount": 2, + + "status": "PENDING", +  }, +  ], +  } + +   1161 | }, +   1162 | }) + > 1163 | expect(contributionListResult).toMatchObject({ +   | ^ +   1164 | contributionCount: 4, +   1165 | contributionList: expect.arrayContaining([ +   1166 | expect.not.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:1163:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › adminListContributions › authenticated as admin › returns 18 creations in total + + expect(received).toMatchObject(expected) + + - Expected - 57 + + Received + 165 + + @@ -1,165 +1,273 @@ +  Object { +  "contributionCount": 18, + - "contributionList": ArrayContaining [ + - ObjectContaining { + - "amount": decimalEqual<100>, + + "contributionList": Array [ + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:51.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 39, +  "lastName": "Lustig", +  "memo": "#firefighters", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<50>, + + Object { + + "amount": "50", + + "confirmedAt": "2023-11-06T08:36:51.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-09-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 38, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido liebe Bibi!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<50>, + + Object { + + "amount": "50", + + "confirmedAt": "2023-11-06T08:36:51.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-09-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 37, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido liebe Bibi!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<450>, + + Object { + + "amount": "450", + + "confirmedAt": "2023-11-06T08:36:51.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-09-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 36, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido liebe Bibi!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<400>, + + Object { + + "amount": "400", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 35, +  "lastName": "Lustig", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": "2023-11-06T08:36:50.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bob", + - "id": Any, + + "id": 34, +  "lastName": "der Baumeister", +  "memo": "Confirmed Contribution", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 33, +  "lastName": "Lustig", +  "memo": "Test env contribution", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 32, +  "lastName": "Bloxberg", +  "memo": "Aktives Grundeinkommen", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 31, +  "lastName": "Lustig", +  "memo": "Das war leider zu Viel!", + - "messagesCount": 0, + + "messagesCount": 1, +  "status": "DELETED", +  }, + - ObjectContaining { + - "amount": decimalEqual<166>, + + Object { + + "amount": "166", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:48.000Z", + + "createdAt": "2023-11-06T08:36:48.000Z", + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "firstName": "Räuber", + - "id": Any, + + "id": 30, +  "lastName": "Hotzenplotz", +  "memo": "Whatever contribution", +  "messagesCount": 0, +  "status": "DENIED", +  }, + - ObjectContaining { + - "amount": decimalEqual<166>, + + Object { + + "amount": "166", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:48.000Z", + + "createdAt": "2023-11-06T08:36:48.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Räuber", + - "id": Any, + + "id": 29, +  "lastName": "Hotzenplotz", +  "memo": "Whatever contribution", +  "messagesCount": 0, +  "status": "DELETED", +  }, + - ObjectContaining { + - "amount": decimalEqual<166>, + + Object { + + "amount": "166", + + "confirmedAt": "2023-11-06T08:36:48.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T08:36:48.000Z", + + "createdAt": "2023-11-06T08:36:48.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Räuber", + - "id": Any, + + "id": 28, +  "lastName": "Hotzenplotz", +  "memo": "Whatever contribution", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 27, +  "lastName": "Bloxberg", +  "memo": "Test contribution to delete", +  "messagesCount": 0, +  "status": "DELETED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": "2023-11-06T08:36:48.000Z", + + "deniedBy": 283, +  "firstName": "Bibi", + - "id": Any, + + "id": 26, +  "lastName": "Bloxberg", +  "memo": "Test contribution to deny", +  "messagesCount": 0, +  "status": "DENIED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": "2023-11-06T08:36:49.000Z", + + "confirmedBy": 283, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 25, +  "lastName": "Bloxberg", +  "memo": "Test contribution to confirm", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:46.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 24, +  "lastName": "Bloxberg", +  "memo": "Test IN_PROGRESS contribution", +  "messagesCount": 1, +  "status": "IN_PROGRESS", +  }, + - ObjectContaining { + - "amount": decimalEqual<10>, + + Object { + + "amount": "10", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:47.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 23, +  "lastName": "Bloxberg", +  "memo": "Test PENDING contribution update", + - "messagesCount": 2, + + "messagesCount": 3, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<1000>, + + Object { + + "amount": "1000", + + "confirmedAt": "2023-11-06T08:36:46.000Z", + + "confirmedBy": 283, + + "contributionDate": "2022-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:46.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Bibi", + - "id": Any, + + "id": 22, +  "lastName": "Bloxberg", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "CONFIRMED", +  }, + +   2827 | // console.log('17 contributions: %s', JSON.stringify(contributionListObject, null, 2)) +   2828 | expect(contributionListObject.contributionList).toHaveLength(18) + > 2829 | expect(contributionListObject).toMatchObject({ +   | ^ +   2830 | contributionCount: 18, +   2831 | contributionList: expect.arrayContaining([ +   2832 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:2829:40 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › adminListContributions › authenticated as admin › with user query › returns only contributions of the queried user + + expect(received).toMatchObject(expected) + + - Expected - 14 + + Received + 38 + +  Object { +  "contributionCount": 4, + - "contributionList": ArrayContaining [ + - ObjectContaining { + - "amount": decimalEqual<100>, + + "contributionList": Array [ + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T08:36:51.000Z", + + "createdAt": "2023-11-06T08:36:51.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 39, +  "lastName": "Lustig", +  "memo": "#firefighters", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<400>, + + Object { + + "amount": "400", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 35, +  "lastName": "Lustig", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 33, +  "lastName": "Lustig", +  "memo": "Test env contribution", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 31, +  "lastName": "Lustig", +  "memo": "Das war leider zu Viel!", + - "messagesCount": 0, + + "messagesCount": 1, +  "status": "DELETED", +  }, +  ], +  } + +   3057 | }) +   3058 | expect(contributionListObject.contributionList).toHaveLength(4) + > 3059 | expect(contributionListObject).toMatchObject({ +   | ^ +   3060 | contributionCount: 4, +   3061 | contributionList: expect.arrayContaining([ +   3062 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:3059:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + ● ContributionResolver › adminListContributions › authenticated as admin › with user query › returns only contributions of the queried user without hashtags + + expect(received).toMatchObject(expected) + + - Expected - 11 + + Received + 29 + +  Object { +  "contributionCount": 3, + - "contributionList": ArrayContaining [ + - ObjectContaining { + - "amount": decimalEqual<400>, + + "contributionList": Array [ + + Object { + + "amount": "400", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 35, +  "lastName": "Lustig", +  "memo": "Herzlich Willkommen bei Gradido!", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<100>, + + Object { + + "amount": "100", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-11-06T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:50.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 33, +  "lastName": "Lustig", +  "memo": "Test env contribution", +  "messagesCount": 0, +  "status": "PENDING", +  }, + - ObjectContaining { + - "amount": decimalEqual<200>, + + Object { + + "amount": "200", + + "confirmedAt": null, + + "confirmedBy": null, + + "contributionDate": "2023-10-01T00:00:00.000Z", + + "createdAt": "2023-11-06T08:36:49.000Z", + + "deniedAt": null, + + "deniedBy": null, +  "firstName": "Peter", + - "id": Any, + + "id": 31, +  "lastName": "Lustig", +  "memo": "Das war leider zu Viel!", + - "messagesCount": 0, + + "messagesCount": 1, +  "status": "DELETED", +  }, +  ], +  } + +   3111 | }) +   3112 | expect(contributionListObject.contributionList).toHaveLength(3) + > 3113 | expect(contributionListObject).toMatchObject({ +   | ^ +   3114 | contributionCount: 3, +   3115 | contributionList: expect.arrayContaining([ +   3116 | expect.objectContaining({ + + at src/graphql/resolver/ContributionResolver.test.ts:3113:42 + at fulfilled (src/graphql/resolver/ContributionResolver.test.ts:5:58) + + +Test Suites: 1 failed, 21 passed, 22 total +Tests: 7 failed, 8 skipped, 482 passed, 497 total +Snapshots: 9 passed, 9 total +Time: 59.16 s +Ran all test suites. +error Command failed with exit code 1. diff --git a/backend/src/apis/DltConnectorClient.test.ts b/backend/src/apis/DltConnectorClient.test.ts index 56fa3d13f..421b957e9 100644 --- a/backend/src/apis/DltConnectorClient.test.ts +++ b/backend/src/apis/DltConnectorClient.test.ts @@ -25,8 +25,6 @@ let testEnv: { jest.mock('graphql-request', () => { const originalModule = jest.requireActual('graphql-request') - let testCursor = 0 - return { __esModule: true, ...originalModule, @@ -38,30 +36,11 @@ jest.mock('graphql-request', () => { // why not using mockResolvedValueOnce or mockReturnValueOnce? // I have tried, but it didn't work and return every time the first value request: jest.fn().mockImplementation(() => { - testCursor++ - if (testCursor === 4) { - return Promise.resolve( - // invalid, is 33 Bytes long as binary - { - transmitTransaction: { - dltTransactionIdHex: - '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A', - }, - }, - ) - } else if (testCursor === 5) { - throw Error('Connection error') - } else { - return Promise.resolve( - // valid, is 32 Bytes long as binary - { - transmitTransaction: { - dltTransactionIdHex: - '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', - }, - }, - ) - } + return Promise.resolve({ + transmitTransaction: { + succeed: true, + }, + }) }), } }), @@ -134,7 +113,10 @@ describe('transmitTransaction', () => { const localTransaction = new DbTransaction() localTransaction.typeId = 12 try { - await DltConnectorClient.getInstance()?.transmitTransaction(localTransaction) + await DltConnectorClient.getInstance()?.transmitTransaction( + localTransaction, + 'senderCommunityUUID', + ) } catch (e) { expect(e).toMatchObject( new LogError('invalid transaction type id: ' + localTransaction.typeId.toString()), diff --git a/backend/src/apis/DltConnectorClient.ts b/backend/src/apis/DltConnectorClient.ts index c558da77a..14f95fbe7 100644 --- a/backend/src/apis/DltConnectorClient.ts +++ b/backend/src/apis/DltConnectorClient.ts @@ -6,6 +6,7 @@ import { CONFIG } from '@/config' import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' +import { Contribution } from '@entity/Contribution' const sendTransaction = gql` mutation ($input: TransactionInput!) { @@ -125,15 +126,14 @@ export class DltConnectorClient { transaction: DbTransaction, type: 'sender' | 'recipient', ): Promise { - const confirmingUserId = transaction.contribution?.confirmedBy - let confirmingUser: DbUser | undefined + let confirmingUserId: number | undefined logger.info('confirming user id', confirmingUserId) - if (confirmingUserId) { - confirmingUser = await DbUser.findOneOrFail({ where: { id: confirmingUserId } }) - } switch (transaction.typeId) { case TransactionTypeId.CREATION: - if (!confirmingUserId || !confirmingUser) { + confirmingUserId = ( + await Contribution.findOneOrFail({ where: { transactionId: transaction.id } }) + ).confirmedBy + if (!confirmingUserId) { throw new LogError( "couldn't find id of confirming moderator for contribution transaction!", ) @@ -170,8 +170,8 @@ export class DltConnectorClient { protected async getCorrectUserIdentifier( transaction: DbTransaction, senderCommunityUuid: string, - recipientCommunityUuid: string, type: 'sender' | 'recipient', + recipientCommunityUuid?: string, ): Promise { // sender and receiver user on creation transaction // sender user on send transaction (SEND and RECEIVE) @@ -195,7 +195,7 @@ export class DltConnectorClient { public async transmitTransaction( transaction: DbTransaction, senderCommunityUuid: string, - recipientCommunityUuid: string, + recipientCommunityUuid?: string, ): Promise { const typeString = getTransactionTypeString(transaction.typeId) // no negative values in dlt connector, gradido concept don't use negative values so the code don't use it too @@ -205,14 +205,14 @@ export class DltConnectorClient { senderUser: await this.getCorrectUserIdentifier( transaction, senderCommunityUuid, - recipientCommunityUuid, 'sender', + recipientCommunityUuid, ), recipientUser: await this.getCorrectUserIdentifier( transaction, senderCommunityUuid, - recipientCommunityUuid, 'recipient', + recipientCommunityUuid, ), amount: amountString, type: typeString, diff --git a/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts b/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts index d9a2da569..05a5b44cf 100644 --- a/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts +++ b/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts @@ -24,6 +24,15 @@ import { CONFIG } from '@/config' import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId' import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector' +import { Contribution } from '@entity/Contribution' +import { User } from '@entity/User' +import { userFactory } from '@/seeds/factory/user' +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' +import { creations } from '@/seeds/creation' +import { creationFactory } from '@/seeds/factory/creation' +import { peterLustig } from '@/seeds/users/peter-lustig' +import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz' +import { bobBaumeister } from '@/seeds/users/bob-baumeister' /* // Mock the GraphQLClient @@ -423,9 +432,17 @@ describe('create and send Transactions to DltConnector', () => { describe('with 3 creations and active dlt-connector', () => { it('found 3 dlt-transactions', async () => { - txCREATION1 = await createTxCREATION1(false) - txCREATION2 = await createTxCREATION2(false) - txCREATION3 = await createTxCREATION3(false) + await userFactory(testEnv, bibiBloxberg) + await userFactory(testEnv, peterLustig) + await userFactory(testEnv, raeuberHotzenplotz) + await userFactory(testEnv, bobBaumeister) + let count = 0 + for (const creation of creations) { + await creationFactory(testEnv, creation) + count++ + // we need only 3 for testing + if (count >= 3) break + } await createHomeCommunity() CONFIG.DLT_CONNECTOR = true @@ -435,10 +452,7 @@ describe('create and send Transactions to DltConnector', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { data: { - sendTransaction: { - dltTransactionIdHex: - '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', - }, + sendTransaction: { succeed: true }, }, } as Response }) @@ -464,7 +478,7 @@ describe('create and send Transactions to DltConnector', () => { expect.objectContaining({ id: expect.any(Number), transactionId: transactions[0].id, - messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', + messageId: 'sended', verified: false, createdAt: expect.any(Date), verifiedAt: null, @@ -472,7 +486,7 @@ describe('create and send Transactions to DltConnector', () => { expect.objectContaining({ id: expect.any(Number), transactionId: transactions[1].id, - messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', + messageId: 'sended', verified: false, createdAt: expect.any(Date), verifiedAt: null, @@ -480,7 +494,7 @@ describe('create and send Transactions to DltConnector', () => { expect.objectContaining({ id: expect.any(Number), transactionId: transactions[2].id, - messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', + messageId: 'sended', verified: false, createdAt: expect.any(Date), verifiedAt: null, @@ -514,10 +528,7 @@ describe('create and send Transactions to DltConnector', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { data: { - sendTransaction: { - dltTransactionIdHex: - '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', - }, + sendTransaction: { succeed: true }, }, } as Response }) @@ -569,7 +580,7 @@ describe('create and send Transactions to DltConnector', () => { expect.objectContaining({ id: expect.any(Number), transactionId: txSEND1to2.id, - messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', + messageId: 'sended', verified: false, createdAt: expect.any(Date), verifiedAt: null, @@ -577,7 +588,7 @@ describe('create and send Transactions to DltConnector', () => { expect.objectContaining({ id: expect.any(Number), transactionId: txRECEIVE2From1.id, - messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621', + messageId: 'sended', verified: false, createdAt: expect.any(Date), verifiedAt: null, diff --git a/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.ts b/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.ts index 98e1ffbe3..9c6d50725 100644 --- a/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.ts +++ b/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.ts @@ -23,7 +23,6 @@ export async function sendTransactionsToDltConnector(): Promise { if (!senderCommunityUuid) { throw new Error('Cannot find community uuid of home community') } - const recipientCommunityUuid = '' if (dltConnector) { logger.debug('with sending to DltConnector...') const dltTransactions = await DltTransaction.find({ @@ -37,27 +36,23 @@ export async function sendTransactionsToDltConnector(): Promise { continue } try { - const messageId = await dltConnector.transmitTransaction( + const result = await dltConnector.transmitTransaction( dltTx.transaction, senderCommunityUuid, - recipientCommunityUuid, ) - const dltMessageId = Buffer.from(messageId, 'hex') - if (dltMessageId.length !== 32) { - logger.error( - 'Error dlt message id is invalid: %s, should by 32 Bytes long in binary after converting from hex', - dltMessageId, - ) - return + // message id isn't known at this point of time, because transaction will not direct sended to iota, + // it will first go to db and then sended, if no transaction is in db before + if (result) { + dltTx.messageId = 'sended' + await DltTransaction.save(dltTx) + logger.info('store messageId=%s in dltTx=%d', dltTx.messageId, dltTx.id) } - dltTx.messageId = dltMessageId.toString('hex') - await DltTransaction.save(dltTx) - logger.info('store messageId=%s in dltTx=%d', dltTx.messageId, dltTx.id) } catch (e) { logger.error( `error while sending to dlt-connector or writing messageId of dltTx=${dltTx.id}`, e, ) + console.log('error', e) } } } else { diff --git a/dlt-connector/src/data/BackendTransaction.factory.ts b/dlt-connector/src/data/BackendTransaction.factory.ts new file mode 100644 index 000000000..365da0693 --- /dev/null +++ b/dlt-connector/src/data/BackendTransaction.factory.ts @@ -0,0 +1,13 @@ +import { BackendTransaction } from '@entity/BackendTransaction' + +import { TransactionDraft } from '@/graphql/input/TransactionDraft' + +export class BackendTransactionFactory { + public static createFromTransactionDraft(transactionDraft: TransactionDraft): BackendTransaction { + const backendTransaction = BackendTransaction.create() + backendTransaction.backendTransactionId = transactionDraft.backendTransactionId + backendTransaction.typeId = transactionDraft.type + backendTransaction.createdAt = new Date(transactionDraft.createdAt) + return backendTransaction + } +} diff --git a/dlt-connector/src/data/BackendTransaction.repository.ts b/dlt-connector/src/data/BackendTransaction.repository.ts new file mode 100644 index 000000000..b4e566659 --- /dev/null +++ b/dlt-connector/src/data/BackendTransaction.repository.ts @@ -0,0 +1,7 @@ +import { BackendTransaction } from '@entity/BackendTransaction' + +import { getDataSource } from '@/typeorm/DataSource' + +export const BackendTransactionRepository = getDataSource() + .getRepository(BackendTransaction) + .extend({}) diff --git a/dlt-connector/src/data/Transaction.builder.ts b/dlt-connector/src/data/Transaction.builder.ts index 7cbcedac5..115391e91 100644 --- a/dlt-connector/src/data/Transaction.builder.ts +++ b/dlt-connector/src/data/Transaction.builder.ts @@ -4,11 +4,13 @@ import { Transaction } from '@entity/Transaction' import { GradidoTransaction } from '@/data/proto/3_3/GradidoTransaction' import { TransactionBody } from '@/data/proto/3_3/TransactionBody' +import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { UserIdentifier } from '@/graphql/input/UserIdentifier' import { LogError } from '@/server/LogError' import { bodyBytesToTransactionBody, transactionBodyToBodyBytes } from '@/utils/typeConverter' import { AccountRepository } from './Account.repository' +import { BackendTransactionFactory } from './BackendTransaction.factory' import { CommunityRepository } from './Community.repository' import { TransactionBodyBuilder } from './proto/TransactionBody.builder' @@ -91,8 +93,13 @@ export class TransactionBuilder { return this } - public setBackendTransactionId(backendTransactionId: number): TransactionBuilder { - this.transaction.backendTransactionId = backendTransactionId + public addBackendTransaction(transactionDraft: TransactionDraft): TransactionBuilder { + if (!this.transaction.backendTransactions) { + this.transaction.backendTransactions = [] + } + this.transaction.backendTransactions.push( + BackendTransactionFactory.createFromTransactionDraft(transactionDraft), + ) return this } diff --git a/dlt-connector/src/data/Transaction.repository.ts b/dlt-connector/src/data/Transaction.repository.ts index 9c21476ad..6ba622c9c 100644 --- a/dlt-connector/src/data/Transaction.repository.ts +++ b/dlt-connector/src/data/Transaction.repository.ts @@ -20,30 +20,21 @@ export const TransactionRepository = getDataSource() relations: { signingAccount: true }, }) }, - async findExistingTransactionAndMissingMessageIds(messageIDsHex: string[]): Promise<{ - existingTransactions: Transaction[] - missingMessageIdsHex: string[] - }> { - const existingTransactions = await this.createQueryBuilder('Transaction') + findExistingTransactionAndMissingMessageIds(messageIDsHex: string[]): Promise { + return this.createQueryBuilder('Transaction') .where('HEX(Transaction.iota_message_id) IN (:...messageIDs)', { messageIDs: messageIDsHex, }) + .leftJoinAndSelect('Transaction.community', 'Community') + .leftJoinAndSelect('Transaction.otherCommunity', 'OtherCommunity') .leftJoinAndSelect('Transaction.recipientAccount', 'RecipientAccount') + .leftJoinAndSelect('Transaction.backendTransactions', 'BackendTransactions') .leftJoinAndSelect('RecipientAccount.user', 'RecipientUser') .leftJoinAndSelect('Transaction.signingAccount', 'SigningAccount') .leftJoinAndSelect('SigningAccount.user', 'SigningUser') .getMany() - - const foundMessageIds = existingTransactions - .map((recipe) => recipe.iotaMessageId?.toString('hex')) - .filter((messageId) => !!messageId) - // find message ids for which we don't already have a transaction recipe - const missingMessageIdsHex = messageIDsHex.filter( - (id: string) => !foundMessageIds.includes(id), - ) - return { existingTransactions, missingMessageIdsHex } }, - async removeConfirmedTransaction(transactions: Transaction[]): Promise { + removeConfirmedTransaction(transactions: Transaction[]): Transaction[] { return transactions.filter( (transaction: Transaction) => transaction.runningHash === undefined || transaction.runningHash.length === 0, diff --git a/dlt-connector/src/graphql/resolver/TransactionsResolver.ts b/dlt-connector/src/graphql/resolver/TransactionsResolver.ts index 4f3f3efe2..10b55573e 100755 --- a/dlt-connector/src/graphql/resolver/TransactionsResolver.ts +++ b/dlt-connector/src/graphql/resolver/TransactionsResolver.ts @@ -4,8 +4,8 @@ import { TransactionDraft } from '@input/TransactionDraft' import { TransactionRepository } from '@/data/Transaction.repository' import { CreateTransactionRecipeContext } from '@/interactions/backendToDb/transaction/CreateTransationRecipe.context' +import { LogError } from '@/server/LogError' -import { TransactionErrorType } from '../enum/TransactionErrorType' import { TransactionError } from '../model/TransactionError' import { TransactionRecipe } from '../model/TransactionRecipe' import { TransactionResult } from '../model/TransactionResult' @@ -21,22 +21,28 @@ export class TransactionResolver { try { await createTransactionRecipeContext.run() const transactionRecipe = createTransactionRecipeContext.getTransactionRecipe() - await transactionRecipe.save() + // check if a transaction with this signature already exist + const existingRecipe = await TransactionRepository.findBySignature( + transactionRecipe.signature, + ) + if (existingRecipe) { + // transaction recipe with this signature already exist, we need only to store the backendTransaction + if (transactionRecipe.backendTransactions.length !== 1) { + throw new LogError('unexpected backend transaction count', { + count: transactionRecipe.backendTransactions.length, + transactionId: transactionRecipe.id, + }) + } + const backendTransaction = transactionRecipe.backendTransactions[0] + backendTransaction.transactionId = transactionRecipe.id + await backendTransaction.save() + } else { + // we can store the transaction and with that automatic the backend transaction + await transactionRecipe.save() + } return new TransactionResult(new TransactionRecipe(transactionRecipe)) // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { - if (error.code === 'ER_DUP_ENTRY') { - const existingRecipe = await TransactionRepository.findBySignature( - createTransactionRecipeContext.getTransactionRecipe().signature, - ) - if (!existingRecipe) { - throw new TransactionError( - TransactionErrorType.LOGIC_ERROR, - "recipe cannot be added because signature exist but couldn't load this existing receipt", - ) - } - return new TransactionResult(new TransactionRecipe(existingRecipe)) - } if (error instanceof TransactionError) { return new TransactionResult(error) } else { diff --git a/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts b/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts index 362d114ef..7f77a5e82 100644 --- a/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts +++ b/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts @@ -40,11 +40,11 @@ export class TransactionRecipeRole { .setSigningAccount(signingAccount) .setRecipientAccount(recipientAccount) .fromTransactionDraft(transactionDraft) - // build transaction entity + // build transaction entity this.transactionBuilder .fromTransactionBodyBuilder(transactionBodyBuilder) - .setBackendTransactionId(transactionDraft.backendTransactionId) + .addBackendTransaction(transactionDraft) await this.transactionBuilder.setSenderCommunityFromSenderUser(senderUser) if (recipientUser.communityUuid !== senderUser.communityUuid) { await this.transactionBuilder.setOtherCommunityFromRecipientUser(recipientUser) diff --git a/dlt-database/entity/0003-refactor_transaction_recipe/BackendTransaction.ts b/dlt-database/entity/0003-refactor_transaction_recipe/BackendTransaction.ts new file mode 100644 index 000000000..12b9c83cf --- /dev/null +++ b/dlt-database/entity/0003-refactor_transaction_recipe/BackendTransaction.ts @@ -0,0 +1,45 @@ +import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToOne, JoinColumn } from 'typeorm' +import { Decimal } from 'decimal.js-light' + +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { Transaction } from '../Transaction' + +@Entity('backend_transactions') +export class BackendTransaction extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true, type: 'bigint' }) + id: number + + @Column({ name: 'backend_transaction_id', type: 'bigint', unsigned: true, nullable: true }) + backendTransactionId?: number + + @ManyToOne(() => Transaction, (transaction) => transaction.backendTransactions) + @JoinColumn({ name: 'transaction_id' }) + transaction?: Transaction + + @Column({ name: 'transaction_id', type: 'bigint', unsigned: true, nullable: true }) + transactionId?: number + + @Column({ name: 'type_id', unsigned: true, nullable: false }) + typeId: number + + // account balance based on creation date + @Column({ + name: 'balance', + type: 'decimal', + precision: 40, + scale: 20, + nullable: true, + transformer: DecimalTransformer, + }) + balance?: Decimal + + @Column({ name: 'created_at', type: 'datetime', precision: 3 }) + createdAt: Date + + // use timestamp from iota milestone which is only in seconds precision, so no need to use 3 Bytes extra here + @Column({ name: 'confirmed_at', type: 'datetime', nullable: true }) + confirmedAt?: Date + + @Column({ name: 'verifiedOnBackend', type: 'tinyint', default: false }) + verifiedOnBackend: boolean +} diff --git a/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts b/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts index 3c4e7959c..922bf81cd 100644 --- a/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts +++ b/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts @@ -6,12 +6,14 @@ import { OneToOne, JoinColumn, BaseEntity, + OneToMany, } from 'typeorm' import { Decimal } from 'decimal.js-light' import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' import { Account } from '../Account' import { Community } from '../Community' +import { BackendTransaction } from '../BackendTransaction' @Entity('transactions') export class Transaction extends BaseEntity { @@ -21,9 +23,6 @@ export class Transaction extends BaseEntity { @Column({ name: 'iota_message_id', type: 'binary', length: 32, nullable: true }) iotaMessageId?: Buffer - @Column({ name: 'backend_transaction_id', type: 'bigint', unsigned: true, nullable: true }) - backendTransactionId?: number - @OneToOne(() => Transaction) // eslint-disable-next-line no-use-before-define paringTransaction?: Transaction @@ -120,4 +119,10 @@ export class Transaction extends BaseEntity { // use timestamp from iota milestone which is only in seconds precision, so no need to use 3 Bytes extra here @Column({ name: 'confirmed_at', type: 'datetime', nullable: true }) confirmedAt?: Date + + @OneToMany(() => BackendTransaction, (backendTransaction) => backendTransaction.transaction, { + cascade: ['insert', 'update'], + }) + @JoinColumn({ name: 'transaction_id' }) + backendTransactions: BackendTransaction[] } diff --git a/dlt-database/entity/BackendTransaction.ts b/dlt-database/entity/BackendTransaction.ts new file mode 100644 index 000000000..6ec68427d --- /dev/null +++ b/dlt-database/entity/BackendTransaction.ts @@ -0,0 +1 @@ +export { BackendTransaction } from './0003-refactor_transaction_recipe/BackendTransaction' diff --git a/dlt-database/entity/index.ts b/dlt-database/entity/index.ts index ba7ea2663..b1215263d 100644 --- a/dlt-database/entity/index.ts +++ b/dlt-database/entity/index.ts @@ -1,5 +1,6 @@ import { Account } from './Account' import { AccountCommunity } from './AccountCommunity' +import { BackendTransaction } from './BackendTransaction' import { Community } from './Community' import { InvalidTransaction } from './InvalidTransaction' import { Migration } from './Migration' @@ -9,6 +10,7 @@ import { User } from './User' export const entities = [ AccountCommunity, Account, + BackendTransaction, Community, InvalidTransaction, Migration, diff --git a/dlt-database/migrations/0003-refactor_transaction_recipe.ts b/dlt-database/migrations/0003-refactor_transaction_recipe.ts index cea8c3cd3..a3e8265e7 100644 --- a/dlt-database/migrations/0003-refactor_transaction_recipe.ts +++ b/dlt-database/migrations/0003-refactor_transaction_recipe.ts @@ -34,7 +34,6 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis `CREATE TABLE \`transactions\` ( \`id\` bigint unsigned NOT NULL AUTO_INCREMENT, \`iota_message_id\` varbinary(32) NULL DEFAULT NULL, - \`backend_transaction_id\` bigint unsigned NULL DEFAULT NULL, \`paring_transaction_id\` bigint unsigned NULL DEFAULT NULL, \`signing_account_id\` int unsigned NULL DEFAULT NULL, \`recipient_account_id\` int unsigned NULL DEFAULT NULL, @@ -62,6 +61,22 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis `, ) + await queryFn( + `CREATE TABLE \`backend_transactions\` ( + \`id\` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, + \`backend_transaction_id\` BIGINT UNSIGNED NOT NULL, + \`transaction_id\` BIGINT UNSIGNED NULL DEFAULT NULL, + \`type_id\` INT UNSIGNED NOT NULL, + \`balance\` DECIMAL(40, 20) NULL DEFAULT NULL, + \`created_at\` DATETIME(3) NOT NULL, + \`confirmed_at\` DATETIME NULL DEFAULT NULL, + \`verifiedOnBackend\` TINYINT NOT NULL DEFAULT 0, + PRIMARY KEY (\`id\`), + FOREIGN KEY (\`transaction_id\`) REFERENCES transactions(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `, + ) + await queryFn(`ALTER TABLE \`communities\` ADD UNIQUE(\`iota_topic\`);`) await queryFn(`ALTER TABLE \`users\` CHANGE \`created_at\` \`created_at\` DATETIME(3) NOT NULL;`) @@ -126,6 +141,7 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom await queryFn(`ALTER TABLE \`invalid_transactions\` DROP INDEX \`iota_message_id\`;`) await queryFn(`ALTER TABLE \`invalid_transactions\` ADD INDEX(\`iota_message_id\`); `) await queryFn(`DROP TABLE \`transactions\`;`) + await queryFn(`DROP TABLE \`backend_transactions\`;`) await queryFn( `ALTER TABLE \`users\` CHANGE \`created_at\` \`created_at\` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3);`,