Files
speckle-server/packages/server/modules/comments/tests/comments.graph.spec.js
T
Gergő Jedlicska 67cb97a262 gergo/testCommentsGQL (#775)
* yarn first go

* fix frontend build cache loader

* yarn workspaces built server Docker

* build(yarn): add workspaces plugin config

* chore(package defs): clean package*.json -s

* chore(gitignore): ignore yarn error log

* build(yarn): update yarn lock

* build(preview-service webpack): add extra resolved path to preview service webpack config

because of yarn package hoisting, there are no package level node_modules folder anymore.

* build(docker): update dockerignore with yarn specific configs

* build(docker): update Dockerfiles for yarn workspaces utilization

* ci(circleci): update server test job to yarn

* ci(circle): disable cache restore

* ci(circleci): trying the node orb yarn-run

* ci(circleci): yarn-run again

* ci(circleci): disable node orb

* ci(circleci): change base node image for tests

* ci(circleci): add yarn cache

* ci(circleci): remove node install step

* ci(circleci): add server specific cache archives

* ci(circleci): test build and publish

* ci(circleci): change npm auth method to suit yarn

* ci(circleci): trying new builder image

* ci(circleci): another base image, maybe this works

* ci(circleci): force a specific docker engine version

* ci(circleci): add yarn version plugin and its changes

* ci(circleci): cleanup and remove temp branch config

* chore(package defs): moving from npm run to yarn

* explicitly specifying webpack4 as a frontend dep

* chore(package defs): replace npm with yarn everywhere

* docs(root readme): update with some yarn specific docs

* test(server comments gql): add wip server comments gql tests

* test(server comments graphql): add missing test operations and generate a bunch of testcases

* test(server comments graphql api): fix all authz test cases for comments

* test(server comments service): fix comments service failing test

* fix(tests): do not look inside

Co-authored-by: Fabians <fabis94@live.com>
Co-authored-by: Dimitrie Stefanescu <didimitrie@gmail.com>
2022-06-02 11:15:27 +02:00

854 lines
24 KiB
JavaScript

const expect = require('chai').expect
const crs = require('crypto-random-string')
const { buildApolloServer } = require('@/app')
const { addLoadersToCtx } = require('@/modules/shared')
const { beforeEachContext } = require('@/test/hooks')
const { Roles, AllScopes } = require('@/modules/core/helpers/mainConstants')
const {
grantPermissionsStream,
updateStream
} = require('@/modules/core/services/streams')
const { createUser } = require('@/modules/core/services/users')
const { gql } = require('apollo-server-express')
const { createStream } = require('@/modules/core/services/streams')
const { createObject } = require('@/modules/core/services/objects')
const { createComment } = require('@/modules/comments/services')
const { createCommitByBranchName } = require('@/modules/core/services/commits')
describe('Subscriptions @comments', () => {
// the idea here, is to use a pubsub.asyncIterator and count the expected events
it('Should publish events to pubsub, test it by registering a subscriber')
})
const testForbiddenResponse = (result) => {
expect(result.errors, 'This should have failed').to.exist
expect(result.errors.length).to.be.above(0)
expect(result.errors[0].extensions.code).to.equal('FORBIDDEN')
}
const testResult = (shouldSucceed, result, successTests) => {
if (shouldSucceed) {
expect(result.errors, 'This should not have failed').to.not.exist
successTests(result)
} else {
testForbiddenResponse(result)
}
}
const writeComment = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($input: CommentCreateInput!) {
commentCreate(input: $input)
}
`,
variables: {
input: {
streamId: resources.streamId,
text: 'foo',
data: {},
resources: [{ resourceId: resources.streamId, resourceType: 'stream' }]
}
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentCreate).to.be.string
expect(res.data.commentCreate.length).to.equal(10)
})
}
const broadcastViewerActivity = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($streamId: String!, $resourceId: String!, $data: JSONObject) {
userViewerActivityBroadcast(
streamId: $streamId
resourceId: $resourceId
data: $data
)
}
`,
variables: {
streamId: resources.streamId,
data: {},
resourceId: resources.objectId
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.userViewerActivityBroadcast).to.be.true
})
}
const broadcastCommentActivity = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($streamId: String!, $commentId: String!, $data: JSONObject) {
userCommentThreadActivityBroadcast(
streamId: $streamId
commentId: $commentId
data: $data
)
}
`,
variables: {
streamId: resources.streamId,
data: {},
commentId: resources.commentId
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.userCommentThreadActivityBroadcast).to.be.true
})
}
const viewAComment = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($streamId: String!, $commentId: String!) {
commentView(streamId: $streamId, commentId: $commentId)
}
`,
variables: {
streamId: resources.streamId,
commentId: resources.commentId
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentView).to.be.true
})
}
const archiveMyComment = async ({ apollo, resources, shouldSucceed }) => {
const context = await apollo.context()
const commentId = await createComment({
userId: context.userId,
input: {
streamId: resources.streamId,
text: 'i wrote this myself',
data: {},
resources: [
{ resourceId: resources.streamId, resourceType: 'stream' },
{ resourceId: resources.objectId, resourceType: 'object' }
]
}
})
const res = await apollo.executeOperation({
query: gql`
mutation ($streamId: String!, $commentId: String!) {
commentArchive(streamId: $streamId, commentId: $commentId)
}
`,
variables: { streamId: resources.streamId, commentId }
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentArchive).to.be.true
})
}
const archiveOthersComment = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($streamId: String!, $commentId: String!) {
commentArchive(streamId: $streamId, commentId: $commentId)
}
`,
variables: {
streamId: resources.streamId,
commentId: resources.commentId
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentArchive).to.be.true
})
}
const editMyComment = async ({ apollo, resources, shouldSucceed }) => {
const context = await apollo.context()
const commentId = await createComment({
userId: context.userId,
input: {
streamId: resources.streamId,
text: 'i wrote this myself',
data: {},
resources: [
{ resourceId: resources.streamId, resourceType: 'stream' },
{ resourceId: resources.objectId, resourceType: 'object' }
]
}
})
const res = await apollo.executeOperation({
query: gql`
mutation ($input: CommentEditInput!) {
commentEdit(input: $input)
}
`,
variables: {
input: {
streamId: resources.streamId,
id: commentId,
text: 'im going to overwrite myself'
}
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentEdit).to.be.true
})
}
const editOthersComment = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($input: CommentEditInput!) {
commentEdit(input: $input)
}
`,
variables: {
input: {
streamId: resources.streamId,
id: resources.commentId,
text: 'what you wrote is dumb, here, let me fix it for you'
}
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentEdit).to.be.true
})
}
const replyToAComment = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
mutation ($input: ReplyCreateInput!) {
commentReply(input: $input)
}
`,
variables: {
input: {
streamId: resources.streamId,
parentComment: resources.commentId,
text: 'what you wrote is dump, here, let me fix it for you',
data: {}
}
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.commentReply).to.be.string
expect(res.data.commentReply.length).to.equal(10)
})
}
const queryComment = async ({ apollo, resources, shouldSucceed }) => {
const res = await apollo.executeOperation({
query: gql`
query ($id: String!, $streamId: String!) {
comment(id: $id, streamId: $streamId) {
id
replies {
totalCount
items {
id
text
}
}
}
}
`,
variables: {
id: resources.commentId,
streamId: resources.streamId
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.comment.id).to.exist
expect(res.data.comment.id).to.equal(resources.commentId)
})
}
const queryComments = async ({ apollo, resources, shouldSucceed }) => {
const object = {
foo: 123,
bar: crs({ length: 5 })
}
const objectId = await createObject(resources.streamId, object)
const numberOfComments = 3
const commentIds = await Promise.all(
[...Array(numberOfComments).keys()].map((key) =>
createComment({
userId: resources.testActorId,
input: {
streamId: resources.streamId,
text: `${key}`,
data: {},
resources: [{ resourceId: objectId, resourceType: 'object' }]
}
})
)
)
const res = await apollo.executeOperation({
query: gql`
query ($streamId: String!, $resources: [ResourceIdentifierInput]) {
comments(streamId: $streamId, resources: $resources) {
totalCount
items {
id
text
}
}
}
`,
variables: {
streamId: resources.streamId,
resources: [
// i expected this to work as intersection, but it works as union
{ resourceId: objectId, resourceType: 'object' }
]
}
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.comments.totalCount).to.be.equal(numberOfComments)
expect(res.data.comments.items.map((i) => i.id)).to.be.equalInAnyOrder(commentIds)
})
}
const queryStreamCommentCount = async ({ apollo, resources, shouldSucceed }) => {
await createComment({
userId: resources.testActorId,
input: {
streamId: resources.streamId,
text: 'im expecting some replies here',
data: {},
resources: [{ resourceId: resources.streamId, resourceType: 'stream' }]
}
})
const res = await apollo.executeOperation({
query: gql`
query ($id: String!) {
stream(id: $id) {
id
commentCount
}
}
`,
variables: { id: resources.streamId }
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.stream.commentCount).to.be.greaterThanOrEqual(1)
})
}
const queryObjectCommentCount = async ({ apollo, resources, shouldSucceed }) => {
const objectId = await createObject(resources.streamId, {
foo: 'bar',
noise: crs({ length: 5 })
})
await createComment({
userId: resources.testActorId,
input: {
streamId: resources.streamId,
text: 'im expecting some replies here',
data: {},
resources: [{ resourceId: objectId, resourceType: 'object' }]
}
})
const res = await apollo.executeOperation({
query: gql`
query ($id: String!, $objectId: String!) {
stream(id: $id) {
object(id: $objectId) {
commentCount
}
}
}
`,
variables: { id: resources.streamId, objectId }
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.stream.object.commentCount).to.equal(1)
})
}
const queryCommitCommentCount = async ({ apollo, resources, shouldSucceed }) => {
const objectId = await createObject(resources.streamId, {
foo: 'bar',
notSignal: crs({ length: 10 })
})
const commitId = await createCommitByBranchName({
streamId: resources.streamId,
branchName: 'main',
objectId,
authorId: resources.testActorId,
message: 'bumm'
})
await createComment({
userId: resources.testActorId,
input: {
streamId: resources.streamId,
text: 'im expecting some replies here',
data: {},
resources: [{ resourceId: commitId, resourceType: 'commit' }]
}
})
const res = await apollo.executeOperation({
query: gql`
query ($id: String!, $commitId: String!) {
stream(id: $id) {
commit(id: $commitId) {
commentCount
}
}
}
`,
variables: { id: resources.streamId, commitId }
})
testResult(shouldSucceed, res, (res) => {
expect(res.data.stream.commit.commentCount).to.equal(1)
})
}
const queryCommitCollectionCommentCount = async ({
apollo,
resources,
shouldSucceed
}) => {
const objectId = await createObject(resources.streamId, {
foo: 'bar',
almostMakesSense: crs({ length: 10 })
})
const commitId = await createCommitByBranchName({
streamId: resources.streamId,
branchName: 'main',
objectId,
authorId: resources.testActorId,
message: 'bumm'
})
await createComment({
userId: resources.testActorId,
input: {
streamId: resources.streamId,
text: 'im expecting some replies here',
data: {},
resources: [{ resourceId: commitId, resourceType: 'commit' }]
}
})
const res = await apollo.executeOperation({
query: gql`
query ($id: String!) {
user(id: $id) {
commits {
items {
commentCount
}
}
}
}
`,
variables: { id: resources.testActorId }
})
testResult(shouldSucceed, res, (res) => {
res.data.user.commits.items
.map((i) => i.commentCount)
.map((commentCount) => {
expect(commentCount).to.be.greaterThanOrEqual(1)
})
})
}
// eslint-disable-next-line no-unused-vars
const actions = ['queryCommitCommentCount', 'queryCommitCollectionCommentCount']
describe('Graphql @comments', () => {
// this user will be admin by default
// it will be used to create all resources, that the other actors can
// be tested against
const myTestActor = {
name: 'Gergo Jedlicska',
email: 'gergo@jedlicska.com',
password: 'sn3aky-1337-b1m'
}
const chadTheEngineer = {
name: 'Chad the Engineer',
email: 'chad@engineering.acme',
password: 'tryingNotToBeACadMonkey',
role: Roles.Server.User
}
const archived = {
name: 'The Balrog of Morgoth',
email: 'durinsbane@moria.bridge',
role: Roles.Server.ArchivedUser
}
const ownedStream = {
name: 'stream owner',
isPublic: false,
role: Roles.Stream.Owner
}
const contributorStream = {
name: 'contributions are welcome',
isPublic: false,
role: Roles.Stream.Contributor
}
const reviewerStream = {
name: 'no work, just talk',
isPublic: false,
role: Roles.Stream.Reviewer
}
const noAccessStream = {
name: 'aint nobody canna cross it',
isPublic: false,
role: null
}
const publicStream = {
name: 'come take a look',
isPublic: true,
role: null
}
const publicStreamWithPublicComments = {
name: 'the gossip protocol',
isPublic: true,
role: null
}
const testData = [
{
user: chadTheEngineer,
streamData: [
{
stream: ownedStream,
cases: [
[writeComment, true],
[broadcastViewerActivity, true],
[broadcastCommentActivity, true],
[viewAComment, true],
[archiveMyComment, true],
[archiveOthersComment, true],
[editMyComment, true],
[editOthersComment, true],
[replyToAComment, true],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, true]
]
},
{
stream: contributorStream,
cases: [
[writeComment, true],
[broadcastViewerActivity, true],
[broadcastCommentActivity, true],
[viewAComment, true],
[archiveMyComment, true],
[archiveOthersComment, false],
[editMyComment, true],
[editOthersComment, true],
[replyToAComment, true],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, true]
]
},
{
stream: reviewerStream,
cases: [
[writeComment, true],
[broadcastViewerActivity, true],
[broadcastCommentActivity, true],
[viewAComment, true],
[archiveMyComment, true],
[archiveOthersComment, false],
[editMyComment, true],
[editOthersComment, true],
[replyToAComment, true],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, true]
]
},
{
stream: noAccessStream,
cases: [
[writeComment, false],
[broadcastViewerActivity, false],
[broadcastCommentActivity, false],
[viewAComment, false],
[archiveOthersComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, false],
[queryComments, false],
[queryStreamCommentCount, false],
[queryObjectCommentCount, false],
[queryCommitCommentCount, false],
[queryCommitCollectionCommentCount, true]
]
},
{
stream: publicStream,
cases: [
[writeComment, false],
[broadcastViewerActivity, true],
[broadcastCommentActivity, false],
[viewAComment, true],
[archiveMyComment, false],
[archiveOthersComment, false],
[editMyComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, true]
]
},
{
stream: publicStreamWithPublicComments,
cases: [
[writeComment, true],
[broadcastViewerActivity, true],
[broadcastCommentActivity, true],
[viewAComment, true],
[archiveMyComment, true],
[archiveOthersComment, false],
[editMyComment, true],
[editOthersComment, false],
[replyToAComment, true],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, true]
]
}
]
},
{
user: archived,
streamData: [
{
stream: ownedStream,
cases: [
[writeComment, false],
[broadcastViewerActivity, false],
[broadcastCommentActivity, false],
[viewAComment, false],
[archiveOthersComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, false],
[queryComments, false],
[queryStreamCommentCount, false],
[queryObjectCommentCount, false],
[queryCommitCommentCount, false],
[queryCommitCollectionCommentCount, false]
]
},
{
stream: publicStreamWithPublicComments,
cases: [
[writeComment, false],
[broadcastViewerActivity, false],
[broadcastCommentActivity, false],
[viewAComment, false],
[archiveOthersComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, false],
[queryComments, false],
[queryStreamCommentCount, false],
[queryObjectCommentCount, false],
[queryCommitCommentCount, false],
[queryCommitCollectionCommentCount, false]
]
}
]
},
{
user: null,
streamData: [
{
stream: ownedStream,
cases: [
[writeComment, false],
[broadcastViewerActivity, false],
[broadcastCommentActivity, false],
[viewAComment, false],
[archiveOthersComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, false],
[queryComments, false],
[queryStreamCommentCount, false],
[queryObjectCommentCount, false],
[queryCommitCommentCount, false],
[queryCommitCollectionCommentCount, false]
]
},
{
stream: publicStreamWithPublicComments,
cases: [
[writeComment, false],
[broadcastViewerActivity, false],
[broadcastCommentActivity, false],
[viewAComment, false],
[archiveOthersComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, false]
]
},
{
stream: publicStream,
cases: [
[writeComment, false],
[broadcastViewerActivity, false],
[broadcastCommentActivity, false],
[viewAComment, false],
[archiveOthersComment, false],
[editOthersComment, false],
[replyToAComment, false],
[queryComment, true],
[queryComments, true],
[queryStreamCommentCount, true],
[queryObjectCommentCount, true],
[queryCommitCommentCount, true],
[queryCommitCollectionCommentCount, false]
]
}
]
}
]
before(async () => {
await beforeEachContext()
myTestActor.id = await createUser(myTestActor)
await Promise.all(
[chadTheEngineer, archived].map((user) =>
createUser({ name: user.name, email: user.email, password: user.password })
.then((id) => (user.id = id))
.catch((err) => {
throw err
})
)
)
ownedStream.id = await createStream({ ...ownedStream, ownerId: myTestActor.id })
contributorStream.id = await createStream({
...contributorStream,
ownerId: myTestActor.id
})
reviewerStream.id = await createStream({
...reviewerStream,
ownerId: myTestActor.id
})
noAccessStream.id = await createStream({
...noAccessStream,
ownerId: myTestActor.id
})
publicStream.id = await createStream({
...publicStream,
ownerId: myTestActor.id
})
publicStreamWithPublicComments.id = await createStream({
...publicStreamWithPublicComments,
ownerId: myTestActor.id
})
await updateStream({
...publicStreamWithPublicComments,
streamId: publicStreamWithPublicComments.id,
allowPublicComments: true
})
})
testData.forEach((userContext) => {
const user = userContext.user
const apollo = buildApolloServer({
context: () =>
addLoadersToCtx({
auth: true,
userId: user?.id,
role: user?.role,
token: 'asd',
scopes: AllScopes
})
})
describe(`I, ${user?.name ?? 'Anonymous'} as a ${
user?.role ?? 'shadow:lurker'
}`, () => {
userContext.streamData.forEach((streamContext) => {
const stream = streamContext.stream
let resources
before(async () => {
if (user && stream.role)
await grantPermissionsStream({
streamId: stream.id,
userId: user.id,
role: stream.role
})
const objectId = await createObject(stream.id, { test: 'object' })
const commentId = await createComment({
userId: myTestActor.id,
input: {
streamId: stream.id,
text: 'foo',
data: {},
resources: [{ resourceId: stream.id, resourceType: 'stream' }]
}
})
resources = {
objectId,
commentId,
streamId: stream.id,
testActorId: myTestActor.id
}
})
describe(`testing ${streamContext.cases.length} cases of acting on ${
stream.name
} stream where I'm a ${
user && stream.role ? stream.role : 'trouble:maker'
}`, () => {
streamContext.cases.forEach(([testCase, shouldSucceed]) => {
it(`${shouldSucceed ? 'can' : 'am not allowed to'} ${
testCase.name
}`, async () => {
await testCase({ apollo, streamId: stream.id, resources, shouldSucceed })
})
})
})
})
})
})
})