From e844bcc925080239ecb445ff048d509760ec6cff Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 11:47:28 +0200 Subject: [PATCH 01/15] Change login from Query to Mutation. --- backend/src/graphql/resolver/UserResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index f2fd048fc..5b4ad6cdc 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -265,7 +265,7 @@ export class UserResolver { } @Authorized([RIGHTS.LOGIN]) - @Query(() => User) + @Mutation(() => User) @UseMiddleware(klicktippNewsletterStateMiddleware) async login( @Args() { email, password, publisherId }: UnsecureLoginArgs, From f63bfa871a3497c861cb49f3592610a80943d9be Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 11:47:48 +0200 Subject: [PATCH 02/15] Change login call from query to mutation. --- frontend/src/graphql/mutations.js | 18 ++++++++++++++++++ frontend/src/graphql/queries.js | 18 ------------------ frontend/src/pages/Login.vue | 7 +++---- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index 9846784d5..2a2eb6353 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -136,3 +136,21 @@ export const createContributionMessage = gql` } } ` + +export const login = gql` + mutation($email: String!, $password: String!, $publisherId: Int) { + login(email: $email, password: $password, publisherId: $publisherId) { + email + firstName + lastName + language + klickTipp { + newsletterState + } + hasElopage + publisherId + isAdmin + creation + } + } +` diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 07b016d0a..a0964aef4 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -1,23 +1,5 @@ import gql from 'graphql-tag' -export const login = gql` - query($email: String!, $password: String!, $publisherId: Int) { - login(email: $email, password: $password, publisherId: $publisherId) { - email - firstName - lastName - language - klickTipp { - newsletterState - } - hasElopage - publisherId - isAdmin - creation - } - } -` - export const verifyLogin = gql` query { verifyLogin { diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 6a3db4e39..0b602f74b 100755 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -43,7 +43,7 @@ import InputPassword from '@/components/Inputs/InputPassword' import InputEmail from '@/components/Inputs/InputEmail' import Message from '@/components/Message/Message' -import { login } from '@/graphql/queries' +import { login } from '@/graphql/mutations' export default { name: 'Login', @@ -71,14 +71,13 @@ export default { container: this.$refs.submitButton, }) this.$apollo - .query({ - query: login, + .mutate({ + mutation: login, variables: { email: this.form.email, password: this.form.password, publisherId: this.$store.state.publisherId, }, - fetchPolicy: 'network-only', }) .then(async (result) => { const { From 2d4f88ce844c549f7393e25038bc8d0a01c7f161 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 11:53:54 +0200 Subject: [PATCH 03/15] Refactor logout method from query to mutation. --- backend/src/graphql/resolver/UserResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 5b4ad6cdc..89d524057 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -329,7 +329,7 @@ export class UserResolver { } @Authorized([RIGHTS.LOGOUT]) - @Query(() => String) + @Mutation(() => String) async logout(): Promise { // TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token. // Furthermore this hook can be useful for tracking user behaviour (did he logout or not? Warn him if he didn't on next login) From 4fdc492a6ca58326673082922ed118c61e9eccd4 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 11:54:36 +0200 Subject: [PATCH 04/15] Change query logout to mutation, call of mutation instead of query. --- frontend/src/graphql/mutations.js | 6 ++++++ frontend/src/graphql/queries.js | 6 ------ frontend/src/layouts/DashboardLayout.vue | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index 2a2eb6353..3156c2861 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -154,3 +154,9 @@ export const login = gql` } } ` + +export const logout = gql` + mutation { + logout + } +` diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index a0964aef4..f772aa931 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -18,12 +18,6 @@ export const verifyLogin = gql` } ` -export const logout = gql` - query { - logout - } -` - export const transactionsQuery = gql` query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) { transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) { diff --git a/frontend/src/layouts/DashboardLayout.vue b/frontend/src/layouts/DashboardLayout.vue index 8e778ab01..bf33daa83 100755 --- a/frontend/src/layouts/DashboardLayout.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -41,7 +41,8 @@ import Navbar from '@/components/Menu/Navbar.vue' import Sidebar from '@/components/Menu/Sidebar.vue' import SessionLogoutTimeout from '@/components/SessionLogoutTimeout.vue' -import { logout, transactionsQuery } from '@/graphql/queries' +import { transactionsQuery } from '@/graphql/queries' +import { logout } from '@/graphql/mutations' import ContentFooter from '@/components/ContentFooter.vue' import { FadeTransition } from 'vue2-transitions' import CONFIG from '@/config' @@ -75,14 +76,15 @@ export default { methods: { async logout() { this.$apollo - .query({ - query: logout, + .mutate({ + mutation: logout, }) .then(() => { this.$store.dispatch('logout') this.$router.push('/login') }) .catch(() => { + console.log('TESTTEST') this.$store.dispatch('logout') if (this.$router.currentRoute.path !== '/login') this.$router.push('/login') }) From 4461ff92a5d6c5d337003ecb4d751518c187bdab Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 12:22:56 +0200 Subject: [PATCH 05/15] Correct tests after changing login and logout from queries to mutations. --- .../graphql/resolver/AdminResolver.test.ts | 58 +++++++++---------- .../ContributionMessageResolver.test.ts | 31 +++++----- .../resolver/ContributionResolver.test.ts | 43 +++++++------- .../src/graphql/resolver/UserResolver.test.ts | 41 +++++++------ backend/src/seeds/factory/contributionLink.ts | 7 +-- backend/src/seeds/factory/creation.ts | 7 +-- backend/src/seeds/factory/transactionLink.ts | 10 ++-- backend/src/seeds/graphql/mutations.ts | 24 ++++++++ backend/src/seeds/graphql/queries.ts | 24 -------- 9 files changed, 127 insertions(+), 118 deletions(-) diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 75c672bd5..8b91f3fd9 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -13,6 +13,7 @@ import { peterLustig } from '@/seeds/users/peter-lustig' import { stephenHawking } from '@/seeds/users/stephen-hawking' import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { + login, setUserRole, deleteUser, unDeleteUser, @@ -27,7 +28,6 @@ import { } from '@/seeds/graphql/mutations' import { listUnconfirmedContributions, - login, searchUsers, listTransactionLinksAdmin, listContributionLinks, @@ -96,8 +96,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -121,8 +121,8 @@ describe('AdminResolver', () => { describe('with admin rights', () => { beforeAll(async () => { admin = await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -249,8 +249,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -274,8 +274,8 @@ describe('AdminResolver', () => { describe('with admin rights', () => { beforeAll(async () => { admin = await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -357,8 +357,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -382,8 +382,8 @@ describe('AdminResolver', () => { describe('with admin rights', () => { beforeAll(async () => { admin = await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -469,8 +469,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -514,8 +514,8 @@ describe('AdminResolver', () => { beforeAll(async () => { admin = await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) @@ -766,8 +766,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -875,8 +875,8 @@ describe('AdminResolver', () => { describe('with admin rights', () => { beforeAll(async () => { admin = await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -1553,8 +1553,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -1599,8 +1599,8 @@ describe('AdminResolver', () => { } // admin: only now log in - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -1859,8 +1859,8 @@ describe('AdminResolver', () => { describe('without admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -1933,8 +1933,8 @@ describe('AdminResolver', () => { describe('with admin rights', () => { beforeAll(async () => { user = await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index 40e9e2ace..bc85c9d58 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -7,8 +7,9 @@ import { adminCreateContributionMessage, createContribution, createContributionMessage, + login, } from '@/seeds/graphql/mutations' -import { listContributionMessages, login } from '@/seeds/graphql/queries' +import { listContributionMessages } from '@/seeds/graphql/queries' import { userFactory } from '@/seeds/factory/user' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' @@ -59,8 +60,8 @@ describe('ContributionMessageResolver', () => { beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) result = await mutate({ @@ -71,8 +72,8 @@ describe('ContributionMessageResolver', () => { creationDate: new Date().toString(), }, }) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -103,8 +104,8 @@ describe('ContributionMessageResolver', () => { }) it('throws error when contribution.userId equals user.id', async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) const result2 = await mutate({ @@ -195,8 +196,8 @@ describe('ContributionMessageResolver', () => { describe('authenticated', () => { beforeAll(async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -227,8 +228,8 @@ describe('ContributionMessageResolver', () => { }) it('throws error when other user tries to send createContributionMessage', async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) await expect( @@ -253,8 +254,8 @@ describe('ContributionMessageResolver', () => { describe('valid input', () => { beforeAll(async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -304,8 +305,8 @@ describe('ContributionMessageResolver', () => { describe('authenticated', () => { beforeAll(async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 20f11ff9a..92546e004 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -8,8 +8,9 @@ import { createContribution, deleteContribution, updateContribution, + login, } from '@/seeds/graphql/mutations' -import { listAllContributions, listContributions, login } from '@/seeds/graphql/queries' +import { listAllContributions, listContributions } from '@/seeds/graphql/queries' import { cleanDB, resetToken, testEnvironment } from '@test/helpers' import { GraphQLError } from 'graphql' import { userFactory } from '@/seeds/factory/user' @@ -54,8 +55,8 @@ describe('ContributionResolver', () => { describe('authenticated with valid user', () => { beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -197,8 +198,8 @@ describe('ContributionResolver', () => { 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 query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) await mutate({ @@ -310,8 +311,8 @@ describe('ContributionResolver', () => { beforeAll(async () => { await userFactory(testEnv, peterLustig) await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) result = await mutate({ @@ -393,8 +394,8 @@ describe('ContributionResolver', () => { describe('wrong user tries to update the contribution', () => { beforeAll(async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) @@ -445,8 +446,8 @@ describe('ContributionResolver', () => { describe('update too much so that the limit is exceeded', () => { beforeAll(async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) @@ -553,8 +554,8 @@ describe('ContributionResolver', () => { 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 query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) await mutate({ @@ -630,8 +631,8 @@ describe('ContributionResolver', () => { beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) result = await mutate({ @@ -668,8 +669,8 @@ describe('ContributionResolver', () => { describe('other user sends a deleteContribtuion', () => { it('returns an error', async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) await expect( @@ -702,8 +703,8 @@ describe('ContributionResolver', () => { describe('User deletes already confirmed contribution', () => { it('throws an error', async () => { - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) await mutate({ @@ -712,8 +713,8 @@ describe('ContributionResolver', () => { id: result.data.createContribution.id, }, }) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) await expect( diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 13715e088..f7671814c 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -4,8 +4,15 @@ import { testEnvironment, headerPushMock, resetToken, cleanDB, resetEntity } from '@test/helpers' import { userFactory } from '@/seeds/factory/user' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' -import { createUser, setPassword, forgotPassword, updateUserInfos } from '@/seeds/graphql/mutations' -import { login, logout, verifyLogin, queryOptIn, searchAdminUsers } from '@/seeds/graphql/queries' +import { + login, + logout, + createUser, + setPassword, + forgotPassword, + updateUserInfos, +} from '@/seeds/graphql/mutations' +import { verifyLogin, queryOptIn, searchAdminUsers } from '@/seeds/graphql/queries' import { GraphQLError } from 'graphql' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { User } from '@entity/User' @@ -459,7 +466,7 @@ bei Gradidio sei dabei!`, describe('no users in database', () => { beforeAll(async () => { - result = await query({ query: login, variables }) + result = await mutate({ mutation: login, variables }) }) it('throws an error', () => { @@ -478,7 +485,7 @@ bei Gradidio sei dabei!`, describe('user is in database and correct login data', () => { beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) - result = await query({ query: login, variables }) + result = await mutate({ mutation: login, variables }) }) afterAll(async () => { @@ -515,7 +522,7 @@ bei Gradidio sei dabei!`, describe('user is in database and wrong password', () => { beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) - result = await query({ query: login, variables: { ...variables, password: 'wrong' } }) + result = await mutate({ mutation: login, variables: { ...variables, password: 'wrong' } }) }) afterAll(async () => { @@ -540,7 +547,7 @@ bei Gradidio sei dabei!`, describe('unauthenticated', () => { it('throws an error', async () => { resetToken() - await expect(query({ query: logout })).resolves.toEqual( + await expect(mutate({ mutation: logout })).resolves.toEqual( expect.objectContaining({ errors: [new GraphQLError('401 Unauthorized')], }), @@ -556,7 +563,7 @@ bei Gradidio sei dabei!`, beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) - await query({ query: login, variables }) + await mutate({ mutation: login, variables }) }) afterAll(async () => { @@ -564,7 +571,7 @@ bei Gradidio sei dabei!`, }) it('returns true', async () => { - await expect(query({ query: logout })).resolves.toEqual( + await expect(mutate({ mutation: logout })).resolves.toEqual( expect.objectContaining({ data: { logout: 'true' }, errors: undefined, @@ -613,7 +620,7 @@ bei Gradidio sei dabei!`, } beforeAll(async () => { - await query({ query: login, variables }) + await mutate({ mutation: login, variables }) user = await User.find() }) @@ -781,8 +788,8 @@ bei Gradidio sei dabei!`, describe('authenticated', () => { beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_', @@ -913,8 +920,8 @@ bei Gradidio sei dabei!`, it('can login with new password', async () => { await expect( - query({ - query: login, + mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Bb12345_', @@ -933,8 +940,8 @@ bei Gradidio sei dabei!`, it('cannot login with old password', async () => { await expect( - query({ - query: login, + mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_', @@ -971,8 +978,8 @@ bei Gradidio sei dabei!`, beforeAll(async () => { await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, peterLustig) - await query({ - query: login, + await mutate({ + mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_', diff --git a/backend/src/seeds/factory/contributionLink.ts b/backend/src/seeds/factory/contributionLink.ts index 5c83b6ad3..c2b4b9bf3 100644 --- a/backend/src/seeds/factory/contributionLink.ts +++ b/backend/src/seeds/factory/contributionLink.ts @@ -1,6 +1,5 @@ import { ApolloServerTestClient } from 'apollo-server-testing' -import { createContributionLink } from '@/seeds/graphql/mutations' -import { login } from '@/seeds/graphql/queries' +import { login, createContributionLink } from '@/seeds/graphql/mutations' import { ContributionLink } from '@model/ContributionLink' import { ContributionLinkInterface } from '@/seeds/contributionLink/ContributionLinkInterface' @@ -8,10 +7,10 @@ export const contributionLinkFactory = async ( client: ApolloServerTestClient, contributionLink: ContributionLinkInterface, ): Promise => { - const { mutate, query } = client + const { mutate } = client // login as admin - await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } }) + await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } }) const variables = { amount: contributionLink.amount, diff --git a/backend/src/seeds/factory/creation.ts b/backend/src/seeds/factory/creation.ts index d3f0f78ca..e93faa372 100644 --- a/backend/src/seeds/factory/creation.ts +++ b/backend/src/seeds/factory/creation.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { adminCreateContribution, confirmContribution } from '@/seeds/graphql/mutations' -import { login } from '@/seeds/graphql/queries' +import { login, adminCreateContribution, confirmContribution } from '@/seeds/graphql/mutations' import { CreationInterface } from '@/seeds/creation/CreationInterface' import { ApolloServerTestClient } from 'apollo-server-testing' import { User } from '@entity/User' @@ -18,9 +17,9 @@ export const creationFactory = async ( client: ApolloServerTestClient, creation: CreationInterface, ): Promise => { - const { mutate, query } = client + const { mutate } = client - await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } }) + await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } }) // TODO it would be nice to have this mutation return the id await mutate({ mutation: adminCreateContribution, variables: { ...creation } }) diff --git a/backend/src/seeds/factory/transactionLink.ts b/backend/src/seeds/factory/transactionLink.ts index 2f54dc70c..be5a01d22 100644 --- a/backend/src/seeds/factory/transactionLink.ts +++ b/backend/src/seeds/factory/transactionLink.ts @@ -1,6 +1,5 @@ import { ApolloServerTestClient } from 'apollo-server-testing' -import { createTransactionLink } from '@/seeds/graphql/mutations' -import { login } from '@/seeds/graphql/queries' +import { login, createTransactionLink } from '@/seeds/graphql/mutations' import { TransactionLinkInterface } from '@/seeds/transactionLink/TransactionLinkInterface' import { transactionLinkExpireDate } from '@/graphql/resolver/TransactionLinkResolver' import { TransactionLink } from '@entity/TransactionLink' @@ -9,10 +8,13 @@ export const transactionLinkFactory = async ( client: ApolloServerTestClient, transactionLink: TransactionLinkInterface, ): Promise => { - const { mutate, query } = client + const { mutate } = client // login - await query({ query: login, variables: { email: transactionLink.email, password: 'Aa12345_' } }) + await mutate({ + mutation: login, + variables: { email: transactionLink.email, password: 'Aa12345_' }, + }) const variables = { amount: transactionLink.amount, diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index e5f290645..1d47b61d9 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -289,3 +289,27 @@ export const adminCreateContributionMessage = gql` } } ` + +export const login = gql` + mutation ($email: String!, $password: String!, $publisherId: Int) { + login(email: $email, password: $password, publisherId: $publisherId) { + id + email + firstName + lastName + language + klickTipp { + newsletterState + } + hasElopage + publisherId + isAdmin + } + } +` + +export const logout = gql` + mutation { + logout + } +` diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 60dffa21b..97f871235 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -1,23 +1,5 @@ import gql from 'graphql-tag' -export const login = gql` - query ($email: String!, $password: String!, $publisherId: Int) { - login(email: $email, password: $password, publisherId: $publisherId) { - id - email - firstName - lastName - language - klickTipp { - newsletterState - } - hasElopage - publisherId - isAdmin - } - } -` - export const verifyLogin = gql` query { verifyLogin { @@ -35,12 +17,6 @@ export const verifyLogin = gql` } ` -export const logout = gql` - query { - logout - } -` - export const queryOptIn = gql` query ($optIn: String!) { queryOptIn(optIn: $optIn) From 98aca9ff524f04775a301c66aa144d9da6cf39d0 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 12:29:03 +0200 Subject: [PATCH 06/15] Change the tests setup to the call of mutations instead of queries. --- frontend/src/layouts/DashboardLayout.spec.js | 10 ++++++---- frontend/src/layouts/DashboardLayout.vue | 1 - frontend/src/pages/Login.spec.js | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/src/layouts/DashboardLayout.spec.js b/frontend/src/layouts/DashboardLayout.spec.js index 398724201..846974781 100644 --- a/frontend/src/layouts/DashboardLayout.spec.js +++ b/frontend/src/layouts/DashboardLayout.spec.js @@ -18,6 +18,7 @@ const apolloMock = jest.fn().mockResolvedValue({ logout: 'success', }, }) +const apolloQueryMock = jest.fn() describe('DashboardLayout', () => { let wrapper @@ -40,7 +41,8 @@ describe('DashboardLayout', () => { }, }, $apollo: { - query: apolloMock, + mutate: apolloMock, + query: apolloQueryMock, }, $store: { state: { @@ -142,7 +144,7 @@ describe('DashboardLayout', () => { describe('update transactions', () => { beforeEach(async () => { - apolloMock.mockResolvedValue({ + apolloQueryMock.mockResolvedValue({ data: { transactionList: { balance: { @@ -163,7 +165,7 @@ describe('DashboardLayout', () => { }) it('calls the API', () => { - expect(apolloMock).toBeCalledWith( + expect(apolloQueryMock).toBeCalledWith( expect.objectContaining({ variables: { currentPage: 2, @@ -201,7 +203,7 @@ describe('DashboardLayout', () => { describe('update transactions returns error', () => { beforeEach(async () => { - apolloMock.mockRejectedValue({ + apolloQueryMock.mockRejectedValue({ message: 'Ouch!', }) await wrapper diff --git a/frontend/src/layouts/DashboardLayout.vue b/frontend/src/layouts/DashboardLayout.vue index bf33daa83..2a103a574 100755 --- a/frontend/src/layouts/DashboardLayout.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -84,7 +84,6 @@ export default { this.$router.push('/login') }) .catch(() => { - console.log('TESTTEST') this.$store.dispatch('logout') if (this.$router.currentRoute.path !== '/login') this.$router.push('/login') }) diff --git a/frontend/src/pages/Login.spec.js b/frontend/src/pages/Login.spec.js index 6359d07c6..90e98cd44 100644 --- a/frontend/src/pages/Login.spec.js +++ b/frontend/src/pages/Login.spec.js @@ -5,7 +5,7 @@ import Login from './Login' const localVue = global.localVue -const apolloQueryMock = jest.fn() +const apolloMutateMock = jest.fn() const mockStoreDispach = jest.fn() const mockStoreCommit = jest.fn() const mockRouterPush = jest.fn() @@ -41,7 +41,7 @@ describe('Login', () => { params: {}, }, $apollo: { - query: apolloQueryMock, + mutate: apolloMutateMock, }, } @@ -113,7 +113,7 @@ describe('Login', () => { await wrapper.find('input[placeholder="Email"]').setValue('user@example.org') await wrapper.find('input[placeholder="form.password"]').setValue('1234') await flushPromises() - apolloQueryMock.mockResolvedValue({ + apolloMutateMock.mockResolvedValue({ data: { login: 'token', }, @@ -123,7 +123,7 @@ describe('Login', () => { }) it('calls the API with the given data', () => { - expect(apolloQueryMock).toBeCalledWith( + expect(apolloMutateMock).toBeCalledWith( expect.objectContaining({ variables: { email: 'user@example.org', @@ -175,7 +175,7 @@ describe('Login', () => { describe('login fails', () => { const createError = async (errorMessage) => { - apolloQueryMock.mockRejectedValue({ + apolloMutateMock.mockRejectedValue({ message: errorMessage, }) wrapper = Wrapper() From 61f59a259426bb784f97d4878fb2f5585c000998 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 22 Sep 2022 12:34:03 +0200 Subject: [PATCH 07/15] Remove query of contribution message resolver test since it is never used. --- .../src/graphql/resolver/ContributionMessageResolver.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index bc85c9d58..612c2d20b 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -22,14 +22,13 @@ jest.mock('@/mailer/sendAddedContributionMessageEmail', () => { } }) -let mutate: any, query: any, con: any +let mutate: any, con: any let testEnv: any let result: any beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate - query = testEnv.query con = testEnv.con await cleanDB() }) From 0b7313d21667adfa1e3db24067951fd7c9bfa1df Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 6 Oct 2022 18:05:04 +0200 Subject: [PATCH 08/15] add checking token expiration time to TransactionLink.vue --- frontend/src/pages/TransactionLink.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/TransactionLink.vue b/frontend/src/pages/TransactionLink.vue index bd1909d7d..c3875d20e 100644 --- a/frontend/src/pages/TransactionLink.vue +++ b/frontend/src/pages/TransactionLink.vue @@ -103,6 +103,12 @@ export default { isContributionLink() { return this.$route.params.code.search(/^CL-/) === 0 }, + tokenExpiresInSeconds() { + const remainingSecs = Math.floor( + (new Date(this.$store.state.tokenTime * 1000).getTime() - new Date().getTime()) / 1000, + ) + return remainingSecs <= 0 ? 0 : remainingSecs + }, itemType() { // link is deleted: at, from if (this.linkData.deletedAt) { @@ -130,7 +136,9 @@ export default { return `TEXT` } - if (this.$store.state.token) { + if (this.$store.state.token && this.$store.state.tokenTime) { + if (this.tokenExpiresInSeconds < 5) return `LOGGED_OUT` + // logged in, nicht berechtigt einzulösen, eigener link if (this.linkData.user && this.$store.state.email === this.linkData.user.email) { return `SELF_CREATOR` From 6bf1eec46337b68f77785ebcd4e47ad562c1926d Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 7 Oct 2022 10:36:40 +0200 Subject: [PATCH 09/15] add node module jsonwebtoken (required for testing) to package.json --- frontend/package.json | 1 + frontend/yarn.lock | 80 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/frontend/package.json b/frontend/package.json index 011193b58..db1f2c99a 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,7 @@ "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", "jest-canvas-mock": "^2.3.1", + "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.1.2", "portal-vue": "^2.1.7", "prettier": "^2.2.1", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fe7ab7947..715b2504d 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4376,6 +4376,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@1.x: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -5946,6 +5951,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -9659,6 +9671,22 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -9669,6 +9697,23 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jwt-decode@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" @@ -9863,6 +9908,36 @@ lodash.defaultsdeep@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -9878,6 +9953,11 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" From 1fd476492825fdc77332edb3273de16e6dcb43b4 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 7 Oct 2022 10:44:15 +0200 Subject: [PATCH 10/15] adapt unit tests to changes in page TransactionLink.vue --- frontend/src/pages/TransactionLink.spec.js | 232 +++++++++++++-------- 1 file changed, 140 insertions(+), 92 deletions(-) diff --git a/frontend/src/pages/TransactionLink.spec.js b/frontend/src/pages/TransactionLink.spec.js index d5a75aa4a..8a8af2e1c 100644 --- a/frontend/src/pages/TransactionLink.spec.js +++ b/frontend/src/pages/TransactionLink.spec.js @@ -3,6 +3,8 @@ import TransactionLink from './TransactionLink' import { queryTransactionLink } from '@/graphql/queries' import { redeemTransactionLink } from '@/graphql/mutations' import { toastSuccessSpy, toastErrorSpy } from '@test/testSetup' +import jwt from 'jsonwebtoken' +import jwtDecode from 'jwt-decode' const localVue = global.localVue @@ -43,6 +45,7 @@ const mocks = { $store: { state: { token: null, + tokenTime: null, email: 'bibi@bloxberg.de', }, }, @@ -68,7 +71,7 @@ describe('TransactionLink', () => { } describe('mount', () => { - beforeEach(() => { + beforeAll(() => { jest.clearAllMocks() wrapper = Wrapper() }) @@ -214,112 +217,157 @@ describe('TransactionLink', () => { }) }) - describe('token in store and own link', () => { - beforeEach(() => { - mocks.$store.state.token = 'token' - apolloQueryMock.mockResolvedValue({ - data: { - queryTransactionLink: { - __typename: 'TransactionLink', - id: 92, - amount: '22', - memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', - createdAt: '2022-03-17T16:10:28.000Z', - validUntil: transactionLinkValidExpireDate(), - redeemedAt: null, - deletedAt: null, - user: { firstName: 'Bibi', publisherId: 0, email: 'bibi@bloxberg.de' }, - }, - }, - }) - wrapper = Wrapper() + describe('token in store', () => { + beforeAll(() => { + mocks.$store.state.token = jwt.sign({ data: 'test' }, 'secret', { expiresIn: '1h' }); }) - it('has a RedeemSelfCreator component', () => { - expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).exists()).toBe(true) - }) - - it('has a no redeem text', () => { - expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).text()).toContain( - 'gdd_per_link.no-redeem', - ) - }) - - it.skip('has a link to transaction page', () => { - expect(wrapper.find('a[target="/transactions"]').exists()).toBe(true) - }) - }) - - describe('valid link', () => { - beforeEach(() => { - mocks.$store.state.token = 'token' - apolloQueryMock.mockResolvedValue({ - data: { - queryTransactionLink: { - __typename: 'TransactionLink', - id: 92, - amount: '22', - memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', - createdAt: '2022-03-17T16:10:28.000Z', - validUntil: transactionLinkValidExpireDate(), - redeemedAt: null, - deletedAt: null, - user: { firstName: 'Peter', publisherId: 0, email: 'peter@listig.de' }, - }, - }, - }) - wrapper = Wrapper() - }) - - it('has a RedeemValid component', () => { - expect(wrapper.findComponent({ name: 'RedeemValid' }).exists()).toBe(true) - }) - - it('has a button with redeem text', () => { - expect(wrapper.findComponent({ name: 'RedeemValid' }).find('button').text()).toBe( - 'gdd_per_link.redeem', - ) - }) - - describe('redeem link with success', () => { - beforeEach(async () => { - apolloMutateMock.mockResolvedValue() - await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click') + describe('sufficient token time in store', () => { + beforeAll(() => { + mocks.$store.state.tokenTime = jwtDecode(mocks.$store.state.token).exp // TODO set a sufficient token Time }) - it('calls the API', () => { - expect(apolloMutateMock).toBeCalledWith( - expect.objectContaining({ - mutation: redeemTransactionLink, - variables: { - code: 'some-code', + describe('own link', () => { + beforeAll(() => { + apolloQueryMock.mockResolvedValue({ + data: { + queryTransactionLink: { + __typename: 'TransactionLink', + id: 92, + amount: '22', + memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', + createdAt: '2022-03-17T16:10:28.000Z', + validUntil: transactionLinkValidExpireDate(), + redeemedAt: null, + deletedAt: null, + user: { firstName: 'Bibi', publisherId: 0, email: 'bibi@bloxberg.de' }, + }, }, - }), - ) + }) + wrapper = Wrapper() + }) + + it('has a RedeemSelfCreator component', () => { + expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).exists()).toBe(true) + }) + + it('has a no redeem text', () => { + expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).text()).toContain( + 'gdd_per_link.no-redeem', + ) + }) + + it.skip('has a link to transaction page', () => { + expect(wrapper.find('a[target="/transactions"]').exists()).toBe(true) + }) }) - it('toasts a success message', () => { - expect(mocks.$t).toBeCalledWith('gdd_per_link.redeem') - expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.redeemed; ') - }) + describe('valid link', () => { + beforeAll(() => { + apolloQueryMock.mockResolvedValue({ + data: { + queryTransactionLink: { + __typename: 'TransactionLink', + id: 92, + amount: '22', + memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', + createdAt: '2022-03-17T16:10:28.000Z', + validUntil: transactionLinkValidExpireDate(), + redeemedAt: null, + deletedAt: null, + user: { firstName: 'Peter', publisherId: 0, email: 'peter@listig.de' }, + }, + }, + }) + wrapper = Wrapper() + }) - it('pushes the route to overview', () => { - expect(routerPushMock).toBeCalledWith('/overview') + it('has a RedeemValid component', () => { + expect(wrapper.findComponent({ name: 'RedeemValid' }).exists()).toBe(true) + }) + + it('has a button with redeem text', () => { + expect(wrapper.findComponent({ name: 'RedeemValid' }).find('button').text()).toBe( + 'gdd_per_link.redeem', + ) + }) + + describe('redeem link with success', () => { + beforeAll(async () => { + apolloMutateMock.mockResolvedValue() + await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click') + }) + + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: redeemTransactionLink, + variables: { + code: 'some-code', + }, + }), + ) + }) + + it('toasts a success message', () => { + expect(mocks.$t).toBeCalledWith('gdd_per_link.redeem') + expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.redeemed; ') + }) + + it('pushes the route to overview', () => { + expect(routerPushMock).toBeCalledWith('/overview') + }) + }) + + describe('redeem link with error', () => { + beforeAll(async () => { + apolloMutateMock.mockRejectedValue({ message: 'Oh Noo!' }) + await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click') + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh Noo!') + }) + + it('pushes the route to overview', () => { + expect(routerPushMock).toBeCalledWith('/overview') + }) + }) }) }) - describe('redeem link with error', () => { - beforeEach(async () => { - apolloMutateMock.mockRejectedValue({ message: 'Oh Noo!' }) - await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click') + describe('no sufficient token time in store', () => { + beforeAll(() => { + mocks.$store.state.token = 'token' + mocks.$store.state.tokenTime = 1665125185 + apolloQueryMock.mockResolvedValue({ + data: { + queryTransactionLink: { + __typename: 'TransactionLink', + id: 92, + amount: '22', + memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', + createdAt: '2022-03-17T16:10:28.000Z', + validUntil: transactionLinkValidExpireDate(), + redeemedAt: null, + deletedAt: null, + user: { firstName: 'Bibi', publisherId: 0, email: 'bibi@bloxberg.de' }, + }, + }, + }) + wrapper = Wrapper() }) - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Oh Noo!') + it('has a RedeemLoggedOut component', () => { + expect(wrapper.findComponent({ name: 'RedeemLoggedOut' }).exists()).toBe(true) }) - it('pushes the route to overview', () => { - expect(routerPushMock).toBeCalledWith('/overview') + it('has a link to register with code', () => { + expect(wrapper.find('a[href="/register/some-code"]').exists()).toBe(true) + }) + + it('has a link to login with code', () => { + expect(wrapper.find('a[href="/login/some-code"]').exists()).toBe(true) }) }) }) From 2c31e4e87f1ae3c87330cf56972f607bb20bc39c Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 7 Oct 2022 10:49:30 +0200 Subject: [PATCH 11/15] fix linting --- frontend/src/pages/TransactionLink.spec.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/TransactionLink.spec.js b/frontend/src/pages/TransactionLink.spec.js index 8a8af2e1c..bd03d7963 100644 --- a/frontend/src/pages/TransactionLink.spec.js +++ b/frontend/src/pages/TransactionLink.spec.js @@ -219,12 +219,12 @@ describe('TransactionLink', () => { describe('token in store', () => { beforeAll(() => { - mocks.$store.state.token = jwt.sign({ data: 'test' }, 'secret', { expiresIn: '1h' }); + mocks.$store.state.token = jwt.sign({ data: 'test' }, 'secret', { expiresIn: '1h' }) }) describe('sufficient token time in store', () => { beforeAll(() => { - mocks.$store.state.tokenTime = jwtDecode(mocks.$store.state.token).exp // TODO set a sufficient token Time + mocks.$store.state.tokenTime = jwtDecode(mocks.$store.state.token).exp }) describe('own link', () => { @@ -235,7 +235,8 @@ describe('TransactionLink', () => { __typename: 'TransactionLink', id: 92, amount: '22', - memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', + memo: + 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', createdAt: '2022-03-17T16:10:28.000Z', validUntil: transactionLinkValidExpireDate(), redeemedAt: null, @@ -270,7 +271,8 @@ describe('TransactionLink', () => { __typename: 'TransactionLink', id: 92, amount: '22', - memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', + memo: + 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', createdAt: '2022-03-17T16:10:28.000Z', validUntil: transactionLinkValidExpireDate(), redeemedAt: null, @@ -346,7 +348,8 @@ describe('TransactionLink', () => { __typename: 'TransactionLink', id: 92, amount: '22', - memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', + memo: + 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ', createdAt: '2022-03-17T16:10:28.000Z', validUntil: transactionLinkValidExpireDate(), redeemedAt: null, From 00e7ea776de61323fd38e436d8907efb6b0d690f Mon Sep 17 00:00:00 2001 From: mahula Date: Wed, 12 Oct 2022 13:01:01 +0200 Subject: [PATCH 12/15] remove usage of jsonwebtoken from transaction list unit tests --- frontend/package.json | 1 - frontend/src/pages/TransactionLink.spec.js | 7 +- frontend/yarn.lock | 80 ---------------------- 3 files changed, 2 insertions(+), 86 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index db1f2c99a..011193b58 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,7 +44,6 @@ "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", "jest-canvas-mock": "^2.3.1", - "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.1.2", "portal-vue": "^2.1.7", "prettier": "^2.2.1", diff --git a/frontend/src/pages/TransactionLink.spec.js b/frontend/src/pages/TransactionLink.spec.js index bd03d7963..adbb25226 100644 --- a/frontend/src/pages/TransactionLink.spec.js +++ b/frontend/src/pages/TransactionLink.spec.js @@ -3,8 +3,6 @@ import TransactionLink from './TransactionLink' import { queryTransactionLink } from '@/graphql/queries' import { redeemTransactionLink } from '@/graphql/mutations' import { toastSuccessSpy, toastErrorSpy } from '@test/testSetup' -import jwt from 'jsonwebtoken' -import jwtDecode from 'jwt-decode' const localVue = global.localVue @@ -219,12 +217,12 @@ describe('TransactionLink', () => { describe('token in store', () => { beforeAll(() => { - mocks.$store.state.token = jwt.sign({ data: 'test' }, 'secret', { expiresIn: '1h' }) + mocks.$store.state.token = 'token' }) describe('sufficient token time in store', () => { beforeAll(() => { - mocks.$store.state.tokenTime = jwtDecode(mocks.$store.state.token).exp + mocks.$store.state.tokenTime = Math.floor(Date.now() / 1000) + 20 }) describe('own link', () => { @@ -340,7 +338,6 @@ describe('TransactionLink', () => { describe('no sufficient token time in store', () => { beforeAll(() => { - mocks.$store.state.token = 'token' mocks.$store.state.tokenTime = 1665125185 apolloQueryMock.mockResolvedValue({ data: { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 715b2504d..fe7ab7947 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4376,11 +4376,6 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - buffer-from@1.x: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -5951,13 +5946,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -9671,22 +9659,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -9697,23 +9669,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - jwt-decode@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" @@ -9908,36 +9863,6 @@ lodash.defaultsdeep@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -9953,11 +9878,6 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" From 34ec593f219a98039ca0214f76ddcb2a6befa720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 12 Oct 2022 16:46:46 +0200 Subject: [PATCH 13/15] Implement GQL logout in admin interface --- admin/src/components/NavBar.vue | 20 ++++++++++++++++---- admin/src/graphql/logout.js | 7 +++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 admin/src/graphql/logout.js diff --git a/admin/src/components/NavBar.vue b/admin/src/components/NavBar.vue index 9103b56e6..e5452428b 100644 --- a/admin/src/components/NavBar.vue +++ b/admin/src/components/NavBar.vue @@ -28,14 +28,26 @@