Merge branch 'master' into 212-change-password

This commit is contained in:
Lala Sabathil 2019-03-13 16:50:19 +01:00 committed by GitHub
commit 12fc42973a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 211 additions and 120 deletions

View File

@ -6,9 +6,11 @@ import reports from './resolvers/reports.js'
import posts from './resolvers/posts.js'
import moderation from './resolvers/moderation.js'
export const typeDefs =
fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql'))
.toString('utf-8')
export const typeDefs = fs
.readFileSync(
process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')
)
.toString('utf-8')
export const resolvers = {
Query: {

View File

@ -58,8 +58,14 @@ const permissions = shield({
DeleteBadge: isAdmin,
enable: isModerator,
disable: isModerator,
changePassword: isAuthenticated
// addFruitToBasket: isAuthenticated
follow: isAuthenticated,
unfollow: isAuthenticated,
shout: isAuthenticated,
unshout: isAuthenticated,
changePassword: isAuthenticated,
enable: isModerator,
disable: isModerator
// CreateUser: allow,
},
User: {

View File

@ -1,12 +1,14 @@
import uniqueSlug from './slugify/uniqueSlug'
const isUniqueFor = (context, type) => {
return async (slug) => {
return async slug => {
const session = context.driver.session()
const response = await session.run(
`MATCH(p:${type} {slug: $slug }) return p.slug`, {
`MATCH(p:${type} {slug: $slug }) return p.slug`,
{
slug
})
}
)
session.close()
return response.records.length === 0
}
@ -15,19 +17,27 @@ const isUniqueFor = (context, type) => {
export default {
Mutation: {
CreatePost: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.title, isUniqueFor(context, 'Post'))
args.slug =
args.slug ||
(await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
return resolve(root, args, context, info)
},
CreateUser: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'User'))
args.slug =
args.slug ||
(await uniqueSlug(args.name, isUniqueFor(context, 'User')))
return resolve(root, args, context, info)
},
CreateOrganization: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Organization'))
args.slug =
args.slug ||
(await uniqueSlug(args.name, isUniqueFor(context, 'Organization')))
return resolve(root, args, context, info)
},
CreateCategory: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Category'))
args.slug =
args.slug ||
(await uniqueSlug(args.name, isUniqueFor(context, 'Category')))
return resolve(root, args, context, info)
}
}

View File

@ -8,7 +8,10 @@ const factory = Factory()
beforeEach(async () => {
await factory.create('User', { email: 'user@example.org', password: '1234' })
await factory.create('User', { email: 'someone@example.org', password: '1234' })
await factory.create('User', {
email: 'someone@example.org',
password: '1234'
})
headers = await login({ email: 'user@example.org', password: '1234' })
authenticatedClient = new GraphQLClient(host, { headers })
})
@ -26,7 +29,9 @@ describe('slugify', () => {
content: "Some content"
) { slug }
}`)
expect(response).toEqual({ CreatePost: { slug: 'i-am-a-brand-new-post' } })
expect(response).toEqual({
CreatePost: { slug: 'i-am-a-brand-new-post' }
})
})
describe('if slug exists', () => {
@ -48,12 +53,15 @@ describe('slugify', () => {
content: "Some content"
) { slug }
}`)
expect(response).toEqual({ CreatePost: { slug: 'pre-existing-post-1' } })
expect(response).toEqual({
CreatePost: { slug: 'pre-existing-post-1' }
})
})
describe('but if the client specifies a slug', () => {
it('rejects CreatePost', async () => {
await expect(authenticatedClient.request(`mutation {
await expect(
authenticatedClient.request(`mutation {
CreatePost(
title: "Pre-existing post",
content: "Some content",
@ -73,28 +81,33 @@ describe('slugify', () => {
}`)
}
it('generates a slug based on name', async () => {
await expect(action('CreateUser', 'name: "I am a user"'))
.resolves.toEqual({ CreateUser: { slug: 'i-am-a-user' } })
await expect(
action('CreateUser', 'name: "I am a user"')
).resolves.toEqual({ CreateUser: { slug: 'i-am-a-user' } })
})
describe('if slug exists', () => {
beforeEach(async () => {
await action('CreateUser', 'name: "Pre-existing user", slug: "pre-existing-user"')
await action(
'CreateUser',
'name: "Pre-existing user", slug: "pre-existing-user"'
)
})
it('chooses another slug', async () => {
await expect(action(
'CreateUser',
'name: "pre-existing-user"'
)).resolves.toEqual({ CreateUser: { slug: 'pre-existing-user-1' } })
await expect(
action('CreateUser', 'name: "pre-existing-user"')
).resolves.toEqual({ CreateUser: { slug: 'pre-existing-user-1' } })
})
describe('but if the client specifies a slug', () => {
it('rejects CreateUser', async () => {
await expect(action(
'CreateUser',
'name: "Pre-existing user", slug: "pre-existing-user"'
)).rejects.toThrow('already exists')
await expect(
action(
'CreateUser',
'name: "Pre-existing user", slug: "pre-existing-user"'
)
).rejects.toThrow('already exists')
})
})
})

View File

@ -4,6 +4,7 @@ import { host, login } from '../jest/helpers'
const factory = Factory()
let clientUser1
let headersUser1
const mutationFollowUser = (id) => `
mutation {
@ -27,18 +28,25 @@ beforeEach(async () => {
email: 'test2@example.org',
password: '1234'
})
headersUser1 = await login({ email: 'test@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('follow ', () => {
describe('(un)follow user', () => {
let headersUser1
beforeEach(async () => {
headersUser1 = await login({ email: 'test@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
describe('follow', () => {
describe('follow user', () => {
describe('unauthenticated follow', () => {
it('throws authorization error', async () => {
let client
client = new GraphQLClient(host)
await expect(
client.request(mutationFollowUser('u2'))
).rejects.toThrow('Not Authorised')
})
})
it('I can follow another user', async () => {
@ -65,31 +73,6 @@ describe('follow ', () => {
expect(User[0]).toMatchObject(expected2)
})
it('I can unfollow a user', async () => {
// follow
await clientUser1.request(
mutationFollowUser('u2')
)
const expected = {
unfollow: true
}
// unfollow
const res = await clientUser1.request(mutationUnfollowUser('u2'))
expect(res).toMatchObject(expected)
const { User } = await clientUser1.request(`{
User(id: "u2") {
followedBy { id }
followedByCurrentUser
}
}`)
const expected2 = {
followedBy: [],
followedByCurrentUser: false
}
expect(User[0]).toMatchObject(expected2)
})
it('I can`t follow myself', async () => {
const res = await clientUser1.request(
mutationFollowUser('u1')
@ -112,4 +95,45 @@ describe('follow ', () => {
expect(User[0]).toMatchObject(expected2)
})
})
describe('unfollow user', () => {
describe('unauthenticated follow', () => {
it('throws authorization error', async () => {
// follow
await clientUser1.request(
mutationFollowUser('u2')
)
// unfollow
let client
client = new GraphQLClient(host)
await expect(
client.request(mutationUnfollowUser('u2'))
).rejects.toThrow('Not Authorised')
})
})
it('I can unfollow a user', async () => {
// follow
await clientUser1.request(
mutationFollowUser('u2')
)
// unfollow
const expected = {
unfollow: true
}
const res = await clientUser1.request(mutationUnfollowUser('u2'))
expect(res).toMatchObject(expected)
const { User } = await clientUser1.request(`{
User(id: "u2") {
followedBy { id }
followedByCurrentUser
}
}`)
const expected2 = {
followedBy: [],
followedByCurrentUser: false
}
expect(User[0]).toMatchObject(expected2)
})
})
})

View File

@ -4,6 +4,7 @@ import { host, login } from '../jest/helpers'
const factory = Factory()
let clientUser1, clientUser2
let headersUser1, headersUser2
const mutationShoutPost = (id) => `
mutation {
@ -27,37 +28,44 @@ beforeEach(async () => {
email: 'test2@example.org',
password: '1234'
})
headersUser1 = await login({ email: 'test@example.org', password: '1234' })
headersUser2 = await login({ email: 'test2@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
clientUser2 = new GraphQLClient(host, { headers: headersUser2 })
await clientUser1.request(`
mutation {
CreatePost(id: "p1", title: "Post Title 1", content: "Some Post Content 1") {
id
title
}
}
`)
await clientUser2.request(`
mutation {
CreatePost(id: "p2", title: "Post Title 2", content: "Some Post Content 2") {
id
title
}
}
`)
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('shout ', () => {
describe('(un)shout foreign post', () => {
let headersUser1, headersUser2
beforeEach(async () => {
headersUser1 = await login({ email: 'test@example.org', password: '1234' })
headersUser2 = await login({ email: 'test2@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
clientUser2 = new GraphQLClient(host, { headers: headersUser2 })
await clientUser1.request(`
mutation {
CreatePost(id: "p1", title: "Post Title 1", content: "Some Post Content 1") {
id
title
}
}
`)
await clientUser2.request(`
mutation {
CreatePost(id: "p2", title: "Post Title 2", content: "Some Post Content 2") {
id
title
}
}
`)
describe('shout', () => {
describe('shout foreign post', () => {
describe('unauthenticated shout', () => {
it('throws authorization error', async () => {
let client
client = new GraphQLClient(host)
await expect(
client.request(mutationShoutPost('p1'))
).rejects.toThrow('Not Authorised')
})
})
it('I shout a post of another user', async () => {
@ -80,29 +88,6 @@ describe('shout ', () => {
expect(Post[0]).toMatchObject(expected2)
})
it('I unshout a post of another user', async () => {
// shout
await clientUser1.request(
mutationShoutPost('p2')
)
const expected = {
unshout: true
}
// unshout
const res = await clientUser1.request(mutationUnshoutPost('p2'))
expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{
Post(id: "p2") {
shoutedByCurrentUser
}
}`)
const expected2 = {
shoutedByCurrentUser: false
}
expect(Post[0]).toMatchObject(expected2)
})
it('I can`t shout my own post', async () => {
const res = await clientUser1.request(
mutationShoutPost('p1')
@ -123,4 +108,44 @@ describe('shout ', () => {
expect(Post[0]).toMatchObject(expected2)
})
})
describe('unshout foreign post', () => {
describe('unauthenticated shout', () => {
it('throws authorization error', async () => {
// shout
await clientUser1.request(
mutationShoutPost('p2')
)
// unshout
let client
client = new GraphQLClient(host)
await expect(
client.request(mutationUnshoutPost('p2'))
).rejects.toThrow('Not Authorised')
})
})
it('I unshout a post of another user', async () => {
// shout
await clientUser1.request(
mutationShoutPost('p2')
)
const expected = {
unshout: true
}
// unshout
const res = await clientUser1.request(mutationUnshoutPost('p2'))
expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{
Post(id: "p2") {
shoutedByCurrentUser
}
}`)
const expected2 = {
shoutedByCurrentUser: false
}
expect(Post[0]).toMatchObject(expected2)
})
})
})

View File

@ -4,6 +4,18 @@ type Query {
currentUser: User
"Get the latest Network Statistics"
statistics: Statistics!
findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
statement: """
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
YIELD node as post, score
MATCH (post)<-[:WROTE]-(user:User)
WHERE score >= 0.2
AND NOT user.deleted = true AND NOT user.disabled = true
AND NOT post.deleted = true AND NOT post.disabled = true
RETURN post
LIMIT $limit
"""
)
}
type Mutation {
"Get a JWT Token for the given Email and password"
@ -27,7 +39,6 @@ type Mutation {
DELETE r
RETURN COUNT(r) > 0
""")
"Follow the given Type and ID"
follow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
MATCH (n {id: $id}), (u:User {id: $cypherParams.currentUserId})

View File

@ -1,6 +1,5 @@
import { GraphQLClient, request } from 'graphql-request'
import { getDriver } from '../../bootstrap/neo4j'
import createBadge from './badges.js'
import createUser from './users.js'
import createOrganization from './organizations.js'
@ -23,26 +22,24 @@ const authenticatedHeaders = async ({ email, password }, host) => {
}
}
const factories = {
'Badge': createBadge,
'User': createUser,
'Organization': createOrganization,
'Post': createPost,
'Comment': createComment,
'Category': createCategory,
'Tag': createTag,
'Report': createReport
Badge: createBadge,
User: createUser,
Organization: createOrganization,
Post: createPost,
Comment: createComment,
Category: createCategory,
Tag: createTag,
Report: createReport
}
export const cleanDatabase = async (options = {}) => {
const {
driver = getDriver()
} = options
const { driver = getDriver() } = options
const session = driver.session()
const cypher = 'MATCH (n) DETACH DELETE n'
try {
return await session.run(cypher)
} catch (error) {
throw (error)
throw error
} finally {
session.close()
}
@ -63,7 +60,10 @@ export default function Factory (options = {}) {
factories,
lastResponse: null,
async authenticateAs ({ email, password }) {
const headers = await authenticatedHeaders({ email, password }, seedServerHost)
const headers = await authenticatedHeaders(
{ email, password },
seedServerHost
)
this.lastResponse = headers
this.graphQLClient = new GraphQLClient(seedServerHost, { headers })
return this