From 926ca9c209480cc4ea4b4f4f76c62471fdf7f415 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 26 Jan 2023 15:23:27 +0100 Subject: [PATCH 01/99] fix cypress config --- e2e-tests/cypress/tests/cypress.config.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/e2e-tests/cypress/tests/cypress.config.ts b/e2e-tests/cypress/tests/cypress.config.ts index 9621b7a00..a9627c5ae 100644 --- a/e2e-tests/cypress/tests/cypress.config.ts +++ b/e2e-tests/cypress/tests/cypress.config.ts @@ -2,6 +2,8 @@ import { defineConfig } from "cypress"; import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; import browserify from "@badeball/cypress-cucumber-preprocessor/browserify"; +let resetPasswordLink: string; + async function setupNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions @@ -15,6 +17,15 @@ async function setupNodeEvents( }) ); + on("task", { + setResetPasswordLink: (val) => { + return (resetPasswordLink = val); + }, + getResetPasswordLink: () => { + return resetPasswordLink; + }, + }); + on("after:run", (results) => { if (results) { // results will be undefined in interactive mode @@ -30,6 +41,7 @@ export default defineConfig({ e2e: { specPattern: "**/*.feature", excludeSpecPattern: "*.js", + experimentalSessionAndOrigin: true, baseUrl: "http://localhost:3000", chromeWebSecurity: false, defaultCommandTimeout: 10000, @@ -43,6 +55,7 @@ export default defineConfig({ }, env: { backendURL: "http://localhost:4000", + mailserverURL: "http://localhost:1080", loginQuery: `query ($email: String!, $password: String!, $publisherId: Int) { login(email: $email, password: $password, publisherId: $publisherId) { email From 58513fa29905020ca71782ca5a655ce71227af14 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 26 Jan 2023 15:27:09 +0100 Subject: [PATCH 02/99] adapt step definitions for new user story --- .../support/step_definitions/common_steps.ts | 15 +----- .../support/step_definitions/email_steps.ts | 45 ++++++++++++++++ .../user_authentication_steps.ts | 54 ++++++++++++++++++- 3 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 e2e-tests/cypress/tests/cypress/support/step_definitions/email_steps.ts diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts index f45358f3c..42142380b 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts @@ -1,5 +1,4 @@ import { Given, Then, When } from "@badeball/cypress-cucumber-preprocessor"; -import { LoginPage } from "../../e2e/models/LoginPage"; import { OverviewPage } from "../../e2e/models/OverviewPage"; import { SideNavMenu } from "../../e2e/models/SideNavMenu"; import { Toasts } from "../../e2e/models/Toasts"; @@ -8,7 +7,7 @@ Given("the browser navigates to page {string}", (page: string) => { cy.visit(page); }); -// login-related +// login related Given( "the user is logged in as {string} {string}", @@ -32,18 +31,6 @@ Then("the user cannot login", () => { }); }); -// - -When( - "the user submits the credentials {string} {string}", - (email: string, password: string) => { - const loginPage = new LoginPage(); - loginPage.enterEmail(email); - loginPage.enterPassword(password); - loginPage.submitLogin(); - } -); - // logout Then("the user logs out", () => { diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/email_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/email_steps.ts new file mode 100644 index 000000000..4044c3717 --- /dev/null +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/email_steps.ts @@ -0,0 +1,45 @@ +import { Then, When } from "@badeball/cypress-cucumber-preprocessor"; +import { ResetPasswordPage } from "../../e2e/models/ResetPasswordPage"; +import { UserEMailSite } from "../../e2e/models/UserEMailSite"; + +const userEMailSite = new UserEMailSite(); +const resetPasswordPage = new ResetPasswordPage(); + +Then("the user receives an e-mail containing the password reset link", () => { + cy.origin( + Cypress.env("mailserverURL"), + { args: userEMailSite }, + (userEMailSite) => { + const linkPattern = /\/reset-password\/[0-9]+\d/; + + cy.visit("/"); // navigate to user's e-maile site (on fake mail server) + cy.get(userEMailSite.emailInbox).should("be.visible"); + + cy.get(userEMailSite.emailList) + .find(".email-item") + .filter(":contains(asswor)") + .first() + .click(); + + cy.get(userEMailSite.emailMeta) + .find(userEMailSite.emailSubject) + .contains("asswor"); + + cy.get(".email-content") + .find(".plain-text") + .contains(linkPattern) + .invoke("text") + .then((text) => { + const resetPasswordLink = text.match(linkPattern)[0]; + cy.task("setResetPasswordLink", resetPasswordLink); + }); + } + ); +}); + +When("the user opens the password reset link in the browser", () => { + cy.task("getResetPasswordLink").then((passwordResetLink) => { + cy.visit(passwordResetLink); + }); + cy.get(resetPasswordPage.newPasswordRepeatBlock).should("be.visible"); +}); diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index 1e5cfe88c..67a64eba0 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -1,7 +1,57 @@ -import { When } from "@badeball/cypress-cucumber-preprocessor"; +import { When, And } from "@badeball/cypress-cucumber-preprocessor"; +import { ForgotPasswordPage } from "../../e2e/models/ForgotPasswordPage"; import { LoginPage } from "../../e2e/models/LoginPage"; +import { ResetPasswordPage } from "../../e2e/models/ResetPasswordPage"; + +const loginPage = new LoginPage(); +const forgotPasswordPage = new ForgotPasswordPage(); +const resetPasswordPage = new ResetPasswordPage(); + +// login related When("the user submits no credentials", () => { - const loginPage = new LoginPage(); loginPage.submitLogin(); }); + +When( + "the user submits the credentials {string} {string}", + (email: string, password: string) => { + loginPage.enterEmail(email); + loginPage.enterPassword(password); + loginPage.submitLogin(); + } +); + +// password reset related + +And("the user navigates to the forgot password page", () => { + loginPage.openForgotPasswordPage(); + cy.url().should("include", "/forgot-password"); +}); + +When("the user enters the e-mail address {string}", (email: string) => { + forgotPasswordPage.enterEmail(email); +}); + +And("the user submits the e-mail form", () => { + forgotPasswordPage.submitEmail(); + cy.get(forgotPasswordPage.successComponent).should("be.visible"); +}); + +And("the user enters the password {string}", (password: string) => { + resetPasswordPage.enterNewPassword(password); +}); + +And("the user repeats the password {string}", (password: string) => { + resetPasswordPage.repeatNewPassword(password); +}); + +And("the user submits the new password", () => { + resetPasswordPage.submitNewPassword(); + cy.get(resetPasswordPage.resetPasswordMessageBlock).should("be.visible"); +}); + +And("the user clicks the sign in button", () => { + resetPasswordPage.openSigninPage(); + cy.url().should("contain", "/login"); +}); From 0812beb0502171905694f2904d2ea51077cc4a93 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 26 Jan 2023 16:59:39 +0100 Subject: [PATCH 03/99] set data-test attribute in login page for e2e testing --- e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts | 5 +++++ frontend/src/pages/Login.vue | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts b/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts index 9a0df62ee..df91e8e14 100644 --- a/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts +++ b/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts @@ -4,6 +4,7 @@ export class LoginPage { // selectors emailInput = "input[type=email]"; passwordInput = "input[type=password]"; + forgotPasswordLink = '[data-test="forgot-password-link"]'; submitBtn = "[type=submit]"; emailHint = "#vee_Email"; passwordHint = "#vee_Password"; @@ -27,4 +28,8 @@ export class LoginPage { cy.get(this.submitBtn).click(); return this; } + + openForgotPasswordPage() { + cy.get(this.forgotPasswordLink).click(); + } } diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index bd07af3ef..f3f27f83e 100755 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -24,7 +24,7 @@ - + {{ $t('settings.password.forgot_pwd') }} From 3ae58ca6a94477567a940503b0dc22a3b55a02e5 Mon Sep 17 00:00:00 2001 From: mahula Date: Mon, 30 Jan 2023 18:13:29 +0100 Subject: [PATCH 04/99] add data-test attributes to vue pages for e2e testing --- frontend/src/pages/ForgotPassword.vue | 14 ++++++++++++-- frontend/src/pages/Login.vue | 2 +- frontend/src/pages/ResetPassword.vue | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/ForgotPassword.vue b/frontend/src/pages/ForgotPassword.vue index 77c2ac926..70d578e10 100644 --- a/frontend/src/pages/ForgotPassword.vue +++ b/frontend/src/pages/ForgotPassword.vue @@ -24,10 +24,20 @@ + diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index f3f27f83e..6abb6d6b6 100755 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -24,7 +24,7 @@ - + {{ $t('settings.password.forgot_pwd') }} diff --git a/frontend/src/pages/ResetPassword.vue b/frontend/src/pages/ResetPassword.vue index ec7ae6811..0d79fcf0d 100644 --- a/frontend/src/pages/ResetPassword.vue +++ b/frontend/src/pages/ResetPassword.vue @@ -5,7 +5,7 @@
- + {{ $t(displaySetup.button) }} From c23327013f14950a38df05520c451e35b1e36d1f Mon Sep 17 00:00:00 2001 From: mahula Date: Mon, 30 Jan 2023 18:17:22 +0100 Subject: [PATCH 05/99] add page object files for e2e testing "forgot password" --- .../cypress/e2e/models/ForgotPasswordPage.ts | 18 +++++++++++ .../cypress/e2e/models/ResetPasswordPage.ts | 32 +++++++++++++++++++ .../tests/cypress/e2e/models/UserEMailSite.ts | 17 ++++++++++ 3 files changed, 67 insertions(+) create mode 100644 e2e-tests/cypress/tests/cypress/e2e/models/ForgotPasswordPage.ts create mode 100644 e2e-tests/cypress/tests/cypress/e2e/models/ResetPasswordPage.ts create mode 100644 e2e-tests/cypress/tests/cypress/e2e/models/UserEMailSite.ts diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/ForgotPasswordPage.ts b/e2e-tests/cypress/tests/cypress/e2e/models/ForgotPasswordPage.ts new file mode 100644 index 000000000..295bb1fda --- /dev/null +++ b/e2e-tests/cypress/tests/cypress/e2e/models/ForgotPasswordPage.ts @@ -0,0 +1,18 @@ +/// + +export class ForgotPasswordPage { + // selectors + emailInput = "input[type=email]"; + submitBtn = "button[type=submit]"; + successComponent = "[data-test='forgot-password-success']"; + + enterEmail(email: string) { + cy.get(this.emailInput).clear().type(email); + return this; + } + + submitEmail() { + cy.get(this.submitBtn).click(); + return this; + } +} diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/ResetPasswordPage.ts b/e2e-tests/cypress/tests/cypress/e2e/models/ResetPasswordPage.ts new file mode 100644 index 000000000..4f01d73b0 --- /dev/null +++ b/e2e-tests/cypress/tests/cypress/e2e/models/ResetPasswordPage.ts @@ -0,0 +1,32 @@ +/// + +export class ResetPasswordPage { + // selectors + newPasswordBlock = "#new-password-input-field"; + newPasswordRepeatBlock = "#repeat-new-password-input-field"; + resetPasswordBtn = "button[type=submit]"; + resetPasswordMessageBlock = '[data-test="reset-password-message"]'; + signinBtn = ".btn.test-message-button"; + + enterNewPassword(password: string) { + cy.get(this.newPasswordBlock).find("input[type=password]").type(password); + return this; + } + + repeatNewPassword(password: string) { + cy.get(this.newPasswordRepeatBlock) + .find("input[type=password]") + .type(password); + return this; + } + + submitNewPassword() { + cy.get(this.resetPasswordBtn).click(); + return this; + } + + openSigninPage() { + cy.get(this.signinBtn).click(); + return this; + } +} diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/UserEMailSite.ts b/e2e-tests/cypress/tests/cypress/e2e/models/UserEMailSite.ts new file mode 100644 index 000000000..02207827d --- /dev/null +++ b/e2e-tests/cypress/tests/cypress/e2e/models/UserEMailSite.ts @@ -0,0 +1,17 @@ +/// + +export class UserEMailSite { + // selectors + emailInbox = ".sidebar-emails-container"; + emailList = ".email-list"; + emailMeta = ".email-meta"; + emailSubject = ".subject"; + + openRecentPasswordResetEMail() { + cy.get(this.emailList) + .find("email-item") + .filter(":contains(asswor)") + .click(); + expect(cy.get(this.emailSubject)).to("contain", "asswor"); + } +} From 2cf1dcbe83cb6efdfe3344dc6bd7660fea04f363 Mon Sep 17 00:00:00 2001 From: mahula Date: Mon, 30 Jan 2023 18:18:36 +0100 Subject: [PATCH 06/99] add feature file for e2e testing "forgot password" --- .../User.Authentication.ResetPassword.feature | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature diff --git a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature new file mode 100644 index 000000000..7722e636e --- /dev/null +++ b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature @@ -0,0 +1,25 @@ +Feature: User Authentication - reset password + As a user + I want the option to reset my password from the sign in page + + # TODO for these pre-conditions utilize seeding or API check, if user exists in test system + # Background: + # Given the following "users" are in the database: + # | email | password | name | + # | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg | + + Scenario: Reset password from signin page successfully + Given the browser navigates to page "/login" + And the user navigates to the forgot password page + When the user enters the e-mail address "bibi@bloxberg.de" + And the user submits the e-mail form + Then the user receives an e-mail containing the password reset link + When the user opens the password reset link in the browser + And the user enters the password "12345Aa_" + And the user repeats the password "12345Aa_" + And the user submits the password form + And the user clicks the sign in button + Then the user submits the credentials "bibi@bloxberg.de" "Aa12345_" + And the user cannot login + But the user submits the credentials "bibi@bloxberg.de" "12345Aa_" + And the user is logged in with username "Bibi Bloxberg" From 2d82043ffd14c3563ddd81d253adac843b0f1010 Mon Sep 17 00:00:00 2001 From: mahula Date: Mon, 30 Jan 2023 22:20:45 +0100 Subject: [PATCH 07/99] add env variables for backend to e2e workflow job --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da8521a76..ce523a26c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,9 @@ name: gradido test CI -on: push +on: + push: + branches: + - 2352-feat-user-story-user-authentication-reset-password jobs: ############################################################################## @@ -621,7 +624,7 @@ jobs: run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: Boot up test system | docker-compose backend - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps -e EMAIL='true' -e -e EMAIL_TEST_MODUS='false' -e EMAIL_TLS='false' -e EMAIL_CODE_REQUEST_TIME=1 backend - name: Sleep for 10 seconds run: sleep 10s From 7604522810f29ffb384613886ab6123bb138f978 Mon Sep 17 00:00:00 2001 From: mahula Date: Mon, 30 Jan 2023 23:00:25 +0100 Subject: [PATCH 08/99] add env variables for backend to e2e workflow job --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce523a26c..785fdbcc7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -624,7 +624,7 @@ jobs: run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: Boot up test system | docker-compose backend - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps -e EMAIL='true' -e -e EMAIL_TEST_MODUS='false' -e EMAIL_TLS='false' -e EMAIL_CODE_REQUEST_TIME=1 backend + run: EMAIL=true EMAIL_TEST_MODUS=false EMAIL_TLS=false EMAIL_CODE_REQUEST_TIME=1 docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend - name: Sleep for 10 seconds run: sleep 10s From b862de07245de39650f6a26db53106429723b2a0 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 11:09:23 +0100 Subject: [PATCH 09/99] Revert "add env variables for backend to e2e workflow job" This reverts commit 7604522810f29ffb384613886ab6123bb138f978. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 785fdbcc7..ce523a26c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -624,7 +624,7 @@ jobs: run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: Boot up test system | docker-compose backend - run: EMAIL=true EMAIL_TEST_MODUS=false EMAIL_TLS=false EMAIL_CODE_REQUEST_TIME=1 docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps -e EMAIL='true' -e -e EMAIL_TEST_MODUS='false' -e EMAIL_TLS='false' -e EMAIL_CODE_REQUEST_TIME=1 backend - name: Sleep for 10 seconds run: sleep 10s From 31ae98bcbad193debcfccf0b1d4bc7d3444031b1 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 11:11:02 +0100 Subject: [PATCH 10/99] linting --- frontend/src/pages/ResetPassword.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ResetPassword.vue b/frontend/src/pages/ResetPassword.vue index 0d79fcf0d..5c791ac61 100644 --- a/frontend/src/pages/ResetPassword.vue +++ b/frontend/src/pages/ResetPassword.vue @@ -5,7 +5,12 @@
- + {{ $t(displaySetup.button) }} From 08d8c008f6bd66c3c726785ba13e8125b9973638 Mon Sep 17 00:00:00 2001 From: elweyn Date: Tue, 31 Jan 2023 13:07:41 +0100 Subject: [PATCH 11/99] Add denyContribution mutation to the seeds. --- backend/src/seeds/graphql/mutations.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 2b4ed6656..8c3f97f17 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -266,6 +266,12 @@ export const deleteContribution = gql` } ` +export const denyContribution = gql` + mutation ($id: Int!) { + denyContribution(id: $id) + } +` + export const createContributionMessage = gql` mutation ($contributionId: Float!, $message: String!) { createContributionMessage(contributionId: $contributionId, message: $message) { From f3f749889f08aa148b4105f465b83e417c199e3e Mon Sep 17 00:00:00 2001 From: elweyn Date: Tue, 31 Jan 2023 13:24:59 +0100 Subject: [PATCH 12/99] Add Test for denyContribution. --- .../resolver/ContributionResolver.test.ts | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index abae8e446..f17851fd8 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -10,6 +10,7 @@ import { createContribution, updateContribution, deleteContribution, + denyContribution, confirmContribution, adminCreateContribution, adminCreateContributions, @@ -671,6 +672,123 @@ describe('ContributionResolver', () => { }) }) + describe('denyContribution', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: 1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + await userFactory(testEnv, peterLustig) + await userFactory(testEnv, bibiBloxberg) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + result = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + describe('wrong contribution id', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: -1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('Contribution not found for given id: -1') + }) + }) + + describe('wrong user tries to deny the contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + it('throws an error', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: result.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('valid input', () => { + it('deny contribution', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: result.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + denyContribution: true, + }, + }), + ) + }) + }) + }) + }) + describe('listAllContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { From 4747c832090019b2a6db95ee85d44aa70851ad34 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 13:35:59 +0100 Subject: [PATCH 13/99] add dot-env file for testing with emails to backend --- backend/.env.test_email | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 backend/.env.test_email diff --git a/backend/.env.test_email b/backend/.env.test_email new file mode 100644 index 000000000..fba34833a --- /dev/null +++ b/backend/.env.test_email @@ -0,0 +1,5 @@ +EMAIL=true +EMAIL_TEST_MODUS=false +EMAIL_TLS=false +# for testing password reset +EMAIL_CODE_REQUEST_TIME=1 From ac2d44e2e6201947a66a3b229838096c544d8e4b Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 14:08:40 +0100 Subject: [PATCH 14/99] add to github workflow: - using .env file to set email related values when starting backend container - add fake email server to end-to-end testing job --- .github/workflows/test.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce523a26c..700949322 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -624,10 +624,13 @@ jobs: run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: Boot up test system | docker-compose backend - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps -e EMAIL='true' -e -e EMAIL_TEST_MODUS='false' -e EMAIL_TLS='false' -e EMAIL_CODE_REQUEST_TIME=1 backend + run: | + docker run -it --rm gradido/backend:test /bin/sh + cp .env.test_email .env && exit + docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend - - name: Sleep for 10 seconds - run: sleep 10s + - name: Boot up test system | docker-compose mailserver + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver - name: Boot up test system | seed backend run: | From 67717811cbbbe94cb803428a6040a9bb799b3470 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 14:08:40 +0100 Subject: [PATCH 15/99] add to github workflow: - using .env file to set email related values when starting backend container - add fake email server to end-to-end testing job --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 700949322..2c30864d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -625,7 +625,7 @@ jobs: - name: Boot up test system | docker-compose backend run: | - docker run -it --rm gradido/backend:test /bin/sh + docker run -i --rm gradido/backend:test /bin/sh cp .env.test_email .env && exit docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend From 15b5e83edbd8d3fe8b1d771472d5fb61f27912a4 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 14:08:40 +0100 Subject: [PATCH 16/99] add to github workflow: - using .env file to set email related values when starting backend container - add fake email server to end-to-end testing job --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c30864d4..5a1f64187 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -626,6 +626,7 @@ jobs: - name: Boot up test system | docker-compose backend run: | docker run -i --rm gradido/backend:test /bin/sh + ls -la cp .env.test_email .env && exit docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend From be70f9127fbc491304b34a39e44c33fee7a43119 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 14:08:40 +0100 Subject: [PATCH 17/99] add to github workflow: - using .env file to set email related values when starting backend container - add fake email server to end-to-end testing job --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a1f64187..85da091e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -626,7 +626,7 @@ jobs: - name: Boot up test system | docker-compose backend run: | docker run -i --rm gradido/backend:test /bin/sh - ls -la + cd backend cp .env.test_email .env && exit docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend From 34cf9548f7a3ce46ce2421fc2d224a5b12f29637 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 15:56:58 +0100 Subject: [PATCH 18/99] let e2e tests run in parallel jobs --- .github/workflows/test.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85da091e1..7957601c1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -562,6 +562,12 @@ jobs: name: End-to-End Tests runs-on: ubuntu-latest needs: [build_test_mariadb, build_test_database_up, build_test_backend, build_test_admin, build_test_frontend, build_test_nginx] + env: + jobs: 2 + strategy: + matrix: + # run copies of the current job in parallel + job: [1, 2] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -656,7 +662,7 @@ jobs: run: | cd e2e-tests/cypress/tests/ yarn - yarn run cypress run --spec cypress/e2e/User.Authentication.feature + yarn run cypress run --spec cypress/e2e/User.Authentication.feature,User.Authentication.ResetPassword.feature - name: End-to-end tests | if tests failed, upload screenshots if: steps.e2e-tests.outcome == 'failure' uses: actions/upload-artifact@v3 From e87401ef4561d57cd898ce788c5aacf17b5ae225 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 16:21:16 +0100 Subject: [PATCH 19/99] fix screenshot upload after failing tests --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7957601c1..78271a6f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -664,7 +664,7 @@ jobs: yarn yarn run cypress run --spec cypress/e2e/User.Authentication.feature,User.Authentication.ResetPassword.feature - name: End-to-end tests | if tests failed, upload screenshots - if: steps.e2e-tests.outcome == 'failure' + if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} uses: actions/upload-artifact@v3 with: name: cypress-screenshots From b6646d1980f26fb4fbc8414d6b92b902eda1b9fc Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 17:01:58 +0100 Subject: [PATCH 20/99] run e2e tests serial for now --- .github/workflows/test.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78271a6f3..021aacc9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -562,12 +562,6 @@ jobs: name: End-to-End Tests runs-on: ubuntu-latest needs: [build_test_mariadb, build_test_database_up, build_test_backend, build_test_admin, build_test_frontend, build_test_nginx] - env: - jobs: 2 - strategy: - matrix: - # run copies of the current job in parallel - job: [1, 2] steps: ########################################################################## # CHECKOUT CODE ########################################################## From 89bff2efbf3bbce153d48099ed858f8b717bd290 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 17:16:13 +0100 Subject: [PATCH 21/99] fix typo --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 021aacc9b..e6e2e58af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -656,7 +656,7 @@ jobs: run: | cd e2e-tests/cypress/tests/ yarn - yarn run cypress run --spec cypress/e2e/User.Authentication.feature,User.Authentication.ResetPassword.feature + yarn run cypress run --spec cypress/e2e/User.Authentication.feature,cypress/e2e/User.Authentication.ResetPassword.feature - name: End-to-end tests | if tests failed, upload screenshots if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} uses: actions/upload-artifact@v3 From 49ccdb52c0eabb56890b3c1aa7ed12255bf8a21c Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 31 Jan 2023 17:20:25 +0100 Subject: [PATCH 22/99] reintroduce a sleep before seeding the backend in e2e testing --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6e2e58af..60b2b9ac7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -633,6 +633,9 @@ jobs: - name: Boot up test system | docker-compose mailserver run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver + - name: Sleep for 5 seconds + run: sleep 5s + - name: Boot up test system | seed backend run: | sudo chown runner:docker -R * From 147035bf5d7917f6ac13a228fee6546aa42a6a0f Mon Sep 17 00:00:00 2001 From: ogerly Date: Wed, 1 Feb 2023 10:49:08 +0100 Subject: [PATCH 23/99] logo inserted with better quality. --- frontend/public/img/brand/gradido-logo.png | Bin 0 -> 51410 bytes frontend/src/components/Auth/AuthNavbar.vue | 13 +++++++------ frontend/src/components/Menu/Navbar.vue | 8 ++++---- frontend/src/pages/TransactionLink.vue | 1 - 4 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 frontend/public/img/brand/gradido-logo.png diff --git a/frontend/public/img/brand/gradido-logo.png b/frontend/public/img/brand/gradido-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..39b4a66366141be55aec64e2c0d01c1c1e008320 GIT binary patch literal 51410 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

r}pp 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�t7TT*$VGv*nqAkt8apw|7e9X= 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 P00000NkvXXu0mjfv$x35 literal 0 HcmV?d00001 diff --git a/frontend/src/components/Auth/AuthNavbar.vue b/frontend/src/components/Auth/AuthNavbar.vue index 7d8c6f08a..f7a8b6f0a 100644 --- a/frontend/src/components/Auth/AuthNavbar.vue +++ b/frontend/src/components/Auth/AuthNavbar.vue @@ -3,16 +3,16 @@ @@ -35,7 +35,8 @@ export default { mixins: [authLinks], data() { return { - logo: '/img/brand/green.png', + background_header: '/img/template/gradido_background_header.png', + logo: '/img/brand/gradido-logo.png', sheet: '/img/template/Blaetter.png', } }, diff --git a/frontend/src/components/Menu/Navbar.vue b/frontend/src/components/Menu/Navbar.vue index 03217a5ae..73470a91b 100644 --- a/frontend/src/components/Menu/Navbar.vue +++ b/frontend/src/components/Menu/Navbar.vue @@ -4,10 +4,10 @@

@@ -60,7 +60,7 @@ export default { }, data() { return { - logo: '/img/brand/green.png', + logo: '/img/brand/gradido-logo.png', sheet: '/img/template/Blaetter.png', } }, diff --git a/frontend/src/pages/TransactionLink.vue b/frontend/src/pages/TransactionLink.vue index c3875d20e..17bd98031 100644 --- a/frontend/src/pages/TransactionLink.vue +++ b/frontend/src/pages/TransactionLink.vue @@ -45,7 +45,6 @@ export default { }, data() { return { - img: '/img/brand/green.png', linkData: { __typename: 'TransactionLink', amount: '123.45', From 91933f6f0dfb4c6b0f7a26a9f0216273d4699b7d Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 12:19:32 +0100 Subject: [PATCH 24/99] add video recodring and upload for testing purposes --- .github/workflows/test.yml | 2 ++ e2e-tests/cypress/tests/cypress.config.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 60b2b9ac7..ef0091c43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -666,3 +666,5 @@ jobs: with: name: cypress-screenshots path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/screenshots/ + name: cypress-videos + path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/videos/ diff --git a/e2e-tests/cypress/tests/cypress.config.ts b/e2e-tests/cypress/tests/cypress.config.ts index a9627c5ae..6bff1a169 100644 --- a/e2e-tests/cypress/tests/cypress.config.ts +++ b/e2e-tests/cypress/tests/cypress.config.ts @@ -48,7 +48,7 @@ export default defineConfig({ supportFile: "cypress/support/index.ts", viewportHeight: 720, viewportWidth: 1280, - video: false, + video: true, retries: { runMode: 2, openMode: 0, From 4e988167527c3aa4b3ae5e8d43f8993d2741f9b2 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 12:19:32 +0100 Subject: [PATCH 25/99] add video recodring and upload for testing purposes --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef0091c43..9bffd0cc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -666,5 +666,9 @@ jobs: with: name: cypress-screenshots path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/screenshots/ + - name: End-to-end tests | if tests failed, upload videos + if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} + uses: actions/upload-artifact@v3 + with: name: cypress-videos path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/videos/ From bc12958e3ed42adc6da38e6f8cf5a2f9bbc906fc Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 14:24:06 +0100 Subject: [PATCH 26/99] Revert "add video recodring and upload for testing purposes" This reverts commit 4e988167527c3aa4b3ae5e8d43f8993d2741f9b2. --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9bffd0cc2..ef0091c43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -666,9 +666,5 @@ jobs: with: name: cypress-screenshots path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/screenshots/ - - name: End-to-end tests | if tests failed, upload videos - if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} - uses: actions/upload-artifact@v3 - with: name: cypress-videos path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/videos/ From 4dfad4086da72fbe5b397efdbfae29b0f7af4c41 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 14:25:08 +0100 Subject: [PATCH 27/99] add response check to submit login e2e test step --- .../step_definitions/user_authentication_steps.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index 67a64eba0..bc637fdbb 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -16,9 +16,19 @@ When("the user submits no credentials", () => { When( "the user submits the credentials {string} {string}", (email: string, password: string) => { + cy.intercept("POST", "/graphql").as("login"); + // cy.intercept('POST', '/graphql', (req) => { + // if (req.body.hasOwnProperty('query') && req.body.query.includes('mutation')) { + // req.alias = 'login' + // } + // }); + loginPage.enterEmail(email); loginPage.enterPassword(password); loginPage.submitLogin(); + cy.wait("@login").then((interception) => { + expect(JSON.stringify(interception.response.body)).contains(email); + }); } ); From aeb2ce35d59f951652d5660c6310382e2d101073 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 14:25:08 +0100 Subject: [PATCH 28/99] add response check to submit login e2e test step --- .github/workflows/test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef0091c43..60b2b9ac7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -666,5 +666,3 @@ jobs: with: name: cypress-screenshots path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/screenshots/ - name: cypress-videos - path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/videos/ From b91bf38f1bdcf481a6c2422cbb771ba0cbcad3c3 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 14:25:08 +0100 Subject: [PATCH 29/99] add response check to submit login e2e test step --- .../step_definitions/user_authentication_steps.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index bc637fdbb..d469180b3 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -16,17 +16,20 @@ When("the user submits no credentials", () => { When( "the user submits the credentials {string} {string}", (email: string, password: string) => { - cy.intercept("POST", "/graphql").as("login"); - // cy.intercept('POST', '/graphql', (req) => { - // if (req.body.hasOwnProperty('query') && req.body.query.includes('mutation')) { - // req.alias = 'login' - // } - // }); + cy.intercept("POST", "/graphql", (req) => { + if ( + req.body.hasOwnProperty("query") && + req.body.query.includes("mutation") + ) { + req.alias = "login"; + } + }); loginPage.enterEmail(email); loginPage.enterPassword(password); loginPage.submitLogin(); cy.wait("@login").then((interception) => { + expect(interception.response.statusCode).equals(200); expect(JSON.stringify(interception.response.body)).contains(email); }); } From a28a46cf08eb421aa7709b1b6f9673fba9239ad0 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 15:43:26 +0100 Subject: [PATCH 30/99] disable video recording in e2e tests --- e2e-tests/cypress/tests/cypress.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/cypress/tests/cypress.config.ts b/e2e-tests/cypress/tests/cypress.config.ts index 6bff1a169..a9627c5ae 100644 --- a/e2e-tests/cypress/tests/cypress.config.ts +++ b/e2e-tests/cypress/tests/cypress.config.ts @@ -48,7 +48,7 @@ export default defineConfig({ supportFile: "cypress/support/index.ts", viewportHeight: 720, viewportWidth: 1280, - video: true, + video: false, retries: { runMode: 2, openMode: 0, From 306c039b4ddd7ba22e2ab8eb13fd301ffba55d1a Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 15:46:13 +0100 Subject: [PATCH 31/99] comment workflow jobs for e2e testing --- .github/workflows/test.yml | 619 ++++++++++++++++++------------------- 1 file changed, 306 insertions(+), 313 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 60b2b9ac7..f4b9dfda9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -192,345 +192,345 @@ jobs: ############################################################################## # JOB: LINT FRONTEND ######################################################### ############################################################################## - lint_frontend: - name: Lint - Frontend - runs-on: ubuntu-latest - needs: [build_test_frontend] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## - # LINT FRONTEND ########################################################## - ########################################################################## - - name: Frontend | Lint - run: docker run --rm gradido/frontend:test yarn run lint + # lint_frontend: + # name: Lint - Frontend + # runs-on: ubuntu-latest + # needs: [build_test_frontend] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Frontend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-frontend-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/frontend.tar + # ########################################################################## + # # LINT FRONTEND ########################################################## + # ########################################################################## + # - name: Frontend | Lint + # run: docker run --rm gradido/frontend:test yarn run lint ############################################################################## # JOB: STYLELINT FRONTEND #################################################### ############################################################################## - stylelint_frontend: - name: Stylelint - Frontend - runs-on: ubuntu-latest - needs: [build_test_frontend] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## - # STYLELINT FRONTEND ##################################################### - ########################################################################## - - name: Frontend | Stylelint - run: docker run --rm gradido/frontend:test yarn run stylelint + # stylelint_frontend: + # name: Stylelint - Frontend + # runs-on: ubuntu-latest + # needs: [build_test_frontend] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Frontend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-frontend-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/frontend.tar + # ########################################################################## + # # STYLELINT FRONTEND ##################################################### + # ########################################################################## + # - name: Frontend | Stylelint + # run: docker run --rm gradido/frontend:test yarn run stylelint ############################################################################## # JOB: LINT ADMIN INTERFACE ################################################## ############################################################################## - lint_admin: - name: Lint - Admin Interface - runs-on: ubuntu-latest - needs: [build_test_admin] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## - # LINT ADMIN INTERFACE ################################################### - ########################################################################## - - name: Admin Interface | Lint - run: docker run --rm gradido/admin:test yarn run lint + # lint_admin: + # name: Lint - Admin Interface + # runs-on: ubuntu-latest + # needs: [build_test_admin] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Admin Interface) + # uses: actions/download-artifact@v3 + # with: + # name: docker-admin-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/admin.tar + # ########################################################################## + # # LINT ADMIN INTERFACE ################################################### + # ########################################################################## + # - name: Admin Interface | Lint + # run: docker run --rm gradido/admin:test yarn run lint ############################################################################## # JOB: STYLELINT ADMIN INTERFACE ############################################## ############################################################################## - stylelint_admin: - name: Stylelint - Admin Interface - runs-on: ubuntu-latest - needs: [build_test_admin] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## - # STYLELINT ADMIN INTERFACE ############################################## - ########################################################################## - - name: Admin Interface | Stylelint - run: docker run --rm gradido/admin:test yarn run stylelint + # stylelint_admin: + # name: Stylelint - Admin Interface + # runs-on: ubuntu-latest + # needs: [build_test_admin] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Admin Interface) + # uses: actions/download-artifact@v3 + # with: + # name: docker-admin-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/admin.tar + # ########################################################################## + # # STYLELINT ADMIN INTERFACE ############################################## + # ########################################################################## + # - name: Admin Interface | Stylelint + # run: docker run --rm gradido/admin:test yarn run stylelint ############################################################################## # JOB: LOCALES ADMIN ######################################################### ############################################################################## - locales_admin: - name: Locales - Admin Interface - runs-on: ubuntu-latest - needs: [build_test_admin] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## - # LOCALES FRONTEND ####################################################### - ########################################################################## - - name: admin | Locales - run: docker run --rm gradido/admin:test yarn run locales + # locales_admin: + # name: Locales - Admin Interface + # runs-on: ubuntu-latest + # needs: [build_test_admin] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Admin Interface) + # uses: actions/download-artifact@v3 + # with: + # name: docker-admin-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/admin.tar + # ########################################################################## + # # LOCALES FRONTEND ####################################################### + # ########################################################################## + # - name: admin | Locales + # run: docker run --rm gradido/admin:test yarn run locales ############################################################################## # JOB: LINT BACKEND ########################################################## ############################################################################## - lint_backend: - name: Lint - Backend - runs-on: ubuntu-latest - needs: [build_test_backend] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Backend) - uses: actions/download-artifact@v3 - with: - name: docker-backend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/backend.tar - ########################################################################## - # LINT BACKEND ########################################################### - ########################################################################## - - name: backend | Lint - run: docker run --rm gradido/backend:test yarn run lint + # lint_backend: + # name: Lint - Backend + # runs-on: ubuntu-latest + # needs: [build_test_backend] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Backend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-backend-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/backend.tar + # ########################################################################## + # # LINT BACKEND ########################################################### + # ########################################################################## + # - name: backend | Lint + # run: docker run --rm gradido/backend:test yarn run lint ############################################################################## # JOB: LINT DATABASE UP ###################################################### ############################################################################## - lint_database_up: - name: Lint - Database Up - runs-on: ubuntu-latest - needs: [build_test_database_up] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Backend) - uses: actions/download-artifact@v3 - with: - name: docker-database-test_up - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/database_up.tar - ########################################################################## - # LINT DATABASE ########################################################## - ########################################################################## - - name: database | Lint - run: docker run --rm gradido/database:test_up yarn run lint + # lint_database_up: + # name: Lint - Database Up + # runs-on: ubuntu-latest + # needs: [build_test_database_up] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Backend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-database-test_up + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/database_up.tar + # ########################################################################## + # # LINT DATABASE ########################################################## + # ########################################################################## + # - name: database | Lint + # run: docker run --rm gradido/database:test_up yarn run lint ############################################################################## # JOB: UNIT TEST FRONTEND ################################################### ############################################################################## - unit_test_frontend: - name: Unit tests - Frontend - runs-on: ubuntu-latest - needs: [build_test_frontend] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGES ################################################# - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## - # UNIT TESTS FRONTEND #################################################### - ########################################################################## - - name: frontend | Unit tests - run: | - docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test - cp -r ~/coverage ./coverage - ########################################################################## - # COVERAGE REPORT FRONTEND ############################################### - ########################################################################## - #- name: frontend | Coverage report - # uses: romeovs/lcov-reporter-action@v0.2.21 - # with: - # github-token: ${{ secrets.GITHUB_TOKEN }} - # lcov-file: ./coverage/lcov.info - ########################################################################## - # COVERAGE CHECK FRONTEND ################################################ - ########################################################################## - - name: frontend | Coverage check - uses: webcraftmedia/coverage-check-action@master - with: - report_name: Coverage Frontend - type: lcov - result_path: ./coverage/lcov.info - min_coverage: 95 - token: ${{ github.token }} + # unit_test_frontend: + # name: Unit tests - Frontend + # runs-on: ubuntu-latest + # needs: [build_test_frontend] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGES ################################################# + # ########################################################################## + # - name: Download Docker Image (Frontend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-frontend-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/frontend.tar + # ########################################################################## + # # UNIT TESTS FRONTEND #################################################### + # ########################################################################## + # - name: frontend | Unit tests + # run: | + # docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test + # cp -r ~/coverage ./coverage + # ########################################################################## + # # COVERAGE REPORT FRONTEND ############################################### + # ########################################################################## + # #- name: frontend | Coverage report + # # uses: romeovs/lcov-reporter-action@v0.2.21 + # # with: + # # github-token: ${{ secrets.GITHUB_TOKEN }} + # # lcov-file: ./coverage/lcov.info + # ########################################################################## + # # COVERAGE CHECK FRONTEND ################################################ + # ########################################################################## + # - name: frontend | Coverage check + # uses: webcraftmedia/coverage-check-action@master + # with: + # report_name: Coverage Frontend + # type: lcov + # result_path: ./coverage/lcov.info + # min_coverage: 95 + # token: ${{ github.token }} ############################################################################## # JOB: UNIT TEST ADMIN INTERFACE ############################################# ############################################################################## - unit_test_admin: - name: Unit tests - Admin Interface - runs-on: ubuntu-latest - needs: [build_test_admin] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGES ################################################# - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## - # UNIT TESTS ADMIN INTERFACE ############################################# - ########################################################################## - - name: Admin Interface | Unit tests - run: | - docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test - cp -r ~/coverage ./coverage - ########################################################################## - # COVERAGE CHECK ADMIN INTERFACE ######################################### - ########################################################################## - - name: Admin Interface | Coverage check - uses: webcraftmedia/coverage-check-action@master - with: - report_name: Coverage Admin Interface - type: lcov - result_path: ./coverage/lcov.info - min_coverage: 96 - token: ${{ github.token }} + # unit_test_admin: + # name: Unit tests - Admin Interface + # runs-on: ubuntu-latest + # needs: [build_test_admin] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGES ################################################# + # ########################################################################## + # - name: Download Docker Image (Admin Interface) + # uses: actions/download-artifact@v3 + # with: + # name: docker-admin-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/admin.tar + # ########################################################################## + # # UNIT TESTS ADMIN INTERFACE ############################################# + # ########################################################################## + # - name: Admin Interface | Unit tests + # run: | + # docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test + # cp -r ~/coverage ./coverage + # ########################################################################## + # # COVERAGE CHECK ADMIN INTERFACE ######################################### + # ########################################################################## + # - name: Admin Interface | Coverage check + # uses: webcraftmedia/coverage-check-action@master + # with: + # report_name: Coverage Admin Interface + # type: lcov + # result_path: ./coverage/lcov.info + # min_coverage: 96 + # token: ${{ github.token }} ############################################################################## # JOB: UNIT TEST BACKEND #################################################### ############################################################################## - unit_test_backend: - name: Unit tests - Backend - runs-on: ubuntu-latest - needs: [build_test_mariadb] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGES ################################################# - ########################################################################## - - name: Download Docker Image (Mariadb) - uses: actions/download-artifact@v3 - with: - name: docker-mariadb-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/mariadb.tar - ########################################################################## - # UNIT TESTS BACKEND ##################################################### - ########################################################################## - - name: backend | docker-compose mariadb - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb - - name: Sleep for 30 seconds - run: sleep 30s - shell: bash - - name: backend | docker-compose database - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - - name: backend Unit tests | test - run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test - # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test - ########################################################################## - # COVERAGE CHECK BACKEND ################################################# - ########################################################################## - - name: backend | Coverage check - uses: webcraftmedia/coverage-check-action@master - with: - report_name: Coverage Backend - type: lcov - result_path: ./backend/coverage/lcov.info - min_coverage: 78 - token: ${{ github.token }} + # unit_test_backend: + # name: Unit tests - Backend + # runs-on: ubuntu-latest + # needs: [build_test_mariadb] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGES ################################################# + # ########################################################################## + # - name: Download Docker Image (Mariadb) + # uses: actions/download-artifact@v3 + # with: + # name: docker-mariadb-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/mariadb.tar + # ########################################################################## + # # UNIT TESTS BACKEND ##################################################### + # ########################################################################## + # - name: backend | docker-compose mariadb + # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb + # - name: Sleep for 30 seconds + # run: sleep 30s + # shell: bash + # - name: backend | docker-compose database + # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database + # - name: backend Unit tests | test + # run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test + # # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test + # ########################################################################## + # # COVERAGE CHECK BACKEND ################################################# + # ########################################################################## + # - name: backend | Coverage check + # uses: webcraftmedia/coverage-check-action@master + # with: + # report_name: Coverage Backend + # type: lcov + # result_path: ./backend/coverage/lcov.info + # min_coverage: 78 + # token: ${{ github.token }} ########################################################################## # DATABASE MIGRATION TEST UP + RESET ##################################### @@ -624,17 +624,10 @@ jobs: run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: Boot up test system | docker-compose backend - run: | - docker run -i --rm gradido/backend:test /bin/sh - cd backend - cp .env.test_email .env && exit - docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend - - name: Boot up test system | docker-compose mailserver - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver - - - name: Sleep for 5 seconds - run: sleep 5s + - name: Sleep for 10 seconds + run: sleep 10s - name: Boot up test system | seed backend run: | From b4550b1af9d48617a1bbcc9fcae801f306141e76 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 15:46:13 +0100 Subject: [PATCH 32/99] comment workflow jobs for e2e testing --- .github/workflows/test.yml | 88 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4b9dfda9..c2660e2f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -163,31 +163,31 @@ jobs: ############################################################################## # JOB: LOCALES FRONTEND ###################################################### ############################################################################## - locales_frontend: - name: Locales - Frontend - runs-on: ubuntu-latest - needs: [build_test_frontend] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## - # LOCALES FRONTEND ####################################################### - ########################################################################## - - name: Frontend | Locales - run: docker run --rm gradido/frontend:test yarn run locales + # locales_frontend: + # name: Locales - Frontend + # runs-on: ubuntu-latest + # needs: [build_test_frontend] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOWNLOAD DOCKER IMAGE ################################################## + # ########################################################################## + # - name: Download Docker Image (Frontend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-frontend-test + # path: /tmp + # - name: Load Docker Image + # run: docker load < /tmp/frontend.tar + # ########################################################################## + # # LOCALES FRONTEND ####################################################### + # ########################################################################## + # - name: Frontend | Locales + # run: docker run --rm gradido/frontend:test yarn run locales ############################################################################## # JOB: LINT FRONTEND ######################################################### @@ -535,25 +535,25 @@ jobs: ########################################################################## # DATABASE MIGRATION TEST UP + RESET ##################################### ########################################################################## - database_migration_test: - name: Database Migration Test - Up + Reset - runs-on: ubuntu-latest - #needs: [nothing] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # DOCKER COMPOSE DATABASE UP + RESET ##################################### - ########################################################################## - - name: database | docker-compose - run: docker-compose -f docker-compose.yml up --detach mariadb - - name: database | up - run: docker-compose -f docker-compose.yml run -T database yarn up - - name: database | reset - run: docker-compose -f docker-compose.yml run -T database yarn reset + # database_migration_test: + # name: Database Migration Test - Up + Reset + # runs-on: ubuntu-latest + # #needs: [nothing] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # DOCKER COMPOSE DATABASE UP + RESET ##################################### + # ########################################################################## + # - name: database | docker-compose + # run: docker-compose -f docker-compose.yml up --detach mariadb + # - name: database | up + # run: docker-compose -f docker-compose.yml run -T database yarn up + # - name: database | reset + # run: docker-compose -f docker-compose.yml run -T database yarn reset ############################################################################## # JOB: END-TO-END TESTS ##################################################### From bea4e7ce4fd464db4a4b551c85d2143e0705f883 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 16:13:54 +0100 Subject: [PATCH 33/99] use own backup image with dot-env for e2e testing --- .github/workflows/test.yml | 23 +++++++++++++++-------- backend/.env.test_e2e | 5 +++++ 2 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 backend/.env.test_e2e diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2660e2f8..9b8ffa8da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -585,13 +585,13 @@ jobs: path: /tmp - name: Load Docker Image (Database Up) run: docker load < /tmp/database_up.tar - - name: Download Docker Image (Backend) - uses: actions/download-artifact@v3 - with: - name: docker-backend-test - path: /tmp - - name: Load Docker Image (Backend) - run: docker load < /tmp/backend.tar + # - name: Download Docker Image (Backend) + # uses: actions/download-artifact@v3 + # with: + # name: docker-backend-test + # path: /tmp + # - name: Load Docker Image (Backend) + # run: docker load < /tmp/backend.tar - name: Download Docker Image (Frontend) uses: actions/download-artifact@v3 with: @@ -624,7 +624,11 @@ jobs: run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: Boot up test system | docker-compose backend - run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend + run: | + cd backend + cp .env.XX .env + cd .. + docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend - name: Sleep for 10 seconds run: sleep 10s @@ -641,6 +645,9 @@ jobs: - name: Boot up test system | docker-compose frontends run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx + - name: Boot up test system | docker-compose frontends + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver + - name: Sleep for 15 seconds run: sleep 15s diff --git a/backend/.env.test_e2e b/backend/.env.test_e2e new file mode 100644 index 000000000..fba34833a --- /dev/null +++ b/backend/.env.test_e2e @@ -0,0 +1,5 @@ +EMAIL=true +EMAIL_TEST_MODUS=false +EMAIL_TLS=false +# for testing password reset +EMAIL_CODE_REQUEST_TIME=1 From 62505d7561aecf2dc333b580f3b4de39de8af167 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 16:13:54 +0100 Subject: [PATCH 34/99] use own backup image with dot-env for e2e testing --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b8ffa8da..bbd4a5151 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -561,7 +561,7 @@ jobs: end-to-end-tests: name: End-to-End Tests runs-on: ubuntu-latest - needs: [build_test_mariadb, build_test_database_up, build_test_backend, build_test_admin, build_test_frontend, build_test_nginx] + needs: [build_test_mariadb, build_test_database_up, build_test_admin, build_test_frontend, build_test_nginx] steps: ########################################################################## # CHECKOUT CODE ########################################################## From 87d2af8d4e34b2568b0094825112cce0dea31464 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 16:13:54 +0100 Subject: [PATCH 35/99] use own backup image with dot-env for e2e testing --- .github/workflows/test.yml | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bbd4a5151..f887afeef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,28 +60,28 @@ jobs: ############################################################################## # JOB: DOCKER BUILD TEST BACKEND ############################################# ############################################################################## - build_test_backend: - name: Docker Build Test - Backend - runs-on: ubuntu-latest - #needs: [nothing] - steps: - ########################################################################## - # CHECKOUT CODE ########################################################## - ########################################################################## - - name: Checkout code - uses: actions/checkout@v3 - ########################################################################## - # BACKEND ################################################################ - ########################################################################## - - name: Backend | Build `test` image - run: | - docker build -f ./backend/Dockerfile --target test -t "gradido/backend:test" . - docker save "gradido/backend:test" > /tmp/backend.tar - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - name: docker-backend-test - path: /tmp/backend.tar + # build_test_backend: + # name: Docker Build Test - Backend + # runs-on: ubuntu-latest + # #needs: [nothing] + # steps: + # ########################################################################## + # # CHECKOUT CODE ########################################################## + # ########################################################################## + # - name: Checkout code + # uses: actions/checkout@v3 + # ########################################################################## + # # BACKEND ################################################################ + # ########################################################################## + # - name: Backend | Build `test` image + # run: | + # docker build -f ./backend/Dockerfile --target test -t "gradido/backend:test" . + # docker save "gradido/backend:test" > /tmp/backend.tar + # - name: Upload Artifact + # uses: actions/upload-artifact@v3 + # with: + # name: docker-backend-test + # path: /tmp/backend.tar ############################################################################## # JOB: DOCKER BUILD TEST DATABASE UP ######################################### @@ -626,7 +626,7 @@ jobs: - name: Boot up test system | docker-compose backend run: | cd backend - cp .env.XX .env + cp .env.test_e2e .env cd .. docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend From eaaf3a81c206b0e52ea7b5d053e3f551f6b8e30d Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 16:13:54 +0100 Subject: [PATCH 36/99] use own backup image with dot-env for e2e testing --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f887afeef..bf6e6fc5f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -645,7 +645,7 @@ jobs: - name: Boot up test system | docker-compose frontends run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx - - name: Boot up test system | docker-compose frontends + - name: Boot up test system | docker-compose mailserver run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver - name: Sleep for 15 seconds From cb39f0b5052a00e7d94ed6e0ced9a35589ed4b1b Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 17:04:13 +0100 Subject: [PATCH 37/99] set down jwt expiration time in dot-env for e2e testing --- backend/.env.test_e2e | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/.env.test_e2e b/backend/.env.test_e2e index fba34833a..a5cdc4bfd 100644 --- a/backend/.env.test_e2e +++ b/backend/.env.test_e2e @@ -1,3 +1,7 @@ +# Server +JWT_EXPIRES_IN=1m + +# Email EMAIL=true EMAIL_TEST_MODUS=false EMAIL_TLS=false From d89fc1ef105366d0f9deffb0bf79869ce5dff019 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 21:03:14 +0100 Subject: [PATCH 38/99] move response in e2e test to common step definitions --- .../support/step_definitions/common_steps.ts | 12 ++++++++++++ .../step_definitions/user_authentication_steps.ts | 13 ------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts index 42142380b..881394e6d 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts @@ -18,6 +18,18 @@ Given( Then("the user is logged in with username {string}", (username: string) => { const overviewPage = new OverviewPage(); + cy.intercept("POST", "/graphql", (req) => { + if ( + req.body.hasOwnProperty("query") && + req.body.query.includes("mutation") + ) { + req.alias = "login"; + } + }); + cy.wait("@login").then((interception) => { + expect(interception.response.statusCode).equals(200); + expect(JSON.stringify(interception.response.body)).contains(email); + }); cy.url().should("include", "/overview"); cy.get(overviewPage.navbarName).should("contain", username); }); diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index d469180b3..67a64eba0 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -16,22 +16,9 @@ When("the user submits no credentials", () => { When( "the user submits the credentials {string} {string}", (email: string, password: string) => { - cy.intercept("POST", "/graphql", (req) => { - if ( - req.body.hasOwnProperty("query") && - req.body.query.includes("mutation") - ) { - req.alias = "login"; - } - }); - loginPage.enterEmail(email); loginPage.enterPassword(password); loginPage.submitLogin(); - cy.wait("@login").then((interception) => { - expect(interception.response.statusCode).equals(200); - expect(JSON.stringify(interception.response.body)).contains(email); - }); } ); From 010e17ee62e87b04540711237048d9a5ff373315 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 21:58:08 +0100 Subject: [PATCH 39/99] Revert "move response in e2e test to common step definitions" This reverts commit d89fc1ef105366d0f9deffb0bf79869ce5dff019. --- .../support/step_definitions/common_steps.ts | 12 ------------ .../step_definitions/user_authentication_steps.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts index 881394e6d..42142380b 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts @@ -18,18 +18,6 @@ Given( Then("the user is logged in with username {string}", (username: string) => { const overviewPage = new OverviewPage(); - cy.intercept("POST", "/graphql", (req) => { - if ( - req.body.hasOwnProperty("query") && - req.body.query.includes("mutation") - ) { - req.alias = "login"; - } - }); - cy.wait("@login").then((interception) => { - expect(interception.response.statusCode).equals(200); - expect(JSON.stringify(interception.response.body)).contains(email); - }); cy.url().should("include", "/overview"); cy.get(overviewPage.navbarName).should("contain", username); }); diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index 67a64eba0..d469180b3 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -16,9 +16,22 @@ When("the user submits no credentials", () => { When( "the user submits the credentials {string} {string}", (email: string, password: string) => { + cy.intercept("POST", "/graphql", (req) => { + if ( + req.body.hasOwnProperty("query") && + req.body.query.includes("mutation") + ) { + req.alias = "login"; + } + }); + loginPage.enterEmail(email); loginPage.enterPassword(password); loginPage.submitLogin(); + cy.wait("@login").then((interception) => { + expect(interception.response.statusCode).equals(200); + expect(JSON.stringify(interception.response.body)).contains(email); + }); } ); From b2eab939797f5a5fe461b2237e6c394d8d53c643 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 21:59:07 +0100 Subject: [PATCH 40/99] fix step_definition for e2e test --- .../step_definitions/user_authentication_steps.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index d469180b3..50701098c 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -21,16 +21,26 @@ When( req.body.hasOwnProperty("query") && req.body.query.includes("mutation") ) { +<<<<<<< Updated upstream req.alias = "login"; +======= + req.alias = "loging"; +>>>>>>> Stashed changes } }); loginPage.enterEmail(email); loginPage.enterPassword(password); loginPage.submitLogin(); +<<<<<<< Updated upstream cy.wait("@login").then((interception) => { expect(interception.response.statusCode).equals(200); expect(JSON.stringify(interception.response.body)).contains(email); +======= + + cy.wait("@loging").then((interception) => { + expect(interception.response.statusCode).equals(200); +>>>>>>> Stashed changes }); } ); From 758bd8e78120562f6a9a75093df6b8667ac3d48e Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 2 Feb 2023 21:59:07 +0100 Subject: [PATCH 41/99] fix step_definition for e2e test --- .../step_definitions/user_authentication_steps.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts index 50701098c..17743ebe5 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_authentication_steps.ts @@ -21,26 +21,15 @@ When( req.body.hasOwnProperty("query") && req.body.query.includes("mutation") ) { -<<<<<<< Updated upstream req.alias = "login"; -======= - req.alias = "loging"; ->>>>>>> Stashed changes } }); loginPage.enterEmail(email); loginPage.enterPassword(password); loginPage.submitLogin(); -<<<<<<< Updated upstream cy.wait("@login").then((interception) => { expect(interception.response.statusCode).equals(200); - expect(JSON.stringify(interception.response.body)).contains(email); -======= - - cy.wait("@loging").then((interception) => { - expect(interception.response.statusCode).equals(200); ->>>>>>> Stashed changes }); } ); From 1e52c237a374409b278ca7cd462493e984d113dd Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 3 Feb 2023 10:14:54 +0100 Subject: [PATCH 42/99] remove unnecessary dot-env file --- backend/.env.test_email | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 backend/.env.test_email diff --git a/backend/.env.test_email b/backend/.env.test_email deleted file mode 100644 index fba34833a..000000000 --- a/backend/.env.test_email +++ /dev/null @@ -1,5 +0,0 @@ -EMAIL=true -EMAIL_TEST_MODUS=false -EMAIL_TLS=false -# for testing password reset -EMAIL_CODE_REQUEST_TIME=1 From c1461ede98e612c0831930f4ad10e9a72c35ecfa Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 3 Feb 2023 10:33:38 +0100 Subject: [PATCH 43/99] uncomment all jobs in test workflow --- .github/workflows/test.yml | 747 ++++++++++++++++++------------------- 1 file changed, 370 insertions(+), 377 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bf6e6fc5f..34c49e12d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,28 +60,28 @@ jobs: ############################################################################## # JOB: DOCKER BUILD TEST BACKEND ############################################# ############################################################################## - # build_test_backend: - # name: Docker Build Test - Backend - # runs-on: ubuntu-latest - # #needs: [nothing] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # BACKEND ################################################################ - # ########################################################################## - # - name: Backend | Build `test` image - # run: | - # docker build -f ./backend/Dockerfile --target test -t "gradido/backend:test" . - # docker save "gradido/backend:test" > /tmp/backend.tar - # - name: Upload Artifact - # uses: actions/upload-artifact@v3 - # with: - # name: docker-backend-test - # path: /tmp/backend.tar + build_test_backend: + name: Docker Build Test - Backend + runs-on: ubuntu-latest + #needs: [nothing] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # BACKEND ################################################################ + ########################################################################## + - name: Backend | Build `test` image + run: | + docker build -f ./backend/Dockerfile --target test -t "gradido/backend:test" . + docker save "gradido/backend:test" > /tmp/backend.tar + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: docker-backend-test + path: /tmp/backend.tar ############################################################################## # JOB: DOCKER BUILD TEST DATABASE UP ######################################### @@ -163,397 +163,397 @@ jobs: ############################################################################## # JOB: LOCALES FRONTEND ###################################################### ############################################################################## - # locales_frontend: - # name: Locales - Frontend - # runs-on: ubuntu-latest - # needs: [build_test_frontend] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Frontend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-frontend-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/frontend.tar - # ########################################################################## - # # LOCALES FRONTEND ####################################################### - # ########################################################################## - # - name: Frontend | Locales - # run: docker run --rm gradido/frontend:test yarn run locales + locales_frontend: + name: Locales - Frontend + runs-on: ubuntu-latest + needs: [build_test_frontend] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Frontend) + uses: actions/download-artifact@v3 + with: + name: docker-frontend-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/frontend.tar + ########################################################################## + # LOCALES FRONTEND ####################################################### + ########################################################################## + - name: Frontend | Locales + run: docker run --rm gradido/frontend:test yarn run locales ############################################################################## # JOB: LINT FRONTEND ######################################################### ############################################################################## - # lint_frontend: - # name: Lint - Frontend - # runs-on: ubuntu-latest - # needs: [build_test_frontend] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Frontend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-frontend-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/frontend.tar - # ########################################################################## - # # LINT FRONTEND ########################################################## - # ########################################################################## - # - name: Frontend | Lint - # run: docker run --rm gradido/frontend:test yarn run lint + lint_frontend: + name: Lint - Frontend + runs-on: ubuntu-latest + needs: [build_test_frontend] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Frontend) + uses: actions/download-artifact@v3 + with: + name: docker-frontend-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/frontend.tar + ########################################################################## + # LINT FRONTEND ########################################################## + ########################################################################## + - name: Frontend | Lint + run: docker run --rm gradido/frontend:test yarn run lint ############################################################################## # JOB: STYLELINT FRONTEND #################################################### ############################################################################## - # stylelint_frontend: - # name: Stylelint - Frontend - # runs-on: ubuntu-latest - # needs: [build_test_frontend] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Frontend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-frontend-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/frontend.tar - # ########################################################################## - # # STYLELINT FRONTEND ##################################################### - # ########################################################################## - # - name: Frontend | Stylelint - # run: docker run --rm gradido/frontend:test yarn run stylelint + stylelint_frontend: + name: Stylelint - Frontend + runs-on: ubuntu-latest + needs: [build_test_frontend] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Frontend) + uses: actions/download-artifact@v3 + with: + name: docker-frontend-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/frontend.tar + ########################################################################## + # STYLELINT FRONTEND ##################################################### + ########################################################################## + - name: Frontend | Stylelint + run: docker run --rm gradido/frontend:test yarn run stylelint ############################################################################## # JOB: LINT ADMIN INTERFACE ################################################## ############################################################################## - # lint_admin: - # name: Lint - Admin Interface - # runs-on: ubuntu-latest - # needs: [build_test_admin] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Admin Interface) - # uses: actions/download-artifact@v3 - # with: - # name: docker-admin-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/admin.tar - # ########################################################################## - # # LINT ADMIN INTERFACE ################################################### - # ########################################################################## - # - name: Admin Interface | Lint - # run: docker run --rm gradido/admin:test yarn run lint + lint_admin: + name: Lint - Admin Interface + runs-on: ubuntu-latest + needs: [build_test_admin] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Admin Interface) + uses: actions/download-artifact@v3 + with: + name: docker-admin-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/admin.tar + ########################################################################## + # LINT ADMIN INTERFACE ################################################### + ########################################################################## + - name: Admin Interface | Lint + run: docker run --rm gradido/admin:test yarn run lint ############################################################################## - # JOB: STYLELINT ADMIN INTERFACE ############################################## + # JOB: STYLELINT ADMIN INTERFACE ############################################# ############################################################################## - # stylelint_admin: - # name: Stylelint - Admin Interface - # runs-on: ubuntu-latest - # needs: [build_test_admin] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Admin Interface) - # uses: actions/download-artifact@v3 - # with: - # name: docker-admin-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/admin.tar - # ########################################################################## - # # STYLELINT ADMIN INTERFACE ############################################## - # ########################################################################## - # - name: Admin Interface | Stylelint - # run: docker run --rm gradido/admin:test yarn run stylelint + stylelint_admin: + name: Stylelint - Admin Interface + runs-on: ubuntu-latest + needs: [build_test_admin] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Admin Interface) + uses: actions/download-artifact@v3 + with: + name: docker-admin-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/admin.tar + ########################################################################## + # STYLELINT ADMIN INTERFACE ############################################## + ########################################################################## + - name: Admin Interface | Stylelint + run: docker run --rm gradido/admin:test yarn run stylelint ############################################################################## # JOB: LOCALES ADMIN ######################################################### ############################################################################## - # locales_admin: - # name: Locales - Admin Interface - # runs-on: ubuntu-latest - # needs: [build_test_admin] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Admin Interface) - # uses: actions/download-artifact@v3 - # with: - # name: docker-admin-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/admin.tar - # ########################################################################## - # # LOCALES FRONTEND ####################################################### - # ########################################################################## - # - name: admin | Locales - # run: docker run --rm gradido/admin:test yarn run locales + locales_admin: + name: Locales - Admin Interface + runs-on: ubuntu-latest + needs: [build_test_admin] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Admin Interface) + uses: actions/download-artifact@v3 + with: + name: docker-admin-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/admin.tar + ########################################################################## + # LOCALES FRONTEND ####################################################### + ########################################################################## + - name: admin | Locales + run: docker run --rm gradido/admin:test yarn run locales ############################################################################## # JOB: LINT BACKEND ########################################################## ############################################################################## - # lint_backend: - # name: Lint - Backend - # runs-on: ubuntu-latest - # needs: [build_test_backend] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Backend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-backend-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/backend.tar - # ########################################################################## - # # LINT BACKEND ########################################################### - # ########################################################################## - # - name: backend | Lint - # run: docker run --rm gradido/backend:test yarn run lint + lint_backend: + name: Lint - Backend + runs-on: ubuntu-latest + needs: [build_test_backend] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Backend) + uses: actions/download-artifact@v3 + with: + name: docker-backend-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/backend.tar + ########################################################################## + # LINT BACKEND ########################################################### + ########################################################################## + - name: backend | Lint + run: docker run --rm gradido/backend:test yarn run lint ############################################################################## # JOB: LINT DATABASE UP ###################################################### ############################################################################## - # lint_database_up: - # name: Lint - Database Up - # runs-on: ubuntu-latest - # needs: [build_test_database_up] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGE ################################################## - # ########################################################################## - # - name: Download Docker Image (Backend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-database-test_up - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/database_up.tar - # ########################################################################## - # # LINT DATABASE ########################################################## - # ########################################################################## - # - name: database | Lint - # run: docker run --rm gradido/database:test_up yarn run lint + lint_database_up: + name: Lint - Database Up + runs-on: ubuntu-latest + needs: [build_test_database_up] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Backend) + uses: actions/download-artifact@v3 + with: + name: docker-database-test_up + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/database_up.tar + ########################################################################## + # LINT DATABASE ########################################################## + ########################################################################## + - name: database | Lint + run: docker run --rm gradido/database:test_up yarn run lint ############################################################################## # JOB: UNIT TEST FRONTEND ################################################### ############################################################################## - # unit_test_frontend: - # name: Unit tests - Frontend - # runs-on: ubuntu-latest - # needs: [build_test_frontend] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGES ################################################# - # ########################################################################## - # - name: Download Docker Image (Frontend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-frontend-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/frontend.tar - # ########################################################################## - # # UNIT TESTS FRONTEND #################################################### - # ########################################################################## - # - name: frontend | Unit tests - # run: | - # docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test - # cp -r ~/coverage ./coverage - # ########################################################################## - # # COVERAGE REPORT FRONTEND ############################################### - # ########################################################################## - # #- name: frontend | Coverage report - # # uses: romeovs/lcov-reporter-action@v0.2.21 - # # with: - # # github-token: ${{ secrets.GITHUB_TOKEN }} - # # lcov-file: ./coverage/lcov.info - # ########################################################################## - # # COVERAGE CHECK FRONTEND ################################################ - # ########################################################################## - # - name: frontend | Coverage check - # uses: webcraftmedia/coverage-check-action@master - # with: - # report_name: Coverage Frontend - # type: lcov - # result_path: ./coverage/lcov.info - # min_coverage: 95 - # token: ${{ github.token }} + unit_test_frontend: + name: Unit tests - Frontend + runs-on: ubuntu-latest + needs: [build_test_frontend] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGES ################################################# + ########################################################################## + - name: Download Docker Image (Frontend) + uses: actions/download-artifact@v3 + with: + name: docker-frontend-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/frontend.tar + ########################################################################## + # UNIT TESTS FRONTEND #################################################### + ########################################################################## + - name: frontend | Unit tests + run: | + docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test + cp -r ~/coverage ./coverage + ########################################################################## + # COVERAGE REPORT FRONTEND ############################################### + ########################################################################## + #- name: frontend | Coverage report + # uses: romeovs/lcov-reporter-action@v0.2.21 + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} + # lcov-file: ./coverage/lcov.info + ########################################################################## + # COVERAGE CHECK FRONTEND ################################################ + ########################################################################## + - name: frontend | Coverage check + uses: webcraftmedia/coverage-check-action@master + with: + report_name: Coverage Frontend + type: lcov + result_path: ./coverage/lcov.info + min_coverage: 95 + token: ${{ github.token }} ############################################################################## # JOB: UNIT TEST ADMIN INTERFACE ############################################# ############################################################################## - # unit_test_admin: - # name: Unit tests - Admin Interface - # runs-on: ubuntu-latest - # needs: [build_test_admin] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGES ################################################# - # ########################################################################## - # - name: Download Docker Image (Admin Interface) - # uses: actions/download-artifact@v3 - # with: - # name: docker-admin-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/admin.tar - # ########################################################################## - # # UNIT TESTS ADMIN INTERFACE ############################################# - # ########################################################################## - # - name: Admin Interface | Unit tests - # run: | - # docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test - # cp -r ~/coverage ./coverage - # ########################################################################## - # # COVERAGE CHECK ADMIN INTERFACE ######################################### - # ########################################################################## - # - name: Admin Interface | Coverage check - # uses: webcraftmedia/coverage-check-action@master - # with: - # report_name: Coverage Admin Interface - # type: lcov - # result_path: ./coverage/lcov.info - # min_coverage: 96 - # token: ${{ github.token }} + unit_test_admin: + name: Unit tests - Admin Interface + runs-on: ubuntu-latest + needs: [build_test_admin] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGES ################################################# + ########################################################################## + - name: Download Docker Image (Admin Interface) + uses: actions/download-artifact@v3 + with: + name: docker-admin-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/admin.tar + ########################################################################## + # UNIT TESTS ADMIN INTERFACE ############################################# + ########################################################################## + - name: Admin Interface | Unit tests + run: | + docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test + cp -r ~/coverage ./coverage + ########################################################################## + # COVERAGE CHECK ADMIN INTERFACE ######################################### + ########################################################################## + - name: Admin Interface | Coverage check + uses: webcraftmedia/coverage-check-action@master + with: + report_name: Coverage Admin Interface + type: lcov + result_path: ./coverage/lcov.info + min_coverage: 96 + token: ${{ github.token }} ############################################################################## # JOB: UNIT TEST BACKEND #################################################### ############################################################################## - # unit_test_backend: - # name: Unit tests - Backend - # runs-on: ubuntu-latest - # needs: [build_test_mariadb] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOWNLOAD DOCKER IMAGES ################################################# - # ########################################################################## - # - name: Download Docker Image (Mariadb) - # uses: actions/download-artifact@v3 - # with: - # name: docker-mariadb-test - # path: /tmp - # - name: Load Docker Image - # run: docker load < /tmp/mariadb.tar - # ########################################################################## - # # UNIT TESTS BACKEND ##################################################### - # ########################################################################## - # - name: backend | docker-compose mariadb - # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb - # - name: Sleep for 30 seconds - # run: sleep 30s - # shell: bash - # - name: backend | docker-compose database - # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - # - name: backend Unit tests | test - # run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test - # # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test - # ########################################################################## - # # COVERAGE CHECK BACKEND ################################################# - # ########################################################################## - # - name: backend | Coverage check - # uses: webcraftmedia/coverage-check-action@master - # with: - # report_name: Coverage Backend - # type: lcov - # result_path: ./backend/coverage/lcov.info - # min_coverage: 78 - # token: ${{ github.token }} + unit_test_backend: + name: Unit tests - Backend + runs-on: ubuntu-latest + needs: [build_test_mariadb] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOWNLOAD DOCKER IMAGES ################################################# + ########################################################################## + - name: Download Docker Image (Mariadb) + uses: actions/download-artifact@v3 + with: + name: docker-mariadb-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/mariadb.tar + ########################################################################## + # UNIT TESTS BACKEND ##################################################### + ########################################################################## + - name: backend | docker-compose mariadb + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb + - name: Sleep for 30 seconds + run: sleep 30s + shell: bash + - name: backend | docker-compose database + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database + - name: backend Unit tests | test + run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test + # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test + ########################################################################## + # COVERAGE CHECK BACKEND ################################################# + ########################################################################## + - name: backend | Coverage check + uses: webcraftmedia/coverage-check-action@master + with: + report_name: Coverage Backend + type: lcov + result_path: ./backend/coverage/lcov.info + min_coverage: 78 + token: ${{ github.token }} ########################################################################## # DATABASE MIGRATION TEST UP + RESET ##################################### ########################################################################## - # database_migration_test: - # name: Database Migration Test - Up + Reset - # runs-on: ubuntu-latest - # #needs: [nothing] - # steps: - # ########################################################################## - # # CHECKOUT CODE ########################################################## - # ########################################################################## - # - name: Checkout code - # uses: actions/checkout@v3 - # ########################################################################## - # # DOCKER COMPOSE DATABASE UP + RESET ##################################### - # ########################################################################## - # - name: database | docker-compose - # run: docker-compose -f docker-compose.yml up --detach mariadb - # - name: database | up - # run: docker-compose -f docker-compose.yml run -T database yarn up - # - name: database | reset - # run: docker-compose -f docker-compose.yml run -T database yarn reset + database_migration_test: + name: Database Migration Test - Up + Reset + runs-on: ubuntu-latest + #needs: [nothing] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v3 + ########################################################################## + # DOCKER COMPOSE DATABASE UP + RESET ##################################### + ########################################################################## + - name: database | docker-compose + run: docker-compose -f docker-compose.yml up --detach mariadb + - name: database | up + run: docker-compose -f docker-compose.yml run -T database yarn up + - name: database | reset + run: docker-compose -f docker-compose.yml run -T database yarn reset ############################################################################## # JOB: END-TO-END TESTS ##################################################### @@ -585,13 +585,6 @@ jobs: path: /tmp - name: Load Docker Image (Database Up) run: docker load < /tmp/database_up.tar - # - name: Download Docker Image (Backend) - # uses: actions/download-artifact@v3 - # with: - # name: docker-backend-test - # path: /tmp - # - name: Load Docker Image (Backend) - # run: docker load < /tmp/backend.tar - name: Download Docker Image (Frontend) uses: actions/download-artifact@v3 with: From 4f0cfb9363a908ffba815639f4ad4d4f5b11c4a9 Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 3 Feb 2023 13:55:08 +0100 Subject: [PATCH 44/99] Change structure of tests so that we create every data needed beforeAll unit tests. --- .../resolver/ContributionResolver.test.ts | 1166 ++++++++++------- 1 file changed, 688 insertions(+), 478 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 9c917368b..226ab63cd 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -17,6 +17,8 @@ import { adminUpdateContribution, adminDeleteContribution, login, + logout, + adminCreateContributionMessage, } from '@/seeds/graphql/mutations' import { listAllContributions, @@ -69,7 +71,13 @@ let mutate: any, query: any, con: any let testEnv: any let creation: Contribution | void let admin: User -let result: any +// let result: any +// let contribution: any +let pendingContribution: any +let inProgressContribution: any +let contributionToConfirm: any +let contributionToDeny: any +let contributionToDelete: any beforeAll(async () => { testEnv = await testEnvironment(logger, localization) @@ -86,6 +94,75 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any + let peter: any + + beforeAll(async () => { + bibi = await userFactory(testEnv, bibiBloxberg) + admin = peter = await userFactory(testEnv, peterLustig) + const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await creationFactory(testEnv, bibisCreation!) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + pendingContribution = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test PENDING contribution', + creationDate: new Date().toString(), + }, + }) + inProgressContribution = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test IN_PROGESS contribution', + creationDate: new Date().toString(), + }, + }) + await mutate({ + mutation: adminCreateContributionMessage, + variables: { + contributionId: inProgressContribution.data.createContribution.id, + message: 'Test message to IN_PROGESS contribution', + }, + }) + contributionToConfirm = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test contribution to confirm', + creationDate: new Date().toString(), + }, + }) + contributionToDeny = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test contribution to deny', + creationDate: new Date().toString(), + }, + }) + contributionToDelete = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test contribution to delete', + creationDate: new Date().toString(), + }, + }) + await mutate({ + mutation: logout, + }) + resetToken() + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) describe('createContribution', () => { describe('unauthenticated', () => { @@ -105,8 +182,6 @@ describe('ContributionResolver', () => { describe('authenticated with valid user', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - bibi = await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, @@ -114,7 +189,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -222,27 +296,14 @@ describe('ContributionResolver', () => { }) describe('valid input', () => { - let contribution: any - - beforeAll(async () => { - contribution = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) - }) - it('creates contribution', async () => { - expect(contribution).toEqual( + expect(pendingContribution).toEqual( expect.objectContaining({ data: { createContribution: { id: expect.any(Number), amount: '100', - memo: 'Test env contribution', + memo: 'Test PENDING contribution', }, }, }), @@ -254,7 +315,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CREATE, amount: expect.decimalEqual(100), - contributionId: contribution.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, userId: bibi.data.login.id, }), ) @@ -263,122 +324,6 @@ describe('ContributionResolver', () => { }) }) - describe('listContributions', () => { - describe('unauthenticated', () => { - it('returns an error', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: false, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - - describe('authenticated', () => { - beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - await userFactory(testEnv, peterLustig) - const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await creationFactory(testEnv, bibisCreation!) - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) - }) - - afterAll(async () => { - await cleanDB() - resetToken() - }) - - describe('filter confirmed is false', () => { - it('returns creations', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: false, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listContributions: { - contributionCount: 2, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - }) - - describe('filter confirmed is true', () => { - it('returns only unconfirmed creations', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: true, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listContributions: { - contributionCount: 1, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - }) - }) - }) - describe('updateContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { @@ -402,24 +347,13 @@ describe('ContributionResolver', () => { describe('authenticated', () => { beforeAll(async () => { - await userFactory(testEnv, peterLustig) - await userFactory(testEnv, bibiBloxberg) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - result = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -456,7 +390,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 100.0, memo: 'Test', creationDate: date.toString(), @@ -482,7 +416,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 100.0, memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test', creationDate: date.toString(), @@ -514,7 +448,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 10.0, memo: 'Test env contribution', creationDate: new Date().toString(), @@ -539,13 +473,20 @@ describe('ContributionResolver', () => { }) describe('admin tries to update a user contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + it('throws an error', async () => { jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, variables: { - id: result.data.createContribution.id, + id: pendingContribution.data.createContribution.id, email: 'bibi@bloxberg.de', amount: 10.0, memo: 'Test env contribution', @@ -562,7 +503,7 @@ describe('ContributionResolver', () => { // TODO check that the error is logged (need to modify AdminResolver, avoid conflicts) }) - describe('update too much so that the limit is exceeded', () => { + describe('update to much so that the limit is exceeded', () => { beforeAll(async () => { await mutate({ mutation: login, @@ -576,7 +517,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 1019.0, memo: 'Test env contribution', creationDate: new Date().toString(), @@ -586,7 +527,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.', ), ], }), @@ -595,7 +536,7 @@ describe('ContributionResolver', () => { it('logs the error found', () => { expect(logger.error).toBeCalledWith( - 'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.', ) }) }) @@ -608,7 +549,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 10.0, memo: 'Test env contribution', creationDate: date.setMonth(date.getMonth() - 3).toString(), @@ -635,9 +576,9 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 10.0, - memo: 'Test contribution', + memo: 'Test PENDING contribution update', creationDate: new Date().toString(), }, }), @@ -645,9 +586,9 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { updateContribution: { - id: result.data.createContribution.id, + id: pendingContribution.data.createContribution.id, amount: '10', - memo: 'Test contribution', + memo: 'Test PENDING contribution update', }, }, }), @@ -664,7 +605,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_UPDATE, amount: expect.decimalEqual(10), - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, userId: bibi.data.login.id, }), ) @@ -691,22 +632,36 @@ describe('ContributionResolver', () => { }) }) - describe('authenticated', () => { + describe('authenticated without admin rights', () => { beforeAll(async () => { - await userFactory(testEnv, peterLustig) - await userFactory(testEnv, bibiBloxberg) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - result = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) + }) + + afterAll(() => { + resetToken() + }) + + it('returns an error', async () => { + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: 1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('authenticated with admin rights', () => { + beforeAll(async () => { await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, @@ -714,13 +669,11 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) describe('wrong contribution id', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: denyContribution, @@ -740,31 +693,6 @@ describe('ContributionResolver', () => { }) }) - describe('wrong user tries to deny the contribution', () => { - beforeAll(async () => { - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - }) - - it('throws an error', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - describe('valid input', () => { it('deny contribution', async () => { await mutate({ @@ -775,7 +703,7 @@ describe('ContributionResolver', () => { mutate({ mutation: denyContribution, variables: { - id: result.data.createContribution.id, + id: contributionToDeny.data.createContribution.id, }, }), ).resolves.toEqual( @@ -790,6 +718,282 @@ describe('ContributionResolver', () => { }) }) + describe('deleteContribution', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + query({ + query: deleteContribution, + variables: { + id: -1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + bibi = await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + resetToken() + }) + + describe('wrong contribution id', () => { + it('returns an error', async () => { + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: -1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('Contribution not found for given id') + }) + }) + + describe('other user sends a deleteContribution', () => { + it('returns an error', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Can not delete contribution of another user')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('Can not delete contribution of another user') + }) + }) + + describe('User deletes own contribution', () => { + it('deletes successfully', async () => { + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }), + ).resolves.toBeTruthy() + }) + + it('stores the delete contribution event in the database', async () => { + const contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + + await mutate({ + mutation: deleteContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await expect(EventProtocol.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventProtocolType.CONTRIBUTION_DELETE, + contributionId: contribution.data.createContribution.id, + amount: expect.decimalEqual(166), + userId: peter.id, + }), + ) + }) + }) + + describe('User deletes already confirmed contribution', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: confirmContribution, + variables: { + id: contributionToConfirm.data.createContribution.id, + }, + }) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: contributionToConfirm.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('A confirmed contribution can not be deleted')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted') + }) + }) + }) + }) + + describe('listContributions', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: false, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + resetToken() + }) + + describe('filter confirmed is false', () => { + it('returns creations', async () => { + await expect( + query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: false, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listContributions: { + contributionCount: 6, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + ]), + }, + }, + }), + ) + }) + }) + + describe('filter confirmed is true', () => { + it('returns only unconfirmed creations', async () => { + await expect( + query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: true, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listContributions: { + contributionCount: 4, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to delete', + amount: '100', + }), + ]), + }, + }, + }), + ) + }) + }) + }) + }) + describe('listAllContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { @@ -813,27 +1017,13 @@ describe('ContributionResolver', () => { describe('authenticated', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - await userFactory(testEnv, peterLustig) - const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await creationFactory(testEnv, bibisCreation!) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -920,20 +1110,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 6, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -956,20 +1170,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 6, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -992,20 +1230,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 6, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -1028,20 +1290,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 1, + contributionCount: 2, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -1067,17 +1353,41 @@ describe('ContributionResolver', () => { contributionCount: 1, contributionList: expect.arrayContaining([ expect.not.objectContaining({ - id: expect.any(Number), + amount: '100', state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', + id: expect.any(Number), + memo: 'Test contribution to confirm', }), expect.objectContaining({ id: expect.any(Number), state: 'PENDING', - memo: 'Test env contribution', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', amount: '100', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), ]), }, }, @@ -1100,20 +1410,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + expect.not.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -1136,20 +1470,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + expect.not.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -1173,20 +1531,7 @@ describe('ContributionResolver', () => { data: { listAllContributions: { contributionCount: 0, - contributionList: expect.not.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), + contributionList: [], }, }, }), @@ -1208,20 +1553,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 3, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), state: 'CONFIRMED', memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), ]), }, }, @@ -1231,173 +1600,6 @@ describe('ContributionResolver', () => { }) }) - describe('deleteContribution', () => { - describe('unauthenticated', () => { - it('returns an error', async () => { - await expect( - query({ - query: deleteContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - - describe('authenticated', () => { - let peter: any - beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - peter = await userFactory(testEnv, peterLustig) - - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - result = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) - }) - - afterAll(async () => { - await cleanDB() - resetToken() - }) - - describe('wrong contribution id', () => { - it('returns an error', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith('Contribution not found for given id') - }) - }) - - describe('other user sends a deleteContribution', () => { - it('returns an error', async () => { - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, - }) - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Can not delete contribution of another user')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith('Can not delete contribution of another user') - }) - }) - - describe('User deletes own contribution', () => { - it('deletes successfully', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toBeTruthy() - }) - - it('stores the delete contribution event in the database', async () => { - const contribution = await mutate({ - mutation: createContribution, - variables: { - amount: 166.0, - memo: 'Whatever contribution', - creationDate: new Date().toString(), - }, - }) - - await mutate({ - mutation: deleteContribution, - variables: { - id: contribution.data.createContribution.id, - }, - }) - - await expect(EventProtocol.find()).resolves.toContainEqual( - expect.objectContaining({ - type: EventProtocolType.CONTRIBUTION_DELETE, - contributionId: contribution.data.createContribution.id, - amount: expect.decimalEqual(166), - userId: peter.id, - }), - ) - }) - }) - - describe('User deletes already confirmed contribution', () => { - it('throws an error', async () => { - jest.clearAllMocks() - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, - }) - await mutate({ - mutation: confirmContribution, - variables: { - id: result.data.createContribution.id, - }, - }) - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('A confirmed contribution can not be deleted')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted') - }) - }) - }) - }) - describe('contributions', () => { const variables = { email: 'bibi@bloxberg.de', @@ -1505,7 +1707,6 @@ describe('ContributionResolver', () => { describe('authenticated', () => { describe('without admin rights', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, @@ -1513,7 +1714,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -1614,7 +1814,6 @@ describe('ContributionResolver', () => { describe('with admin rights', () => { beforeAll(async () => { - admin = await userFactory(testEnv, peterLustig) await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, @@ -1622,7 +1821,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -1644,6 +1842,7 @@ describe('ContributionResolver', () => { creation = await Contribution.findOneOrFail({ where: { memo: 'Herzlich Willkommen bei Gradido!', + amount: 400, }, }) }) @@ -1651,6 +1850,7 @@ describe('ContributionResolver', () => { describe('user to create for does not exist', () => { it('throws an error', async () => { jest.clearAllMocks() + variables.email = 'some@fake.email' variables.creationDate = contributionDateFormatter( new Date(now.getFullYear(), now.getMonth() - 1, 1), ) @@ -1658,15 +1858,13 @@ describe('ContributionResolver', () => { mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('Could not find user with email: bibi@bloxberg.de')], + errors: [new GraphQLError('Could not find user with email: some@fake.email')], }), ) }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'Could not find user with email: bibi@bloxberg.de', - ) + expect(logger.error).toBeCalledWith('Could not find user with email: some@fake.email') }) }) @@ -1730,7 +1928,6 @@ describe('ContributionResolver', () => { describe('valid user to create for', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) variables.email = 'bibi@bloxberg.de' variables.creationDate = 'invalid-date' }) @@ -1812,7 +2009,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (2000 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (900 GDD) still available for this month.', ), ], }), @@ -1821,7 +2018,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (2000 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (900 GDD) still available for this month.', ) }) }) @@ -1834,7 +2031,7 @@ describe('ContributionResolver', () => { ).resolves.toEqual( expect.objectContaining({ data: { - adminCreateContribution: [1000, 1000, 800], + adminCreateContribution: [1000, 1000, 700], }, }), ) @@ -1859,7 +2056,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1000 GDD) to be created exceeds the amount (800 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (700 GDD) still available for this month.', ), ], }), @@ -1868,7 +2065,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1000 GDD) to be created exceeds the amount (800 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (700 GDD) still available for this month.', ) }) }) @@ -2013,6 +2210,7 @@ describe('ContributionResolver', () => { describe('user email does not match creation user', () => { it('throws an error', async () => { jest.clearAllMocks() + console.log('creation', creation) await expect( mutate({ mutation: adminUpdateContribution, @@ -2170,7 +2368,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listUnconfirmedContributions: expect.arrayContaining([ - { + expect.objectContaining({ id: expect.any(Number), firstName: 'Peter', lastName: 'Lustig', @@ -2179,9 +2377,9 @@ describe('ContributionResolver', () => { memo: 'Das war leider zu Viel!', amount: '200', moderator: admin.id, - creation: ['1000', '800', '500'], - }, - { + creation: ['1000', '600', '500'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Peter', lastName: 'Lustig', @@ -2190,9 +2388,20 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '800', '500'], - }, - { + creation: ['1000', '600', '500'], + }), + expect.objectContaining({ + id: expect.any(Number), + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Test contribution to delete', + amount: '100', + moderator: null, + creation: ['1000', '1000', '200'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2201,9 +2410,9 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '1000', '300'], - }, - { + creation: ['1000', '1000', '200'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2212,8 +2421,8 @@ describe('ContributionResolver', () => { memo: 'Aktives Grundeinkommen', amount: '200', moderator: admin.id, - creation: ['1000', '1000', '300'], - }, + creation: ['1000', '1000', '200'], + }), ]), }, }), @@ -2245,12 +2454,13 @@ describe('ContributionResolver', () => { }) describe('admin deletes own user contribution', () => { + let ownContribution: any beforeAll(async () => { await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - result = await mutate({ + ownContribution = await mutate({ mutation: createContribution, variables: { amount: 100.0, @@ -2266,7 +2476,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminDeleteContribution, variables: { - id: result.data.createContribution.id, + id: ownContribution.data.createContribution.id, }, }), ).resolves.toEqual( From d3f8c22fdb5df3f8d72b0c00bd7b69084b36f4c5 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 3 Feb 2023 14:43:06 +0100 Subject: [PATCH 45/99] straighten out the wording of the feature's focus Co-authored-by: Ulf Gebhardt --- .../tests/cypress/e2e/User.Authentication.ResetPassword.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature index 7722e636e..a2fd3b36a 100644 --- a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature +++ b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature @@ -1,6 +1,6 @@ Feature: User Authentication - reset password As a user - I want the option to reset my password from the sign in page + I want to reset my password from the sign in page # TODO for these pre-conditions utilize seeding or API check, if user exists in test system # Background: From a8a107628d8d9d370ee1a1f8f5b17dc14e2dc474 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 3 Feb 2023 14:51:26 +0100 Subject: [PATCH 46/99] for more coherent reading of scenario's storyline change completely to the user as the actor --- .../tests/cypress/e2e/User.Authentication.ResetPassword.feature | 2 +- e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature | 2 +- e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature | 2 +- .../tests/cypress/e2e/UserProfile.ChangePassword.feature | 2 +- .../tests/cypress/support/step_definitions/common_steps.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature index a2fd3b36a..55ca87215 100644 --- a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature +++ b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.ResetPassword.feature @@ -9,7 +9,7 @@ Feature: User Authentication - reset password # | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg | Scenario: Reset password from signin page successfully - Given the browser navigates to page "/login" + Given the user navigates to page "/login" And the user navigates to the forgot password page When the user enters the e-mail address "bibi@bloxberg.de" And the user submits the e-mail form diff --git a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature index e2c459692..3b460efc6 100644 --- a/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature +++ b/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature @@ -11,7 +11,7 @@ Feature: User authentication # | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg | Scenario: Log in successfully - Given the browser navigates to page "/login" + Given the user navigates to page "/login" When the user submits the credentials "bibi@bloxberg.de" "Aa12345_" Then the user is logged in with username "Bibi Bloxberg" diff --git a/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature b/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature index 9361d2b84..ed53bb4b0 100644 --- a/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature +++ b/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature @@ -4,7 +4,7 @@ Feature: User registration @skip Scenario: Register successfully - Given the browser navigates to page "/register" + Given the user navigates to page "/register" When the user fills name and email "Regina" "Register" "regina@register.com" And the user agrees to the privacy policy And the user submits the registration form diff --git a/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature b/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature index ceee131fb..aa853f6ff 100644 --- a/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature +++ b/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature @@ -12,7 +12,7 @@ Feature: User profile - change password Given the user is logged in as "bibi@bloxberg.de" "Aa12345_" Scenario: Change password successfully - Given the browser navigates to page "/profile" + Given the user navigates to page "/profile" And the user opens the change password menu When the user fills the password form with: | Old password | Aa12345_ | diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts index 42142380b..e2d66f76a 100644 --- a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts +++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts @@ -3,7 +3,7 @@ import { OverviewPage } from "../../e2e/models/OverviewPage"; import { SideNavMenu } from "../../e2e/models/SideNavMenu"; import { Toasts } from "../../e2e/models/Toasts"; -Given("the browser navigates to page {string}", (page: string) => { +Given("the user navigates to page {string}", (page: string) => { cy.visit(page); }); From b79a7a453632d11a3267174f988a0d6b84538256 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 3 Feb 2023 15:04:13 +0100 Subject: [PATCH 47/99] remove whitespace from test workflow file --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34c49e12d..c737f10f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -625,7 +625,7 @@ jobs: - name: Sleep for 10 seconds run: sleep 10s - + - name: Boot up test system | seed backend run: | sudo chown runner:docker -R * From d2665583b51e593be1172ce3b75c088703b6263f Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 3 Feb 2023 15:28:52 +0100 Subject: [PATCH 48/99] set test workflow trigger back to push --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c737f10f4..04dbb8382 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,6 @@ name: gradido test CI -on: - push: - branches: - - 2352-feat-user-story-user-authentication-reset-password +on: push jobs: ############################################################################## From 0575c513c4f91d0bd200bac620b32edc8d514796 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 08:01:29 +0100 Subject: [PATCH 49/99] End refactoring of ContributionResolver.test. --- .../resolver/ContributionResolver.test.ts | 147 ++++++++++++++---- 1 file changed, 113 insertions(+), 34 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 226ab63cd..a7b6716db 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -118,17 +118,10 @@ describe('ContributionResolver', () => { mutation: createContribution, variables: { amount: 100.0, - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', creationDate: new Date().toString(), }, }) - await mutate({ - mutation: adminCreateContributionMessage, - variables: { - contributionId: inProgressContribution.data.createContribution.id, - message: 'Test message to IN_PROGESS contribution', - }, - }) contributionToConfirm = await mutate({ mutation: createContribution, variables: { @@ -153,6 +146,17 @@ describe('ContributionResolver', () => { creationDate: new Date().toString(), }, }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: adminCreateContributionMessage, + variables: { + contributionId: inProgressContribution.data.createContribution.id, + message: 'Test message to IN_PROGRESS contribution', + }, + }) await mutate({ mutation: logout, }) @@ -770,11 +774,18 @@ describe('ContributionResolver', () => { }) describe('other user sends a deleteContribution', () => { - it('returns an error', async () => { + beforeAll(async () => { await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) + }) + + afterAll(() => { + resetToken() + }) + + it('returns an error', async () => { await expect( mutate({ mutation: deleteContribution, @@ -795,6 +806,17 @@ describe('ContributionResolver', () => { }) describe('User deletes own contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(() => { + resetToken() + }) + it('deletes successfully', async () => { await expect( mutate({ @@ -803,10 +825,21 @@ describe('ContributionResolver', () => { id: contributionToDelete.data.createContribution.id, }, }), - ).resolves.toBeTruthy() + ).resolves.toEqual( + expect.objectContaining({ + data: { + deleteContribution: true, + }, + }), + ) }) it('stores the delete contribution event in the database', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + const contribution = await mutate({ mutation: createContribution, variables: { @@ -945,7 +978,7 @@ describe('ContributionResolver', () => { }), expect.objectContaining({ id: expect.any(Number), - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -979,11 +1012,36 @@ describe('ContributionResolver', () => { listContributions: { contributionCount: 4, contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + amount: '100', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to deny', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), memo: 'Test contribution to delete', amount: '100', }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), ]), }, }, @@ -1110,7 +1168,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 6, + contributionCount: 5, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1139,7 +1197,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1170,7 +1228,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 6, + contributionCount: 5, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1199,7 +1257,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1230,7 +1288,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 6, + contributionCount: 5, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1259,7 +1317,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1319,7 +1377,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1379,7 +1437,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.not.objectContaining({ @@ -1439,7 +1497,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.not.objectContaining({ @@ -1499,7 +1557,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.not.objectContaining({ @@ -1582,7 +1640,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -2009,7 +2067,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (2000 GDD) to be created exceeds the amount (900 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.', ), ], }), @@ -2018,7 +2076,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (2000 GDD) to be created exceeds the amount (900 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.', ) }) }) @@ -2031,7 +2089,7 @@ describe('ContributionResolver', () => { ).resolves.toEqual( expect.objectContaining({ data: { - adminCreateContribution: [1000, 1000, 700], + adminCreateContribution: [1000, 1000, 590], }, }), ) @@ -2056,7 +2114,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1000 GDD) to be created exceeds the amount (700 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.', ), ], }), @@ -2065,7 +2123,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1000 GDD) to be created exceeds the amount (700 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.', ) }) }) @@ -2210,7 +2268,6 @@ describe('ContributionResolver', () => { describe('user email does not match creation user', () => { it('throws an error', async () => { jest.clearAllMocks() - console.log('creation', creation) await expect( mutate({ mutation: adminUpdateContribution, @@ -2377,7 +2434,7 @@ describe('ContributionResolver', () => { memo: 'Das war leider zu Viel!', amount: '200', moderator: admin.id, - creation: ['1000', '600', '500'], + creation: ['1000', '800', '500'], }), expect.objectContaining({ id: expect.any(Number), @@ -2388,9 +2445,9 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '600', '500'], + creation: ['1000', '800', '500'], }), - expect.objectContaining({ + expect.not.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2399,7 +2456,29 @@ describe('ContributionResolver', () => { memo: 'Test contribution to delete', amount: '100', moderator: null, - creation: ['1000', '1000', '200'], + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ + id: expect.any(Number), + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Test PENDING contribution update', + amount: '10', + moderator: null, + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ + id: expect.any(Number), + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Test IN_PROGRESS contribution', + amount: '100', + moderator: null, + creation: ['1000', '1000', '90'], }), expect.objectContaining({ id: expect.any(Number), @@ -2410,7 +2489,7 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '1000', '200'], + creation: ['1000', '1000', '90'], }), expect.objectContaining({ id: expect.any(Number), @@ -2421,7 +2500,7 @@ describe('ContributionResolver', () => { memo: 'Aktives Grundeinkommen', amount: '200', moderator: admin.id, - creation: ['1000', '1000', '200'], + creation: ['1000', '1000', '90'], }), ]), }, From b7343fdc3e950d1b5988dd4d2ac2c97f1f45a7c3 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 09:25:44 +0100 Subject: [PATCH 50/99] Add test user raeuber hotzenplotz and deny already confirmed, deleted or denied contributions. --- .../resolver/ContributionResolver.test.ts | 263 +++++++++++++++++- 1 file changed, 257 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index a7b6716db..eaf157edc 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -49,6 +49,7 @@ import { User } from '@entity/User' import { EventProtocolType } from '@/event/EventProtocolType' import { logger, i18n as localization } from '@test/testSetup' import { UserInputError } from 'apollo-server-express' +import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz' // mock account activation email to avoid console spam // mock account activation email to avoid console spam @@ -94,11 +95,13 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any + let raueber: any let peter: any beforeAll(async () => { bibi = await userFactory(testEnv, bibiBloxberg) admin = peter = await userFactory(testEnv, peterLustig) + raueber = await userFactory(testEnv, raeuberHotzenplotz) const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await creationFactory(testEnv, bibisCreation!) @@ -697,6 +700,158 @@ describe('ContributionResolver', () => { }) }) + describe('deny contribution that is already confirmed', () => { + let contribution: any + it('throws an error', async () => { + await mutate({ + mutation: login, + variables: { email: 'raeuber@hotzenplotz.de', password: 'Aa12345_' }, + }) + + contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + await mutate({ + mutation: confirmContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + `Contribution not found for given id: ${contribution.data.createContribution.id}`, + ) + }) + }) + + describe('deny contribution that is already deleted', () => { + let contribution: any + + it('throws an error', async () => { + await mutate({ + mutation: login, + variables: { email: 'raeuber@hotzenplotz.de', password: 'Aa12345_' }, + }) + + contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + + await mutate({ + mutation: deleteContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + `Contribution not found for given id: ${contribution.data.createContribution.id}`, + ) + }) + }) + + describe('deny contribution that is already denied', () => { + let contribution: any + + it('throws an error', async () => { + await mutate({ + mutation: login, + variables: { email: 'raeuber@hotzenplotz.de', password: 'Aa12345_' }, + }) + + contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + `Contribution not found for given id: ${contribution.data.createContribution.id}`, + ) + }) + }) + describe('valid input', () => { it('deny contribution', async () => { await mutate({ @@ -1168,7 +1323,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 5, + contributionCount: 7, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1206,6 +1361,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1228,7 +1395,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 5, + contributionCount: 7, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1266,6 +1433,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1288,7 +1467,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 5, + contributionCount: 7, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1326,6 +1505,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1348,7 +1539,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 3, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1386,6 +1577,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1446,6 +1649,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1506,6 +1721,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1528,7 +1755,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 1, + contributionCount: 2, contributionList: expect.arrayContaining([ expect.not.objectContaining({ amount: '100', @@ -1566,6 +1793,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1611,7 +1850,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 3, + contributionCount: 4, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1649,6 +1888,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, From 46e8a55d2e69cec40c2ff1e6257506b60b26dcd7 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 09:48:20 +0100 Subject: [PATCH 51/99] remove unused variable raeuber. --- backend/src/graphql/resolver/ContributionResolver.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index eaf157edc..7c239e699 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -95,13 +95,12 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any - let raueber: any let peter: any beforeAll(async () => { bibi = await userFactory(testEnv, bibiBloxberg) admin = peter = await userFactory(testEnv, peterLustig) - raueber = await userFactory(testEnv, raeuberHotzenplotz) + await userFactory(testEnv, raeuberHotzenplotz) const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await creationFactory(testEnv, bibisCreation!) From e1bf7b20f4dbb7fcf65554892158829652e46118 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 7 Feb 2023 07:53:12 +0100 Subject: [PATCH 52/99] set data-test attribute in forgot password page avoiding v-if --- frontend/src/pages/ForgotPassword.vue | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/ForgotPassword.vue b/frontend/src/pages/ForgotPassword.vue index 70d578e10..a7cf81c71 100644 --- a/frontend/src/pages/ForgotPassword.vue +++ b/frontend/src/pages/ForgotPassword.vue @@ -24,20 +24,11 @@ -
From 2214ce90e42d3e40452cf2d18826dcc289a4d7da Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 7 Feb 2023 08:11:26 +0100 Subject: [PATCH 53/99] linting --- frontend/src/pages/ForgotPassword.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/ForgotPassword.vue b/frontend/src/pages/ForgotPassword.vue index a7cf81c71..692f2da93 100644 --- a/frontend/src/pages/ForgotPassword.vue +++ b/frontend/src/pages/ForgotPassword.vue @@ -26,7 +26,7 @@ From 3beb5cad624bfb8dd2b650ae9b6116308c6e3300 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 7 Feb 2023 08:11:26 +0100 Subject: [PATCH 54/99] linting --- frontend/src/pages/ForgotPassword.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/ForgotPassword.vue b/frontend/src/pages/ForgotPassword.vue index 692f2da93..e014592ca 100644 --- a/frontend/src/pages/ForgotPassword.vue +++ b/frontend/src/pages/ForgotPassword.vue @@ -1,3 +1,4 @@ +