mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch '5059-groups/5131-implement-update-group-resolver' into 5140-My-Groups-Page
This commit is contained in:
commit
dbb8006432
@ -1,2 +1,3 @@
|
||||
// this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js`
|
||||
export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags
|
||||
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 120 // with removed HTML tags
|
||||
|
||||
@ -12,6 +12,7 @@ export const createGroupMutation = gql`
|
||||
$groupType: GroupType!
|
||||
$actionRadius: GroupActionRadius!
|
||||
$categoryIds: [ID]
|
||||
$locationName: String
|
||||
) {
|
||||
CreateGroup(
|
||||
id: $id
|
||||
@ -22,6 +23,7 @@ export const createGroupMutation = gql`
|
||||
groupType: $groupType
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
name
|
||||
@ -34,6 +36,60 @@ export const createGroupMutation = gql`
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const updateGroupMutation = gql`
|
||||
mutation (
|
||||
$id: ID!
|
||||
$name: String
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String
|
||||
$actionRadius: GroupActionRadius
|
||||
$categoryIds: [ID]
|
||||
$avatar: ImageInput
|
||||
$locationName: String
|
||||
) {
|
||||
UpdateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
# avatar # test this as result
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
@ -112,6 +168,8 @@ export const groupQuery = gql`
|
||||
name
|
||||
icon
|
||||
}
|
||||
# avatar # test this as result
|
||||
# locationName # test this as result
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import trunc from 'trunc-html'
|
||||
import { DESCRIPTION_EXCERPT_HTML_LENGTH } from '../constants/groups'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateGroup: async (resolve, root, args, context, info) => {
|
||||
args.descriptionExcerpt = trunc(args.description, 120).html
|
||||
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdateGroup: async (resolve, root, args, context, info) => {
|
||||
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
|
||||
@ -52,6 +52,36 @@ const isMySocialMedia = rule({
|
||||
return socialMedia.ownedBy.node.id === user.id
|
||||
})
|
||||
|
||||
const isAllowedToChangeGroupSettings = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
if (!(user && user.id)) return false
|
||||
const ownerId = user.id
|
||||
const { id: groupId } = args
|
||||
const session = driver.session()
|
||||
const readTxPromise = session.readTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (owner:User {id: $ownerId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||
RETURN group {.*}, owner {.*, myRoleInGroup: membership.role}
|
||||
`,
|
||||
{ groupId, ownerId },
|
||||
)
|
||||
return {
|
||||
owner: transactionResponse.records.map((record) => record.get('owner'))[0],
|
||||
group: transactionResponse.records.map((record) => record.get('group'))[0],
|
||||
}
|
||||
})
|
||||
try {
|
||||
const { owner, group } = await readTxPromise
|
||||
return !!group && !!owner && ['owner'].includes(owner.myRoleInGroup)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedSeeingMembersOfGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
@ -252,6 +282,7 @@ export default shield(
|
||||
SignupVerification: allow,
|
||||
UpdateUser: onlyYourself,
|
||||
CreateGroup: isAuthenticated,
|
||||
UpdateGroup: isAllowedToChangeGroupSettings,
|
||||
JoinGroup: isAllowedToJoinGroup,
|
||||
ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole,
|
||||
CreatePost: isAuthenticated,
|
||||
|
||||
@ -30,6 +30,10 @@ export default {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdateGroup: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
|
||||
@ -2,12 +2,13 @@ import { getNeode, getDriver } from '../db/neo4j'
|
||||
import createServer from '../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import { createGroupMutation } from '../db/graphql/groups'
|
||||
import { createGroupMutation, updateGroupMutation } from '../db/graphql/groups'
|
||||
import { createPostMutation } from '../db/graphql/posts'
|
||||
import { signupVerificationMutation } from '../db/graphql/authentications'
|
||||
|
||||
let authenticatedUser
|
||||
let variables
|
||||
const categoryIds = ['cat9']
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
@ -62,8 +63,6 @@ afterEach(async () => {
|
||||
|
||||
describe('slugifyMiddleware', () => {
|
||||
describe('CreateGroup', () => {
|
||||
const categoryIds = ['cat9']
|
||||
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
...variables,
|
||||
@ -130,15 +129,14 @@ describe('slugifyMiddleware', () => {
|
||||
})
|
||||
|
||||
it('chooses another slug', async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
about: 'As an about',
|
||||
}
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables,
|
||||
variables: {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
about: 'As an about',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
@ -151,15 +149,17 @@ describe('slugifyMiddleware', () => {
|
||||
|
||||
describe('but if the client specifies a slug', () => {
|
||||
it('rejects CreateGroup', async (done) => {
|
||||
variables = {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
about: 'As an about',
|
||||
slug: 'pre-existing-group',
|
||||
}
|
||||
try {
|
||||
await expect(
|
||||
mutate({ mutation: createGroupMutation, variables }),
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
about: 'As an about',
|
||||
slug: 'pre-existing-group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [
|
||||
{
|
||||
@ -189,9 +189,163 @@ describe('slugifyMiddleware', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('CreatePost', () => {
|
||||
const categoryIds = ['cat9']
|
||||
describe('UpdateGroup', () => {
|
||||
let createGroupResult
|
||||
|
||||
beforeEach(async () => {
|
||||
createGroupResult = await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
name: 'The Best Group',
|
||||
slug: 'the-best-group',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('if group exists', () => {
|
||||
describe('if new slug not(!) exists', () => {
|
||||
describe('setting slug by group name', () => {
|
||||
it('has the new slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
name: 'My Best Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
name: 'My Best Group',
|
||||
slug: 'my-best-group',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setting slug explicitly', () => {
|
||||
it('has the new slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
slug: 'my-best-group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
name: 'The Best Group',
|
||||
slug: 'my-best-group',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('if new slug exists in another group', () => {
|
||||
beforeEach(async () => {
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
name: 'Pre-Existing Group',
|
||||
slug: 'pre-existing-group',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('setting slug by group name', () => {
|
||||
it('has unique slug "*-1"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
name: 'Pre-Existing Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
name: 'Pre-Existing Group',
|
||||
slug: 'pre-existing-group-1',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setting slug explicitly', () => {
|
||||
it('rejects UpdateGroup', async (done) => {
|
||||
try {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
slug: 'pre-existing-group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [
|
||||
{
|
||||
message: 'Group with this slug already exists!',
|
||||
},
|
||||
],
|
||||
})
|
||||
done()
|
||||
} catch (error) {
|
||||
throw new Error(`
|
||||
${error}
|
||||
|
||||
Probably your database has no unique constraints!
|
||||
|
||||
To see all constraints go to http://localhost:7474/browser/ and
|
||||
paste the following:
|
||||
\`\`\`
|
||||
CALL db.constraints();
|
||||
\`\`\`
|
||||
|
||||
Learn how to setup the database here:
|
||||
https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/backend/README.md#database-indices-and-constraints
|
||||
`)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('CreatePost', () => {
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
...variables,
|
||||
@ -252,16 +406,15 @@ describe('slugifyMiddleware', () => {
|
||||
})
|
||||
|
||||
it('chooses another slug', async () => {
|
||||
variables = {
|
||||
...variables,
|
||||
title: 'Pre-existing post',
|
||||
content: 'Some content',
|
||||
categoryIds,
|
||||
}
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createPostMutation,
|
||||
variables,
|
||||
variables: {
|
||||
...variables,
|
||||
title: 'Pre-existing post',
|
||||
content: 'Some content',
|
||||
categoryIds,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
@ -274,16 +427,18 @@ describe('slugifyMiddleware', () => {
|
||||
|
||||
describe('but if the client specifies a slug', () => {
|
||||
it('rejects CreatePost', async (done) => {
|
||||
variables = {
|
||||
...variables,
|
||||
title: 'Pre-existing post',
|
||||
content: 'Some content',
|
||||
slug: 'pre-existing-post',
|
||||
categoryIds,
|
||||
}
|
||||
try {
|
||||
await expect(
|
||||
mutate({ mutation: createPostMutation, variables }),
|
||||
mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
title: 'Pre-existing post',
|
||||
content: 'Some content',
|
||||
slug: 'pre-existing-post',
|
||||
categoryIds,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [
|
||||
{
|
||||
@ -313,6 +468,8 @@ describe('slugifyMiddleware', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.todo('UpdatePost')
|
||||
|
||||
describe('SignupVerification', () => {
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
|
||||
@ -5,38 +5,40 @@ import { CATEGORIES_MIN, CATEGORIES_MAX } from '../../constants/categories'
|
||||
import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups'
|
||||
import { removeHtmlTags } from '../../middleware/helpers/cleanHtml.js'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { mergeImage } from './images/images'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Group: async (_object, params, context, _resolveInfo) => {
|
||||
const { isMember } = params
|
||||
const { id: groupId, isMember } = params
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const groupIdCypher = groupId ? ` {id: "${groupId}"}` : ''
|
||||
let groupCypher
|
||||
if (isMember === true) {
|
||||
groupCypher = `
|
||||
MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group:Group)
|
||||
MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group:Group${groupIdCypher})
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`
|
||||
} else {
|
||||
if (isMember === false) {
|
||||
groupCypher = `
|
||||
MATCH (group:Group)
|
||||
MATCH (group:Group${groupIdCypher})
|
||||
WHERE NOT (:User {id: $userId})-[:MEMBER_OF]->(group)
|
||||
RETURN group {.*, myRole: NULL}
|
||||
`
|
||||
} else {
|
||||
groupCypher = `
|
||||
MATCH (group:Group)
|
||||
MATCH (group:Group${groupIdCypher})
|
||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`
|
||||
}
|
||||
}
|
||||
const result = await txc.run(groupCypher, {
|
||||
const transactionResponse = await txc.run(groupCypher, {
|
||||
userId: context.user.id,
|
||||
})
|
||||
return result.records.map((record) => record.get('group'))
|
||||
return transactionResponse.records.map((record) => record.get('group'))
|
||||
})
|
||||
try {
|
||||
return await readTxResultPromise
|
||||
@ -54,10 +56,10 @@ export default {
|
||||
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
|
||||
RETURN user {.*, myRoleInGroup: membership.role}
|
||||
`
|
||||
const result = await txc.run(groupMemberCypher, {
|
||||
const transactionResponse = await txc.run(groupMemberCypher, {
|
||||
groupId,
|
||||
})
|
||||
return result.records.map((record) => record.get('user'))
|
||||
return transactionResponse.records.map((record) => record.get('user'))
|
||||
})
|
||||
try {
|
||||
return await readTxResultPromise
|
||||
@ -131,6 +133,76 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
UpdateGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
const { id: groupId, avatar: avatarInput } = params
|
||||
delete params.categoryIds
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds) {
|
||||
if (categoryIds.length < CATEGORIES_MIN) {
|
||||
throw new UserInputError('Too view categories!')
|
||||
}
|
||||
if (categoryIds.length > CATEGORIES_MAX) {
|
||||
throw new UserInputError('Too many categories!')
|
||||
}
|
||||
}
|
||||
if (
|
||||
params.description &&
|
||||
removeHtmlTags(params.description).length < DESCRIPTION_WITHOUT_HTML_LENGTH_MIN
|
||||
) {
|
||||
throw new UserInputError('Description too short!')
|
||||
}
|
||||
const session = context.driver.session()
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
RETURN group, category
|
||||
`
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(cypherDeletePreviousRelations, { groupId })
|
||||
})
|
||||
}
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
let updateGroupCypher = `
|
||||
MATCH (group:Group {id: $groupId})
|
||||
SET group += $params
|
||||
SET group.updatedAt = toString(datetime())
|
||||
WITH group
|
||||
`
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
updateGroupCypher += `
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (group)-[:CATEGORIZED]->(category)
|
||||
WITH group
|
||||
`
|
||||
}
|
||||
updateGroupCypher += `
|
||||
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
`
|
||||
const transactionResponse = await transaction.run(updateGroupCypher, {
|
||||
groupId,
|
||||
userId: context.user.id,
|
||||
categoryIds,
|
||||
params,
|
||||
})
|
||||
const [group] = await transactionResponse.records.map((record) => record.get('group'))
|
||||
if (avatarInput) {
|
||||
await mergeImage(group, 'AVATAR_IMAGE', avatarInput, { transaction })
|
||||
}
|
||||
return group
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('Group with this slug already exists!')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
JoinGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { groupId, userId } = params
|
||||
const session = context.driver.session()
|
||||
@ -148,8 +220,8 @@ export default {
|
||||
END
|
||||
RETURN member {.*, myRoleInGroup: membership.role}
|
||||
`
|
||||
const result = await transaction.run(joinGroupCypher, { groupId, userId })
|
||||
const [member] = await result.records.map((record) => record.get('member'))
|
||||
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
|
||||
const [member] = await transactionResponse.records.map((record) => record.get('member'))
|
||||
return member
|
||||
})
|
||||
try {
|
||||
@ -176,8 +248,12 @@ export default {
|
||||
membership.role = $roleInGroup
|
||||
RETURN member {.*, myRoleInGroup: membership.role}
|
||||
`
|
||||
const result = await transaction.run(joinGroupCypher, { groupId, userId, roleInGroup })
|
||||
const [member] = await result.records.map((record) => record.get('member'))
|
||||
const transactionResponse = await transaction.run(joinGroupCypher, {
|
||||
groupId,
|
||||
userId,
|
||||
roleInGroup,
|
||||
})
|
||||
const [member] = await transactionResponse.records.map((record) => record.get('member'))
|
||||
return member
|
||||
})
|
||||
try {
|
||||
|
||||
@ -2,6 +2,7 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import {
|
||||
createGroupMutation,
|
||||
updateGroupMutation,
|
||||
joinGroupMutation,
|
||||
changeGroupMemberRoleMutation,
|
||||
groupMembersQuery,
|
||||
@ -106,6 +107,7 @@ describe('in mode', () => {
|
||||
groupType: 'public',
|
||||
actionRadius: 'regional',
|
||||
categoryIds,
|
||||
// locationName, // test this as result
|
||||
}
|
||||
})
|
||||
|
||||
@ -129,6 +131,9 @@ describe('in mode', () => {
|
||||
name: 'The Best Group',
|
||||
slug: 'the-group',
|
||||
about: 'We will change the world!',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'public',
|
||||
actionRadius: 'regional',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -161,7 +166,7 @@ describe('in mode', () => {
|
||||
describe('description', () => {
|
||||
describe('length without HTML', () => {
|
||||
describe('less then 100 chars', () => {
|
||||
it('throws error: "Too view categories!"', async () => {
|
||||
it('throws error: "Description too short!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
@ -182,13 +187,50 @@ describe('in mode', () => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
})
|
||||
|
||||
describe('not even one', () => {
|
||||
it('throws error: "Too view categories!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: { ...variables, categoryIds: null },
|
||||
describe('with matching amount of categories', () => {
|
||||
it('has new categories', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
categoryIds: ['cat4', 'cat27'],
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
CreateGroup: {
|
||||
categories: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'cat4' }),
|
||||
expect.objectContaining({ id: 'cat27' }),
|
||||
]),
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not even one', () => {
|
||||
describe('by "categoryIds: null"', () => {
|
||||
it('throws error: "Too view categories!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: { ...variables, categoryIds: null },
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Too view categories!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('by "categoryIds: []"', () => {
|
||||
it('throws error: "Too view categories!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: { ...variables, categoryIds: [] },
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Too view categories!')
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Too view categories!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -287,6 +329,61 @@ describe('in mode', () => {
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
})
|
||||
|
||||
it('has set categories', async () => {
|
||||
await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({
|
||||
data: {
|
||||
Group: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'my-group',
|
||||
slug: 'the-best-group',
|
||||
categories: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'cat4' }),
|
||||
expect.objectContaining({ id: 'cat9' }),
|
||||
expect.objectContaining({ id: 'cat15' }),
|
||||
]),
|
||||
myRole: 'owner',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'others-group',
|
||||
slug: 'uninteresting-group',
|
||||
categories: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'cat4' }),
|
||||
expect.objectContaining({ id: 'cat9' }),
|
||||
expect.objectContaining({ id: 'cat15' }),
|
||||
]),
|
||||
myRole: null,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("id = 'my-group'", () => {
|
||||
it('finds only the group with this id', async () => {
|
||||
await expect(
|
||||
query({ query: groupQuery, variables: { id: 'my-group' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
Group: [
|
||||
expect.objectContaining({
|
||||
id: 'my-group',
|
||||
slug: 'the-best-group',
|
||||
myRole: 'owner',
|
||||
}),
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isMember = true', () => {
|
||||
@ -2043,5 +2140,211 @@ describe('in mode', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdateGroup', () => {
|
||||
beforeAll(async () => {
|
||||
await seedBasicsAndClearAuthentication()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({
|
||||
query: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
slug: 'my-best-group',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
let otherUser
|
||||
|
||||
beforeAll(async () => {
|
||||
otherUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'other-user',
|
||||
name: 'Other TestUser',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
authenticatedUser = await otherUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'others-group',
|
||||
name: 'Uninteresting Group',
|
||||
about: 'We will change nothing!',
|
||||
description: 'We love it like it is!?' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'global',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
name: 'The Best Group',
|
||||
about: 'We will change the world!',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'public',
|
||||
actionRadius: 'regional',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('change group settings', () => {
|
||||
describe('as owner', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
it('has set the settings', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
name: 'The New Group For Our Country',
|
||||
about: 'We will change the land!',
|
||||
description: 'Some country relevant description' + descriptionAdditional100,
|
||||
actionRadius: 'national',
|
||||
// avatar, // test this as result
|
||||
// locationName, // test this as result
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
id: 'my-group',
|
||||
name: 'The New Group For Our Country',
|
||||
slug: 'the-new-group-for-our-country', // changing the slug is tested in the slugifyMiddleware
|
||||
about: 'We will change the land!',
|
||||
description: 'Some country relevant description' + descriptionAdditional100,
|
||||
actionRadius: 'national',
|
||||
// avatar, // test this as result
|
||||
// locationName, // test this as result
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
describe('description', () => {
|
||||
describe('length without HTML', () => {
|
||||
describe('less then 100 chars', () => {
|
||||
it('throws error: "Description too short!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
description:
|
||||
'0123456789' +
|
||||
'<a href="https://domain.org/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789">0123456789</a>',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Description too short!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
beforeEach(async () => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
})
|
||||
|
||||
describe('with matching amount of categories', () => {
|
||||
it('has new categories', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
categoryIds: ['cat4', 'cat27'],
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
id: 'my-group',
|
||||
categories: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'cat4' }),
|
||||
expect.objectContaining({ id: 'cat27' }),
|
||||
]),
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not even one', () => {
|
||||
describe('by "categoryIds: []"', () => {
|
||||
it('throws error: "Too view categories!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
categoryIds: [],
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Too view categories!')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('four', () => {
|
||||
it('throws error: "Too many categories!"', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'],
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Too many categories!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as no(!) owner', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = await otherUser.toJson()
|
||||
const { errors } = await mutate({
|
||||
mutation: updateGroupMutation,
|
||||
variables: {
|
||||
id: 'my-group',
|
||||
name: 'The New Group For Our Country',
|
||||
about: 'We will change the land!',
|
||||
description: 'Some country relevant description' + descriptionAdditional100,
|
||||
actionRadius: 'national',
|
||||
categoryIds: ['cat4', 'cat27'], // test this as result
|
||||
// avatar, // test this as result
|
||||
// locationName, // test this as result
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -131,11 +131,11 @@ export default {
|
||||
delete params.image
|
||||
const session = context.driver.session()
|
||||
let updatePostCypher = `
|
||||
MATCH (post:Post {id: $params.id})
|
||||
SET post += $params
|
||||
SET post.updatedAt = toString(datetime())
|
||||
WITH post
|
||||
`
|
||||
MATCH (post:Post {id: $params.id})
|
||||
SET post += $params
|
||||
SET post.updatedAt = toString(datetime())
|
||||
WITH post
|
||||
`
|
||||
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
const cypherDeletePreviousRelations = `
|
||||
|
||||
@ -368,7 +368,7 @@ describe('UpdatePost', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = null
|
||||
expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({
|
||||
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorized!' }],
|
||||
data: { UpdatePost: null },
|
||||
})
|
||||
|
||||
@ -67,6 +67,9 @@ type Query {
|
||||
updatedAt: String
|
||||
about: String
|
||||
description: String
|
||||
# groupType: GroupType # test this
|
||||
# actionRadius: GroupActionRadius # test this
|
||||
# avatar: ImageInput # test this
|
||||
locationName: String
|
||||
first: Int
|
||||
offset: Int
|
||||
@ -93,24 +96,27 @@ type Mutation {
|
||||
id: ID
|
||||
name: String!
|
||||
slug: String
|
||||
avatar: ImageInput
|
||||
about: String
|
||||
description: String!
|
||||
groupType: GroupType!
|
||||
actionRadius: GroupActionRadius!
|
||||
categoryIds: [ID]
|
||||
locationName: String
|
||||
# avatar: ImageInput # a group can not be created with an avatar
|
||||
locationName: String # test this as result
|
||||
): Group
|
||||
|
||||
# UpdateGroup(
|
||||
# id: ID!
|
||||
# name: String
|
||||
# slug: String
|
||||
# avatar: ImageInput
|
||||
# locationName: String
|
||||
# about: String
|
||||
# description: String
|
||||
# ): Group
|
||||
UpdateGroup(
|
||||
id: ID!
|
||||
name: String
|
||||
slug: String
|
||||
about: String
|
||||
description: String
|
||||
# groupType: GroupType # is not possible at the moment and has to be discussed. may be in the stronger direction: public → closed → hidden
|
||||
actionRadius: GroupActionRadius
|
||||
categoryIds: [ID]
|
||||
avatar: ImageInput # test this as result
|
||||
locationName: String # test this as result
|
||||
): Group
|
||||
|
||||
# DeleteGroup(id: ID!): Group
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user