From 5fd1a86af1f5efa514b01784750830380fd53c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 3 May 2022 09:28:57 +0200 Subject: [PATCH 01/11] Replace some withDeleted by filterByDeleted --- backend/src/graphql/arg/TransactionLinkFilters.ts | 2 +- backend/src/graphql/resolver/AdminResolver.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/arg/TransactionLinkFilters.ts b/backend/src/graphql/arg/TransactionLinkFilters.ts index e2f752d3f..4d02a18cc 100644 --- a/backend/src/graphql/arg/TransactionLinkFilters.ts +++ b/backend/src/graphql/arg/TransactionLinkFilters.ts @@ -3,7 +3,7 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export default class TransactionLinkFilters { @Field(() => Boolean, { nullable: true, defaultValue: true }) - withDeleted?: boolean + filterByDeleted?: boolean @Field(() => Boolean, { nullable: true, defaultValue: true }) withExpired?: boolean diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 78cbf3fc8..20eab662f 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -149,7 +149,7 @@ export class AdminResolver { } // soft-delete user await user.softRemove() - const newUser = await dbUser.findOne({ id: userId }, { withDeleted: true }) + const newUser = await dbUser.findOne({ id: userId }, { filterByDeleted: true }) return newUser ? newUser.deletedAt : null } @@ -444,7 +444,7 @@ export class AdminResolver { if (!filters.withExpired) where.validUntil = MoreThan(new Date()) const [transactionLinks, count] = await dbTransactionLink.findAndCount({ where, - withDeleted: filters.withDeleted, + withDeleted: filters.filterByDeleted, order: { createdAt: order, }, From ab5e3a7729d494b1e587efc5d6c0fd8367aadfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 16 May 2022 11:45:27 +0200 Subject: [PATCH 02/11] Revert "Replace some withDeleted by filterByDeleted" This reverts commit 5fd1a86af1f5efa514b01784750830380fd53c0f. --- backend/src/graphql/arg/TransactionLinkFilters.ts | 2 +- backend/src/graphql/resolver/AdminResolver.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/arg/TransactionLinkFilters.ts b/backend/src/graphql/arg/TransactionLinkFilters.ts index 4d02a18cc..e2f752d3f 100644 --- a/backend/src/graphql/arg/TransactionLinkFilters.ts +++ b/backend/src/graphql/arg/TransactionLinkFilters.ts @@ -3,7 +3,7 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export default class TransactionLinkFilters { @Field(() => Boolean, { nullable: true, defaultValue: true }) - filterByDeleted?: boolean + withDeleted?: boolean @Field(() => Boolean, { nullable: true, defaultValue: true }) withExpired?: boolean diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 20eab662f..78cbf3fc8 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -149,7 +149,7 @@ export class AdminResolver { } // soft-delete user await user.softRemove() - const newUser = await dbUser.findOne({ id: userId }, { filterByDeleted: true }) + const newUser = await dbUser.findOne({ id: userId }, { withDeleted: true }) return newUser ? newUser.deletedAt : null } @@ -444,7 +444,7 @@ export class AdminResolver { if (!filters.withExpired) where.validUntil = MoreThan(new Date()) const [transactionLinks, count] = await dbTransactionLink.findAndCount({ where, - withDeleted: filters.filterByDeleted, + withDeleted: filters.withDeleted, order: { createdAt: order, }, From 946280a6db41f456206cac1461f67d5fccc8ddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 16 May 2022 12:00:57 +0200 Subject: [PATCH 03/11] Rename transaction link filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit – Fix spelling in comment. --- backend/src/graphql/arg/TransactionLinkFilters.ts | 6 +++--- backend/src/graphql/resolver/AdminResolver.ts | 6 +++--- backend/src/seeds/index.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/arg/TransactionLinkFilters.ts b/backend/src/graphql/arg/TransactionLinkFilters.ts index e2f752d3f..b009a3180 100644 --- a/backend/src/graphql/arg/TransactionLinkFilters.ts +++ b/backend/src/graphql/arg/TransactionLinkFilters.ts @@ -3,11 +3,11 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export default class TransactionLinkFilters { @Field(() => Boolean, { nullable: true, defaultValue: true }) - withDeleted?: boolean + filterByDeleted?: boolean @Field(() => Boolean, { nullable: true, defaultValue: true }) - withExpired?: boolean + filterByExpired?: boolean @Field(() => Boolean, { nullable: true, defaultValue: true }) - withRedeemed?: boolean + filterByRedeemed?: boolean } diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 8da92a61c..d0dc3f442 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -442,11 +442,11 @@ export class AdminResolver { } = { userId, } - if (!filters.withRedeemed) where.redeemedBy = null - if (!filters.withExpired) where.validUntil = MoreThan(new Date()) + if (!filters.filterByRedeemed) where.redeemedBy = null + if (!filters.filterByExpired) where.validUntil = MoreThan(new Date()) const [transactionLinks, count] = await dbTransactionLink.findAndCount({ where, - withDeleted: filters.withDeleted, + withDeleted: filters.filterByDeleted, order: { createdAt: order, }, diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index f26000e06..11993c03e 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -29,7 +29,7 @@ const context = { } export const cleanDB = async () => { - // this only works as lond we do not have foreign key constraints + // this only works as long we do not have foreign key constraints for (let i = 0; i < entities.length; i++) { await resetEntity(entities[i]) } From 0f8ffb58607e4f0106a716e04b0c31d1b1d8b03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 18 May 2022 14:45:36 +0200 Subject: [PATCH 04/11] Refactor to have filters GQL object and input for usererSearch, a start --- admin/src/graphql/searchUsers.js | 2 +- admin/src/pages/UserSearch.spec.js | 12 +++---- admin/src/pages/UserSearch.vue | 2 +- backend/src/graphql/arg/SearchUsersArgs.ts | 15 ++++++--- backend/src/graphql/arg/SearchUsersFilters.ts | 13 ++++++++ backend/src/graphql/resolver/AdminResolver.ts | 20 +++++++---- backend/src/seeds/graphql/mutations.ts | 33 +++++++++++++++++++ 7 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 backend/src/graphql/arg/SearchUsersFilters.ts diff --git a/admin/src/graphql/searchUsers.js b/admin/src/graphql/searchUsers.js index ddf759031..fc9ac5148 100644 --- a/admin/src/graphql/searchUsers.js +++ b/admin/src/graphql/searchUsers.js @@ -12,7 +12,7 @@ export const searchUsers = gql` searchText: $searchText currentPage: $currentPage pageSize: $pageSize - filterByActivated: $filterByActivated + filterByActivated: $filterByActivated # Wolle: put in 'filters' object? filterByDeleted: $filterByDeleted ) { userCount diff --git a/admin/src/pages/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js index 2eb24f84b..079058479 100644 --- a/admin/src/pages/UserSearch.spec.js +++ b/admin/src/pages/UserSearch.spec.js @@ -82,7 +82,7 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: null, + filterByActivated: null, // Wolle: put in 'filters' object? filterByDeleted: null, }, }), @@ -101,7 +101,7 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: false, + filterByActivated: false, // Wolle: put in 'filters' object? filterByDeleted: null, }, }), @@ -121,7 +121,7 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: null, + filterByActivated: null, // Wolle: put in 'filters' object? filterByDeleted: true, }, }), @@ -141,7 +141,7 @@ describe('UserSearch', () => { searchText: '', currentPage: 2, pageSize: 25, - filterByActivated: null, + filterByActivated: null, // Wolle: put in 'filters' object? filterByDeleted: null, }, }), @@ -161,7 +161,7 @@ describe('UserSearch', () => { searchText: 'search string', currentPage: 1, pageSize: 25, - filterByActivated: null, + filterByActivated: null, // Wolle: put in 'filters' object? filterByDeleted: null, }, }), @@ -178,7 +178,7 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: null, + filterByActivated: null, // Wolle: put in 'filters' object? filterByDeleted: null, }, }), diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue index f8ceac36c..82eebc0b8 100644 --- a/admin/src/pages/UserSearch.vue +++ b/admin/src/pages/UserSearch.vue @@ -97,7 +97,7 @@ export default { searchText: this.criteria, currentPage: this.currentPage, pageSize: this.perPage, - filterByActivated: this.filterByActivated, + filterByActivated: this.filterByActivated, // Wolle: put in 'filters' object? filterByDeleted: this.filterByDeleted, }, fetchPolicy: 'no-cache', diff --git a/backend/src/graphql/arg/SearchUsersArgs.ts b/backend/src/graphql/arg/SearchUsersArgs.ts index b47f39d56..7826f26fd 100644 --- a/backend/src/graphql/arg/SearchUsersArgs.ts +++ b/backend/src/graphql/arg/SearchUsersArgs.ts @@ -1,4 +1,5 @@ -import { ArgsType, Field, Int } from 'type-graphql' +import { Args, ArgsType, Field, Int } from 'type-graphql' +import SearchUsersFilters from '@arg/SearchUsersFilters' @ArgsType() export default class SearchUsersArgs { @@ -11,9 +12,13 @@ export default class SearchUsersArgs { @Field(() => Int, { nullable: true }) pageSize?: number - @Field(() => Boolean, { nullable: true }) - filterByActivated?: boolean | null + // Wolle: @Field(() => Boolean, { nullable: true }) + // filterByActivated?: boolean | null - @Field(() => Boolean, { nullable: true }) - filterByDeleted?: boolean | null + // Wolle: @Field(() => Boolean, { nullable: true }) + // filterByDeleted?: boolean | null + + // Wolle: shall this be nullable? + @Field() + filters: SearchUsersFilters } diff --git a/backend/src/graphql/arg/SearchUsersFilters.ts b/backend/src/graphql/arg/SearchUsersFilters.ts new file mode 100644 index 000000000..55a09304c --- /dev/null +++ b/backend/src/graphql/arg/SearchUsersFilters.ts @@ -0,0 +1,13 @@ +// Wolle: import { ArgsType, Field, InputType } from 'type-graphql' +import { Field, InputType, ObjectType } from 'type-graphql' + +@ObjectType() +@InputType('SearchUsersFiltersInput') +// Wolle: @ArgsType() +export default class SearchUsersFilters { + @Field(() => Boolean, { nullable: true, defaultValue: null }) + filterByActivated?: boolean | null + + @Field(() => Boolean, { nullable: true, defaultValue: null }) + filterByDeleted?: boolean | null +} diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index d0dc3f442..0f46729c4 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -56,19 +56,27 @@ export class AdminResolver { searchText, currentPage = 1, pageSize = 25, - filterByActivated = null, - filterByDeleted = null, + // Wolle: filters = { + // filterByActivated: null, + // filterByDeleted: null, + // }, + filters = { + filterByActivated: null, + filterByDeleted: null, + }, }: SearchUsersArgs, ): Promise { + // Wolle + console.log('filters: ', filters) const userRepository = getCustomRepository(UserRepository) const filterCriteria: ObjectLiteral[] = [] - if (filterByActivated !== null) { - filterCriteria.push({ emailChecked: filterByActivated }) + if (filters.filterByActivated !== null) { + filterCriteria.push({ emailChecked: filters.filterByActivated }) } - if (filterByDeleted !== null) { - filterCriteria.push({ deletedAt: filterByDeleted ? Not(IsNull()) : IsNull() }) + if (filters.filterByDeleted !== null) { + filterCriteria.push({ deletedAt: filters.filterByDeleted ? Not(IsNull()) : IsNull() }) } const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked', 'deletedAt'] diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index d3026dbdd..f6c6b1fae 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -107,6 +107,39 @@ export const unDeleteUser = gql` } ` +export const searchUsers = gql` + query ( + $searchText: String! + $currentPage: Int + $pageSize: Int + # Wolle $filterByActivated: Boolean + # $filterByDeleted: Boolean + $filters: SearchUsersFiltersInput! + ) { + searchUsers( + searchText: $searchText + currentPage: $currentPage + pageSize: $pageSize + # Wolle filterByActivated: $filterByActivated # Wolle: put in 'filters' object? + # filterByDeleted: $filterByDeleted + filters: $filters + ) { + userCount + userList { + userId + firstName + lastName + email + creation + emailChecked + hasElopage + emailConfirmationSend + deletedAt + } + } + } +` + export const createPendingCreations = gql` mutation ($pendingCreations: [CreatePendingCreationArgs!]!) { createPendingCreations(pendingCreations: $pendingCreations) { From c5ac4140a8721b2967968d7b9972dc8abbb7d146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 18 May 2022 14:45:59 +0200 Subject: [PATCH 05/11] Writing tests, a start --- .../graphql/resolver/AdminResolver.test.ts | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index ca6bf0fe7..0ce4c5314 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -11,6 +11,7 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { deleteUser, unDeleteUser, + searchUsers, createPendingCreation, createPendingCreations, updatePendingCreation, @@ -261,6 +262,160 @@ describe('AdminResolver', () => { }) }) + describe('search users', () => { + const variablesWithoutTextAndFilters = { + searchText: '', + currentPage: 1, + pageSize: 25, + filters: { + filterByActivated: null, + filterByDeleted: null, + }, + } + + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('authenticated', () => { + describe('without admin rights', () => { + beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) + await query({ + query: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + it('returns an error', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('with admin rights', () => { + beforeAll(async () => { + admin = await userFactory(testEnv, peterLustig) + await query({ + query: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + await userFactory(testEnv, bibiBloxberg) + await userFactory(testEnv, stephenHawking) + await userFactory(testEnv, garrickOllivander) + // Wolle await userFactory(testEnv, XXX) + // await userFactory(testEnv, XXX) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + describe('find', () => { + it('all users', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + searchUsers: { + userCount: 4, + userList: expect.arrayContaining([ + expect.objectContaining({ + email: 'bibi@bloxberg.de', + }), + expect.objectContaining({ + email: 'garrick@ollivander.com', + }), + expect.objectContaining({ + email: 'peter@lustig.de', + }), + expect.objectContaining({ + email: 'stephen@hawking.uk', + }), + ]), + }, + }, + }), + ) + }) + + it.only('users with unchecked email', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + filters: { + filterByActivated: false, + filterByDeleted: null, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + searchUsers: { + userCount: 4, + userList: expect.arrayContaining([ + expect.objectContaining({ + email: 'bibi@bloxberg.de', + }), + expect.objectContaining({ + email: 'garrick@ollivander.com', + }), + expect.objectContaining({ + email: 'peter@lustig.de', + }), + expect.objectContaining({ + email: 'stephen@hawking.uk', + }), + ]), + }, + }, + }), + ) + }) + }) + }) + }) + }) + describe('creations', () => { const variables = { email: 'bibi@bloxberg.de', From cde9b171af2e13c501b72d31f027e7caa1a03b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 19 May 2022 10:16:23 +0200 Subject: [PATCH 06/11] Writing resolver tests --- backend/src/graphql/arg/SearchUsersArgs.ts | 2 +- .../graphql/resolver/AdminResolver.test.ts | 74 +++++++++++-------- backend/src/util/utilities.ts | 5 ++ 3 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 backend/src/util/utilities.ts diff --git a/backend/src/graphql/arg/SearchUsersArgs.ts b/backend/src/graphql/arg/SearchUsersArgs.ts index 7826f26fd..da5b6c312 100644 --- a/backend/src/graphql/arg/SearchUsersArgs.ts +++ b/backend/src/graphql/arg/SearchUsersArgs.ts @@ -1,4 +1,4 @@ -import { Args, ArgsType, Field, Int } from 'type-graphql' +import { ArgsType, Field, Int } from 'type-graphql' import SearchUsersFilters from '@arg/SearchUsersFilters' @ArgsType() diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 0ce4c5314..77c50d734 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { convertObjValuesToArray } from '@/util/utilities' import { testEnvironment, resetToken, cleanDB } from '@test/helpers' import { userFactory } from '@/seeds/factory/user' import { creationFactory } from '@/seeds/factory/creation' @@ -342,6 +343,21 @@ describe('AdminResolver', () => { }) describe('find', () => { + const allUsers = { + bibi: expect.objectContaining({ + email: 'bibi@bloxberg.de', + }), + garrick: expect.objectContaining({ + email: 'garrick@ollivander.com', + }), + peter: expect.objectContaining({ + email: 'peter@lustig.de', + }), + stephen: expect.objectContaining({ + email: 'stephen@hawking.uk', + }), + } + it('all users', async () => { await expect( query({ @@ -355,27 +371,14 @@ describe('AdminResolver', () => { data: { searchUsers: { userCount: 4, - userList: expect.arrayContaining([ - expect.objectContaining({ - email: 'bibi@bloxberg.de', - }), - expect.objectContaining({ - email: 'garrick@ollivander.com', - }), - expect.objectContaining({ - email: 'peter@lustig.de', - }), - expect.objectContaining({ - email: 'stephen@hawking.uk', - }), - ]), + userList: expect.arrayContaining(convertObjValuesToArray(allUsers)), }, }, }), ) }) - it.only('users with unchecked email', async () => { + it('users with unchecked email', async () => { await expect( query({ query: searchUsers, @@ -391,21 +394,32 @@ describe('AdminResolver', () => { expect.objectContaining({ data: { searchUsers: { - userCount: 4, - userList: expect.arrayContaining([ - expect.objectContaining({ - email: 'bibi@bloxberg.de', - }), - expect.objectContaining({ - email: 'garrick@ollivander.com', - }), - expect.objectContaining({ - email: 'peter@lustig.de', - }), - expect.objectContaining({ - email: 'stephen@hawking.uk', - }), - ]), + userCount: 1, + userList: expect.arrayContaining([allUsers.garrick]), + }, + }, + }), + ) + }) + + it('users with deleted account', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + filters: { + filterByActivated: null, + filterByDeleted: true, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + searchUsers: { + userCount: 1, + userList: expect.arrayContaining([allUsers.stephen]), }, }, }), diff --git a/backend/src/util/utilities.ts b/backend/src/util/utilities.ts new file mode 100644 index 000000000..f77ad05ec --- /dev/null +++ b/backend/src/util/utilities.ts @@ -0,0 +1,5 @@ +export const convertObjValuesToArray = (obj: { [x: string]: string }): Array => { + return Object.keys(obj).map(function (key) { + return obj[key] + }) +} From 3ba19359a3eca652af9a76254b4b556ff16f24a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 19 May 2022 10:46:07 +0200 Subject: [PATCH 07/11] Make filters object nullable and writing resolver tests --- backend/src/graphql/arg/SearchUsersArgs.ts | 9 +-- backend/src/graphql/arg/SearchUsersFilters.ts | 2 - .../graphql/resolver/AdminResolver.test.ts | 57 ++++++++++++++++--- backend/src/graphql/resolver/AdminResolver.ts | 20 +------ backend/src/seeds/graphql/mutations.ts | 6 +- 5 files changed, 55 insertions(+), 39 deletions(-) diff --git a/backend/src/graphql/arg/SearchUsersArgs.ts b/backend/src/graphql/arg/SearchUsersArgs.ts index da5b6c312..8db6bfc06 100644 --- a/backend/src/graphql/arg/SearchUsersArgs.ts +++ b/backend/src/graphql/arg/SearchUsersArgs.ts @@ -12,13 +12,6 @@ export default class SearchUsersArgs { @Field(() => Int, { nullable: true }) pageSize?: number - // Wolle: @Field(() => Boolean, { nullable: true }) - // filterByActivated?: boolean | null - - // Wolle: @Field(() => Boolean, { nullable: true }) - // filterByDeleted?: boolean | null - - // Wolle: shall this be nullable? - @Field() + @Field(() => SearchUsersFilters, { nullable: true }) filters: SearchUsersFilters } diff --git a/backend/src/graphql/arg/SearchUsersFilters.ts b/backend/src/graphql/arg/SearchUsersFilters.ts index 55a09304c..de7c7c20a 100644 --- a/backend/src/graphql/arg/SearchUsersFilters.ts +++ b/backend/src/graphql/arg/SearchUsersFilters.ts @@ -1,9 +1,7 @@ -// Wolle: import { ArgsType, Field, InputType } from 'type-graphql' import { Field, InputType, ObjectType } from 'type-graphql' @ObjectType() @InputType('SearchUsersFiltersInput') -// Wolle: @ArgsType() export default class SearchUsersFilters { @Field(() => Boolean, { nullable: true, defaultValue: null }) filterByActivated?: boolean | null diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 77c50d734..1df80aa1a 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -268,10 +268,7 @@ describe('AdminResolver', () => { searchText: '', currentPage: 1, pageSize: 25, - filters: { - filterByActivated: null, - filterByDeleted: null, - }, + filters: null, } describe('unauthenticated', () => { @@ -333,8 +330,6 @@ describe('AdminResolver', () => { await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, stephenHawking) await userFactory(testEnv, garrickOllivander) - // Wolle await userFactory(testEnv, XXX) - // await userFactory(testEnv, XXX) }) afterAll(async () => { @@ -358,7 +353,7 @@ describe('AdminResolver', () => { }), } - it('all users', async () => { + it('all users by "filters === null"', async () => { await expect( query({ query: searchUsers, @@ -378,6 +373,30 @@ describe('AdminResolver', () => { ) }) + it('all users by "filterByActivated === null && filterByDeleted === null"', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + filters: { + filterByActivated: null, + filterByDeleted: null, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + searchUsers: { + userCount: 4, + userList: expect.arrayContaining(convertObjValuesToArray(allUsers)), + }, + }, + }), + ) + }) + it('users with unchecked email', async () => { await expect( query({ @@ -425,6 +444,30 @@ describe('AdminResolver', () => { }), ) }) + + it('no users with deleted account and unchecked email', async () => { + await expect( + query({ + query: searchUsers, + variables: { + ...variablesWithoutTextAndFilters, + filters: { + filterByActivated: false, + filterByDeleted: true, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + searchUsers: { + userCount: 0, + userList: [], + }, + }, + }), + ) + }) }) }) }) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 0f46729c4..fe4075cd1 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -52,30 +52,16 @@ export class AdminResolver { @Query(() => SearchUsersResult) async searchUsers( @Args() - { - searchText, - currentPage = 1, - pageSize = 25, - // Wolle: filters = { - // filterByActivated: null, - // filterByDeleted: null, - // }, - filters = { - filterByActivated: null, - filterByDeleted: null, - }, - }: SearchUsersArgs, + { searchText, currentPage = 1, pageSize = 25, filters }: SearchUsersArgs, ): Promise { - // Wolle - console.log('filters: ', filters) const userRepository = getCustomRepository(UserRepository) const filterCriteria: ObjectLiteral[] = [] - if (filters.filterByActivated !== null) { + if (filters && filters.filterByActivated !== null) { filterCriteria.push({ emailChecked: filters.filterByActivated }) } - if (filters.filterByDeleted !== null) { + if (filters && filters.filterByDeleted !== null) { filterCriteria.push({ deletedAt: filters.filterByDeleted ? Not(IsNull()) : IsNull() }) } diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index f6c6b1fae..4598cbbe2 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -112,16 +112,12 @@ export const searchUsers = gql` $searchText: String! $currentPage: Int $pageSize: Int - # Wolle $filterByActivated: Boolean - # $filterByDeleted: Boolean - $filters: SearchUsersFiltersInput! + $filters: SearchUsersFiltersInput ) { searchUsers( searchText: $searchText currentPage: $currentPage pageSize: $pageSize - # Wolle filterByActivated: $filterByActivated # Wolle: put in 'filters' object? - # filterByDeleted: $filterByDeleted filters: $filters ) { userCount From 762bdfc505ded7b01ca0c4415dcf4fa5f007a589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 19 May 2022 10:58:48 +0200 Subject: [PATCH 08/11] Refactor frontend user search to refactored resolver --- admin/src/graphql/searchUsers.js | 6 ++--- admin/src/pages/UserSearch.spec.js | 36 ++++++++++++++++++++---------- admin/src/pages/UserSearch.vue | 6 +++-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/admin/src/graphql/searchUsers.js b/admin/src/graphql/searchUsers.js index fc9ac5148..5740e24cc 100644 --- a/admin/src/graphql/searchUsers.js +++ b/admin/src/graphql/searchUsers.js @@ -5,15 +5,13 @@ export const searchUsers = gql` $searchText: String! $currentPage: Int $pageSize: Int - $filterByActivated: Boolean - $filterByDeleted: Boolean + $filters: SearchUsersFiltersInput ) { searchUsers( searchText: $searchText currentPage: $currentPage pageSize: $pageSize - filterByActivated: $filterByActivated # Wolle: put in 'filters' object? - filterByDeleted: $filterByDeleted + filters: $filters ) { userCount userList { diff --git a/admin/src/pages/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js index 079058479..1a23b4292 100644 --- a/admin/src/pages/UserSearch.spec.js +++ b/admin/src/pages/UserSearch.spec.js @@ -82,8 +82,10 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: null, // Wolle: put in 'filters' object? - filterByDeleted: null, + filters: { + filterByActivated: null, + filterByDeleted: null, + }, }, }), ) @@ -101,8 +103,10 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: false, // Wolle: put in 'filters' object? - filterByDeleted: null, + filters: { + filterByActivated: false, + filterByDeleted: null, + }, }, }), ) @@ -121,8 +125,10 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: null, // Wolle: put in 'filters' object? - filterByDeleted: true, + filters: { + filterByActivated: null, + filterByDeleted: true, + }, }, }), ) @@ -141,8 +147,10 @@ describe('UserSearch', () => { searchText: '', currentPage: 2, pageSize: 25, - filterByActivated: null, // Wolle: put in 'filters' object? - filterByDeleted: null, + filters: { + filterByActivated: null, + filterByDeleted: null, + }, }, }), ) @@ -161,8 +169,10 @@ describe('UserSearch', () => { searchText: 'search string', currentPage: 1, pageSize: 25, - filterByActivated: null, // Wolle: put in 'filters' object? - filterByDeleted: null, + filters: { + filterByActivated: null, + filterByDeleted: null, + }, }, }), ) @@ -178,8 +188,10 @@ describe('UserSearch', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: null, // Wolle: put in 'filters' object? - filterByDeleted: null, + filters: { + filterByActivated: null, + filterByDeleted: null, + }, }, }), ) diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue index 82eebc0b8..7b638c316 100644 --- a/admin/src/pages/UserSearch.vue +++ b/admin/src/pages/UserSearch.vue @@ -97,8 +97,10 @@ export default { searchText: this.criteria, currentPage: this.currentPage, pageSize: this.perPage, - filterByActivated: this.filterByActivated, // Wolle: put in 'filters' object? - filterByDeleted: this.filterByDeleted, + filters: { + filterByActivated: this.filterByActivated, + filterByDeleted: this.filterByDeleted, + }, }, fetchPolicy: 'no-cache', }) From 879ff45dfd4abff83d82bf219d1f35a999a3059e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 19 May 2022 17:41:16 +0200 Subject: [PATCH 09/11] Use if statement for 'filters' object may be null --- backend/src/graphql/resolver/AdminResolver.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index fe4075cd1..8c3d71b73 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -57,12 +57,14 @@ export class AdminResolver { const userRepository = getCustomRepository(UserRepository) const filterCriteria: ObjectLiteral[] = [] - if (filters && filters.filterByActivated !== null) { - filterCriteria.push({ emailChecked: filters.filterByActivated }) - } + if (filters) { + if (filters.filterByActivated !== null) { + filterCriteria.push({ emailChecked: filters.filterByActivated }) + } - if (filters && filters.filterByDeleted !== null) { - filterCriteria.push({ deletedAt: filters.filterByDeleted ? Not(IsNull()) : IsNull() }) + if (filters.filterByDeleted !== null) { + filterCriteria.push({ deletedAt: filters.filterByDeleted ? Not(IsNull()) : IsNull() }) + } } const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked', 'deletedAt'] From 78c403e268bc272041f69f83aa6adace82ba2a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 19 May 2022 18:10:19 +0200 Subject: [PATCH 10/11] Refactor forgotten Creation.vue --- admin/src/pages/Creation.spec.js | 24 ++++++--- admin/src/pages/Creation.vue | 6 ++- admin/src/pages/UserSearch.spec.js | 2 +- .../graphql/resolver/AdminResolver.test.ts | 50 +++++++++++-------- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/admin/src/pages/Creation.spec.js b/admin/src/pages/Creation.spec.js index 98c03d277..432cbe19b 100644 --- a/admin/src/pages/Creation.spec.js +++ b/admin/src/pages/Creation.spec.js @@ -71,8 +71,10 @@ describe('Creation', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: true, - filterByDeleted: false, + filters: { + filterByActivated: true, + filterByDeleted: false, + }, }, }), ) @@ -271,8 +273,10 @@ describe('Creation', () => { searchText: 'XX', currentPage: 1, pageSize: 25, - filterByActivated: true, - filterByDeleted: false, + filters: { + filterByActivated: true, + filterByDeleted: false, + }, }, }), ) @@ -288,8 +292,10 @@ describe('Creation', () => { searchText: '', currentPage: 1, pageSize: 25, - filterByActivated: true, - filterByDeleted: false, + filters: { + filterByActivated: true, + filterByDeleted: false, + }, }, }), ) @@ -305,8 +311,10 @@ describe('Creation', () => { searchText: '', currentPage: 2, pageSize: 25, - filterByActivated: true, - filterByDeleted: false, + filters: { + filterByActivated: true, + filterByDeleted: false, + }, }, }), ) diff --git a/admin/src/pages/Creation.vue b/admin/src/pages/Creation.vue index 54bc0d735..17962bfff 100644 --- a/admin/src/pages/Creation.vue +++ b/admin/src/pages/Creation.vue @@ -102,8 +102,10 @@ export default { searchText: this.criteria, currentPage: this.currentPage, pageSize: this.perPage, - filterByActivated: true, - filterByDeleted: false, + filters: { + filterByActivated: true, + filterByDeleted: false, + }, }, fetchPolicy: 'network-only', }) diff --git a/admin/src/pages/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js index 1a23b4292..a1d809a66 100644 --- a/admin/src/pages/UserSearch.spec.js +++ b/admin/src/pages/UserSearch.spec.js @@ -7,7 +7,7 @@ const localVue = global.localVue const apolloQueryMock = jest.fn().mockResolvedValue({ data: { searchUsers: { - userCount: 1, + userCount: 4, userList: [ { userId: 1, diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 1df80aa1a..4771232ea 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -320,6 +320,21 @@ describe('AdminResolver', () => { }) describe('with admin rights', () => { + const allUsers = { + bibi: expect.objectContaining({ + email: 'bibi@bloxberg.de', + }), + garrick: expect.objectContaining({ + email: 'garrick@ollivander.com', + }), + peter: expect.objectContaining({ + email: 'peter@lustig.de', + }), + stephen: expect.objectContaining({ + email: 'stephen@hawking.uk', + }), + } + beforeAll(async () => { admin = await userFactory(testEnv, peterLustig) await query({ @@ -337,23 +352,8 @@ describe('AdminResolver', () => { resetToken() }) - describe('find', () => { - const allUsers = { - bibi: expect.objectContaining({ - email: 'bibi@bloxberg.de', - }), - garrick: expect.objectContaining({ - email: 'garrick@ollivander.com', - }), - peter: expect.objectContaining({ - email: 'peter@lustig.de', - }), - stephen: expect.objectContaining({ - email: 'stephen@hawking.uk', - }), - } - - it('all users by "filters === null"', async () => { + describe('without any filters', () => { + it('finds all users', async () => { await expect( query({ query: searchUsers, @@ -372,8 +372,10 @@ describe('AdminResolver', () => { }), ) }) + }) - it('all users by "filterByActivated === null && filterByDeleted === null"', async () => { + describe('all filters are null', () => { + it('finds all users', async () => { await expect( query({ query: searchUsers, @@ -396,8 +398,10 @@ describe('AdminResolver', () => { }), ) }) + }) - it('users with unchecked email', async () => { + describe('filter by unchecked email', () => { + it('finds only users with unchecked email', async () => { await expect( query({ query: searchUsers, @@ -420,8 +424,10 @@ describe('AdminResolver', () => { }), ) }) + }) - it('users with deleted account', async () => { + describe('filter by deleted users', () => { + it('finds only users with deleted account', async () => { await expect( query({ query: searchUsers, @@ -444,8 +450,10 @@ describe('AdminResolver', () => { }), ) }) + }) - it('no users with deleted account and unchecked email', async () => { + describe('filter by deleted account and unchecked email', () => { + it('finds no users', async () => { await expect( query({ query: searchUsers, From 7325ed53ec150b90fb8d67f5eec8bc79781ca268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 19 May 2022 18:17:17 +0200 Subject: [PATCH 11/11] Set backend 'min_coverage' to '66' --- .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 8680c9203..bb2441701 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -528,7 +528,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 64 + min_coverage: 66 token: ${{ github.token }} ##########################################################################