From b12056594fbbe13056d80dc8a22cc5925ab17755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 9 Aug 2022 07:15:30 +0200 Subject: [PATCH 1/3] Add issue to TODO comment --- backend/src/helpers/jest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/helpers/jest.js b/backend/src/helpers/jest.js index 14317642b..ecfc1a042 100644 --- a/backend/src/helpers/jest.js +++ b/backend/src/helpers/jest.js @@ -1,5 +1,6 @@ -// TODO: can be replaced with, which is no a fake: +// TODO: can be replaced with: (which is no a fake) // import gql from 'graphql-tag' +// See issue: https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/5152 //* This is a fake ES2015 template string, just to benefit of syntax // highlighting of `gql` template strings in certain editors. From 7847d6912c2142e5bdc5f061df0e981c4accb8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 9 Aug 2022 08:19:51 +0200 Subject: [PATCH 2/3] Return 'categories' in the 'Group' GQL query --- backend/src/db/graphql/groups.js | 30 +- backend/src/schema/resolvers/groups.spec.js | 372 ++++++++++---------- backend/src/schema/types/type/Group.gql | 14 +- 3 files changed, 219 insertions(+), 197 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index e8da8e90b..c41f06e4d 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -4,13 +4,13 @@ import gql from 'graphql-tag' export const createGroupMutation = gql` mutation ( - $id: ID, - $name: String!, - $slug: String, - $about: String, - $description: String!, - $groupType: GroupType!, - $actionRadius: GroupActionRadius!, + $id: ID + $name: String! + $slug: String + $about: String + $description: String! + $groupType: GroupType! + $actionRadius: GroupActionRadius! $categoryIds: [ID] ) { CreateGroup( @@ -47,13 +47,13 @@ export const createGroupMutation = gql` export const groupQuery = gql` query ( $isMember: Boolean - $id: ID, - $name: String, - $slug: String, + $id: ID + $name: String + $slug: String $createdAt: String $updatedAt: String - $about: String, - $description: String, + $about: String + $description: String # $groupType: GroupType!, # $actionRadius: GroupActionRadius!, # $categoryIds: [ID] @@ -93,6 +93,12 @@ export const groupQuery = gql` groupType actionRadius myRole + categories { + id + slug + name + icon + } # Wolle: owner { # name # } diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 58e5f37be..dd5a48568 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -52,21 +52,25 @@ beforeEach(async () => { neode.create('Category', { id: 'cat9', name: 'Democracy & Politics', + slug: 'democracy-politics', icon: 'university', }), neode.create('Category', { id: 'cat4', name: 'Environment & Nature', + slug: 'environment-nature', icon: 'tree', }), neode.create('Category', { id: 'cat15', name: 'Consumption & Sustainability', + slug: 'consumption-sustainability', icon: 'shopping-cart', }), neode.create('Category', { id: 'cat27', name: 'Animal Protection', + slug: 'animal-protection', icon: 'paw', }), ]) @@ -133,44 +137,8 @@ describe('Group', () => { }) }) - describe('query can fetch', () => { - it('groups where user is member (or owner in this case)', async () => { - const expected = { - data: { - Group: [ - { - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }, - ], - }, - errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject(expected) - }) - - it('groups where user is not(!) member', async () => { - const expected = { - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject(expected) - }) - - it('all groups', async () => { + describe('can find', () => { + it('all', async () => { const expected = { data: { Group: expect.arrayContaining([ @@ -190,164 +158,200 @@ describe('Group', () => { } await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) }) + + it('where user is member (or owner in this case)', async () => { + const expected = { + data: { + Group: [ + { + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }, + ], + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: true } }), + ).resolves.toMatchObject(expected) + }) + + it('where user is not(!) member', async () => { + const expected = { + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject(expected) + }) }) - // Wolle: describe('can be filtered', () => { - // let followedUser, happyPost, cryPost - // beforeEach(async () => { - // ;[followedUser] = await Promise.all([ - // Factory.build( - // 'user', - // { - // id: 'followed-by-me', - // name: 'Followed User', - // }, - // { - // email: 'followed@example.org', - // password: '1234', - // }, - // ), - // ]) - // ;[happyPost, cryPost] = await Promise.all([ - // Factory.build('post', { id: 'happy-post' }, { categoryIds: ['cat4'] }), - // Factory.build('post', { id: 'cry-post' }, { categoryIds: ['cat15'] }), - // Factory.build( - // 'post', - // { - // id: 'post-by-followed-user', - // }, - // { - // categoryIds: ['cat9'], - // author: followedUser, - // }, - // ), - // ]) - // }) - // describe('no filter', () => { - // it('returns all posts', async () => { - // const postQueryNoFilters = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // } - // } - // ` - // const expected = [{ id: 'happy-post' }, { id: 'cry-post' }, { id: 'post-by-followed-user' }] - // variables = { filter: {} } - // await expect(query({ query: postQueryNoFilters, variables })).resolves.toMatchObject({ - // data: { - // Post: expect.arrayContaining(expected), - // }, - // }) - // }) - // }) - // /* it('by categories', async () => { - // const postQueryFilteredByCategories = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // categories { - // id - // } - // } + // describe('can be filtered', () => { + // Wolle: it('by categories', async () => { + // const postQueryFilteredByCategories = gql` + // query Post($filter: _PostFilter) { + // Post(filter: $filter) { + // id + // categories { + // id // } - // ` - // const expected = { - // data: { - // Post: [ - // { - // id: 'post-by-followed-user', - // categories: [{ id: 'cat9' }], - // }, - // ], - // }, // } - // variables = { ...variables, filter: { categories_some: { id_in: ['cat9'] } } } - // await expect( - // query({ query: postQueryFilteredByCategories, variables }), - // ).resolves.toMatchObject(expected) - // }) */ - // describe('by emotions', () => { - // const postQueryFilteredByEmotions = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // emotions { - // emotion - // } - // } + // } + // ` + // const expected = { + // data: { + // Post: [ + // { + // id: 'post-by-followed-user', + // categories: [{ id: 'cat9' }], + // }, + // ], + // }, + // } + // variables = { ...variables, filter: { categories_some: { id_in: ['cat9'] } } } + // await expect( + // query({ query: postQueryFilteredByCategories, variables }), + // ).resolves.toMatchObject(expected) + // }) + // Wolle: let followedUser, happyPost, cryPost + // beforeEach(async () => { + // ;[followedUser] = await Promise.all([ + // Factory.build( + // 'user', + // { + // id: 'followed-by-me', + // name: 'Followed User', + // }, + // { + // email: 'followed@example.org', + // password: '1234', + // }, + // ), + // ]) + // ;[happyPost, cryPost] = await Promise.all([ + // Factory.build('post', { id: 'happy-post' }, { categoryIds: ['cat4'] }), + // Factory.build('post', { id: 'cry-post' }, { categoryIds: ['cat15'] }), + // Factory.build( + // 'post', + // { + // id: 'post-by-followed-user', + // }, + // { + // categoryIds: ['cat9'], + // author: followedUser, + // }, + // ), + // ]) + // }) + // describe('no filter', () => { + // it('returns all posts', async () => { + // const postQueryNoFilters = gql` + // query Post($filter: _PostFilter) { + // Post(filter: $filter) { + // id // } - // ` - // it('filters by single emotion', async () => { - // const expected = { - // data: { - // Post: [ - // { - // id: 'happy-post', - // emotions: [{ emotion: 'happy' }], - // }, - // ], - // }, + // } + // ` + // const expected = [{ id: 'happy-post' }, { id: 'cry-post' }, { id: 'post-by-followed-user' }] + // variables = { filter: {} } + // await expect(query({ query: postQueryNoFilters, variables })).resolves.toMatchObject({ + // data: { + // Post: expect.arrayContaining(expected), + // }, + // }) + // }) + // }) + // describe('by emotions', () => { + // const postQueryFilteredByEmotions = gql` + // query Post($filter: _PostFilter) { + // Post(filter: $filter) { + // id + // emotions { + // emotion // } - // await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) - // variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy'] } } } - // await expect( - // query({ query: postQueryFilteredByEmotions, variables }), - // ).resolves.toMatchObject(expected) - // }) - // it('filters by multiple emotions', async () => { - // const expected = [ + // } + // } + // ` + // it('filters by single emotion', async () => { + // const expected = { + // data: { + // Post: [ // { // id: 'happy-post', // emotions: [{ emotion: 'happy' }], // }, - // { - // id: 'cry-post', - // emotions: [{ emotion: 'cry' }], - // }, - // ] - // await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) - // await user.relateTo(cryPost, 'emoted', { emotion: 'cry' }) - // variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy', 'cry'] } } } - // await expect( - // query({ query: postQueryFilteredByEmotions, variables }), - // ).resolves.toMatchObject({ - // data: { - // Post: expect.arrayContaining(expected), - // }, - // errors: undefined, - // }) - // }) - // }) - // it('by followed-by', async () => { - // const postQueryFilteredByUsersFollowed = gql` - // query Post($filter: _PostFilter) { - // Post(filter: $filter) { - // id - // author { - // id - // name - // } - // } - // } - // ` - // await user.relateTo(followedUser, 'following') - // variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } } - // await expect( - // query({ query: postQueryFilteredByUsersFollowed, variables }), - // ).resolves.toMatchObject({ - // data: { - // Post: [ - // { - // id: 'post-by-followed-user', - // author: { id: 'followed-by-me', name: 'Followed User' }, - // }, - // ], - // }, - // errors: undefined, - // }) + // ], + // }, + // } + // await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) + // variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy'] } } } + // await expect( + // query({ query: postQueryFilteredByEmotions, variables }), + // ).resolves.toMatchObject(expected) + // }) + // it('filters by multiple emotions', async () => { + // const expected = [ + // { + // id: 'happy-post', + // emotions: [{ emotion: 'happy' }], + // }, + // { + // id: 'cry-post', + // emotions: [{ emotion: 'cry' }], + // }, + // ] + // await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) + // await user.relateTo(cryPost, 'emoted', { emotion: 'cry' }) + // variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy', 'cry'] } } } + // await expect( + // query({ query: postQueryFilteredByEmotions, variables }), + // ).resolves.toMatchObject({ + // data: { + // Post: expect.arrayContaining(expected), + // }, + // errors: undefined, // }) // }) + // }) + // it('by followed-by', async () => { + // const postQueryFilteredByUsersFollowed = gql` + // query Post($filter: _PostFilter) { + // Post(filter: $filter) { + // id + // author { + // id + // name + // } + // } + // } + // ` + // await user.relateTo(followedUser, 'following') + // variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } } + // await expect( + // query({ query: postQueryFilteredByUsersFollowed, variables }), + // ).resolves.toMatchObject({ + // data: { + // Post: [ + // { + // id: 'post-by-followed-user', + // author: { id: 'followed-by-me', name: 'Followed User' }, + // }, + // ], + // }, + // errors: undefined, + // }) + // }) + // }) }) }) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index b8e00f0ee..2dc20aebf 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -141,6 +141,14 @@ input _GroupFilter { id_not: ID id_in: [ID!] id_not_in: [ID!] + # categories: _CategoryFilter + # categories_not: _CategoryFilter + # categories_in: [_CategoryFilter!] + # categories_not_in: [_CategoryFilter!] + # categories_some: _CategoryFilter + # categories_none: _CategoryFilter + # categories_single: _CategoryFilter + # categories_every: _CategoryFilter # Wolle: # friends: _GroupFilter # friends_not: _GroupFilter @@ -185,7 +193,11 @@ type Query { filter: _GroupFilter ): [Group] - availableGroupTypes: [GroupType]! + AvailableGroupTypes: [GroupType]! + + AvailableGroupActionRadii: [GroupActionRadius]! + + AvailableGroupMemberRoles: [GroupMemberRole]! # Wolle: # availableRoles: [UserRole]! From 61344fc96bb920e3fe04da97a3ee67dbcc643b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 9 Aug 2022 08:50:25 +0200 Subject: [PATCH 3/3] Implement errors for to less or to many categories and test it --- backend/src/constants/categories.js | 5 +++++ backend/src/schema/resolvers/groups.js | 7 ++++++ backend/src/schema/resolvers/groups.spec.js | 24 ++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 backend/src/constants/categories.js diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js new file mode 100644 index 000000000..37cac8151 --- /dev/null +++ b/backend/src/constants/categories.js @@ -0,0 +1,5 @@ +// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js` +export default { + CATEGORIES_MIN: 1, + CATEGORIES_MAX: 3, +} diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index be07fecc6..a958e990e 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid' // Wolle: import { isEmpty } from 'lodash' import { UserInputError } from 'apollo-server' import CONFIG from '../../config' +import categories from '../../constants/categories' // Wolle: import { mergeImage, deleteImage } from './images/images' import Resolver from './helpers/Resolver' // Wolle: import { filterForMutedUsers } from './helpers/filterForMutedUsers' @@ -69,6 +70,12 @@ export default { CreateGroup: async (_parent, params, context, _resolveInfo) => { const { categoryIds } = params delete params.categoryIds + if (!categoryIds || categoryIds.length < categories.CATEGORIES_MIN) { + throw new UserInputError('To Less Categories!') + } + if (categoryIds && categoryIds.length > categories.CATEGORIES_MAX) { + throw new UserInputError('To Many Categories!') + } params.id = params.id || uuid() const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index dd5a48568..8f20c4fa7 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -416,12 +416,34 @@ describe('CreateGroup', () => { ) }) - it('`disabled` and `deleted` default to `false`', async () => { + it('"disabled" and "deleted" default to "false"', async () => { const expected = { data: { CreateGroup: { disabled: false, deleted: false } } } await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( expected, ) }) + + describe('categories', () => { + describe('not even one', () => { + it('throws error: "To Less Categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { ...variables, categoryIds: null }, + }) + expect(errors[0]).toHaveProperty('message', 'To Less Categories!') + }) + }) + + describe('four', () => { + it('throws error: "To Many Categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] }, + }) + expect(errors[0]).toHaveProperty('message', 'To Many Categories!') + }) + }) + }) }) })