55f91d2cdf
* fix(workspace): auto approval * fix(scopes): access scopes across the server * fix(hasAccessRole): establish for all mutations * feat(token): scoping does not require the token to exist * chore(scopes): added additional roles * fix: replaced UNAUTHORIZED_ACCESS_ERROR with UNAUTHORIZED * fix(email): user list scopes
331 lines
9.8 KiB
TypeScript
331 lines
9.8 KiB
TypeScript
import { buildApolloServer } from '@/app'
|
|
import { db } from '@/db/knex'
|
|
import { Commits, Streams, Users } from '@/modules/core/dbSchema'
|
|
import { Roles } from '@/modules/core/helpers/mainConstants'
|
|
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
|
import { getCommitsFactory } from '@/modules/core/repositories/commits'
|
|
import {
|
|
getStreamRolesFactory,
|
|
grantStreamPermissionsFactory
|
|
} from '@/modules/core/repositories/streams'
|
|
import { getUserFactory } from '@/modules/core/repositories/users'
|
|
import {
|
|
addOrUpdateStreamCollaboratorFactory,
|
|
validateStreamAccessFactory
|
|
} from '@/modules/core/services/streams/access'
|
|
import { authorizeResolver } from '@/modules/shared'
|
|
import { getEventBus } from '@/modules/shared/services/eventBus'
|
|
import type { BasicTestUser } from '@/test/authHelper'
|
|
import { createTestUsers } from '@/test/authHelper'
|
|
import { deleteCommits, moveCommits } from '@/test/graphql/commits'
|
|
import type { ServerAndContext } from '@/test/graphqlHelper'
|
|
import { createAuthedTestContext, createTestContext } from '@/test/graphqlHelper'
|
|
import { truncateTables } from '@/test/hooks'
|
|
import type { BasicTestCommit } from '@/test/speckle-helpers/commitHelper'
|
|
import { createTestCommits } from '@/test/speckle-helpers/commitHelper'
|
|
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
|
import { createTestStreams } from '@/test/speckle-helpers/streamHelper'
|
|
import { expect } from 'chai'
|
|
import { times } from 'lodash-es'
|
|
|
|
enum BatchActionType {
|
|
Move,
|
|
Delete
|
|
}
|
|
|
|
const getUser = getUserFactory({ db })
|
|
const createBranch = createBranchFactory({ db })
|
|
const getCommits = getCommitsFactory({ db })
|
|
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
|
|
const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({
|
|
validateStreamAccess,
|
|
getUser,
|
|
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
|
|
getStreamRoles: getStreamRolesFactory({ db }),
|
|
emitEvent: getEventBus().emit
|
|
})
|
|
|
|
const cleanup = async () => {
|
|
await truncateTables([Streams.name, Users.name, Commits.name])
|
|
}
|
|
|
|
describe('Batch commits', () => {
|
|
const userCommmitCount = 10
|
|
|
|
const secondBranchName = 'second'
|
|
|
|
const me: BasicTestUser = {
|
|
name: 'batch commit dude',
|
|
email: 'batchcommitguy@example.org',
|
|
id: ''
|
|
}
|
|
|
|
const otherGuy: BasicTestUser = {
|
|
name: 'other batch commit guy',
|
|
email: 'otherbatchcommitguy@example.org',
|
|
id: ''
|
|
}
|
|
|
|
const myStream: BasicTestStream = {
|
|
name: 'my first test stream',
|
|
isPublic: false,
|
|
ownerId: '',
|
|
id: ''
|
|
}
|
|
|
|
const otherStream: BasicTestStream = {
|
|
name: 'other guys first test stream',
|
|
isPublic: false,
|
|
ownerId: '',
|
|
id: ''
|
|
}
|
|
|
|
let myCommits: BasicTestCommit[]
|
|
|
|
let otherCommits: BasicTestCommit[]
|
|
|
|
let streamId: string
|
|
|
|
before(async () => {
|
|
await cleanup()
|
|
await createTestUsers([me, otherGuy])
|
|
await createTestStreams([
|
|
[myStream, me],
|
|
[otherStream, otherGuy]
|
|
])
|
|
|
|
await Promise.all([
|
|
// create another branch for each stream
|
|
createBranch({
|
|
name: secondBranchName,
|
|
description: '',
|
|
streamId: myStream.id,
|
|
authorId: me.id
|
|
}),
|
|
createBranch({
|
|
name: secondBranchName,
|
|
description: '',
|
|
streamId: otherStream.id,
|
|
authorId: otherGuy.id
|
|
}),
|
|
// add users as contributors to each others streams
|
|
addOrUpdateStreamCollaborator(
|
|
otherStream.id,
|
|
me.id,
|
|
Roles.Stream.Contributor,
|
|
otherGuy.id
|
|
)
|
|
])
|
|
|
|
myCommits = times(userCommmitCount, (i): BasicTestCommit => {
|
|
streamId = i % 2 === 0 ? myStream.id : otherStream.id
|
|
return {
|
|
id: '',
|
|
objectId: '',
|
|
branchId: '',
|
|
streamId,
|
|
authorId: me.id
|
|
}
|
|
})
|
|
otherCommits = times(
|
|
userCommmitCount,
|
|
(): BasicTestCommit => ({
|
|
id: '',
|
|
objectId: '',
|
|
branchId: '',
|
|
streamId: otherStream.id,
|
|
authorId: otherGuy.id
|
|
})
|
|
)
|
|
|
|
await createTestCommits([...myCommits, ...otherCommits])
|
|
})
|
|
|
|
const batchActionDataSet = [
|
|
{ display: 'move', type: BatchActionType.Move },
|
|
{ display: 'delete', type: BatchActionType.Delete }
|
|
]
|
|
|
|
const buildBatchActionInvoker =
|
|
(apollo: ServerAndContext) => (type: BatchActionType, commitIds: string[]) => {
|
|
if (type === BatchActionType.Delete) {
|
|
return deleteCommits(apollo, { input: { commitIds, streamId } })
|
|
} else if (type === BatchActionType.Move) {
|
|
return moveCommits(apollo, {
|
|
input: { commitIds, targetBranch: secondBranchName, streamId }
|
|
})
|
|
} else {
|
|
throw new Error('Unexpected batch action type')
|
|
}
|
|
}
|
|
|
|
type BatchActionInvoker = ReturnType<typeof buildBatchActionInvoker>
|
|
|
|
describe('when authenticated', () => {
|
|
let apollo: ServerAndContext
|
|
let invokeBatchAction: BatchActionInvoker
|
|
|
|
before(async () => {
|
|
apollo = {
|
|
apollo: await buildApolloServer(),
|
|
context: await createAuthedTestContext(me.id)
|
|
}
|
|
invokeBatchAction = buildBatchActionInvoker(apollo)
|
|
})
|
|
|
|
batchActionDataSet.forEach(({ display, type }) => {
|
|
it(`can't batch ${display} commits if not commit or stream author`, async () => {
|
|
const result = await invokeBatchAction(
|
|
type,
|
|
otherCommits.map((c) => c.id)
|
|
)
|
|
|
|
expect(result).to.haveGraphQLErrors({
|
|
code: 'FORBIDDEN'
|
|
})
|
|
})
|
|
|
|
it(`can't batch ${display} an empty commit array`, async () => {
|
|
const result = await invokeBatchAction(type, [])
|
|
|
|
expect(result).to.haveGraphQLErrors('No commits specified')
|
|
})
|
|
|
|
it(`can't batch ${display} commits if at least one is nonexistant`, async () => {
|
|
const result = await invokeBatchAction(type, [
|
|
...myCommits.map((c) => c.id),
|
|
'aaaaaaaa'
|
|
])
|
|
|
|
expect(result).to.haveGraphQLErrors({
|
|
code: 'NOT_FOUND_ERROR'
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('and deleting commits', async () => {
|
|
const deletableCommitCount = 5
|
|
|
|
let myDeletableCommits: BasicTestCommit[]
|
|
|
|
beforeEach(async () => {
|
|
myDeletableCommits = times(deletableCommitCount, (i): BasicTestCommit => {
|
|
streamId = i % 2 === 0 ? myStream.id : otherStream.id
|
|
return {
|
|
id: '',
|
|
branchId: '',
|
|
streamId,
|
|
objectId: '',
|
|
authorId: me.id
|
|
}
|
|
})
|
|
|
|
await createTestCommits(myDeletableCommits)
|
|
})
|
|
|
|
const invokeDelete = (commitIds: string[]) =>
|
|
deleteCommits(apollo, { input: { commitIds, streamId } })
|
|
|
|
const validateDeleted = async (commitIds: string[]) => {
|
|
const commits = await getCommits(commitIds)
|
|
expect(commits).to.be.empty
|
|
}
|
|
|
|
it('can do it for commits of multiple streams', async () => {
|
|
const commitIds = myDeletableCommits.map((c) => c.id)
|
|
const result = await invokeDelete(commitIds)
|
|
|
|
expect(result).to.not.haveGraphQLErrors()
|
|
await validateDeleted(commitIds)
|
|
})
|
|
})
|
|
|
|
describe('and moving commits', async () => {
|
|
const movableCommitCount = 5
|
|
|
|
let myMovableCommits: BasicTestCommit[]
|
|
let streamId: string
|
|
|
|
before(async () => {
|
|
myMovableCommits = times(movableCommitCount, (i): BasicTestCommit => {
|
|
streamId = i % 2 === 0 ? myStream.id : otherStream.id
|
|
return {
|
|
id: '',
|
|
branchId: '',
|
|
objectId: '',
|
|
streamId,
|
|
authorId: me.id
|
|
}
|
|
})
|
|
|
|
await createTestCommits(myMovableCommits)
|
|
})
|
|
|
|
const invokeMove = (commitIds: string[], targetBranch = secondBranchName) =>
|
|
moveCommits(apollo, { input: { commitIds, targetBranch, streamId } })
|
|
const validateMoved = async (
|
|
commitIds: string[],
|
|
targetBranch = secondBranchName
|
|
) => {
|
|
const commits = await getCommits(commitIds)
|
|
const areAllMoved =
|
|
commits.length === commitIds.length &&
|
|
commits.every((c) => c.branchName === targetBranch)
|
|
expect(areAllMoved).to.be.true
|
|
}
|
|
|
|
it("can't do it for commits belonging to multiple streams", async () => {
|
|
const commitIds = myMovableCommits.map((c) => c.id)
|
|
const result = await invokeMove(commitIds)
|
|
|
|
expect(result).to.haveGraphQLErrors('commits belong to different streams')
|
|
})
|
|
|
|
it('moves to new branch when specifying a nonexistant target branch', async () => {
|
|
const newBranchName = 'some-nonexistant-stream'
|
|
const commitIds = myMovableCommits
|
|
.filter((c) => c.streamId === myStream.id)
|
|
.map((c) => c.id)
|
|
const result = await invokeMove(commitIds, newBranchName)
|
|
|
|
expect(result).to.not.haveGraphQLErrors()
|
|
await validateMoved(commitIds, newBranchName)
|
|
})
|
|
|
|
it('can do it with commits belonging to the same stream', async () => {
|
|
const commitIds = myMovableCommits
|
|
.filter((c) => c.streamId === myStream.id)
|
|
.map((c) => c.id)
|
|
const result = await invokeMove(commitIds)
|
|
|
|
expect(result).to.not.haveGraphQLErrors()
|
|
await validateMoved(commitIds)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when not authenticated', () => {
|
|
let apollo: ServerAndContext
|
|
let invokeBatchAction: BatchActionInvoker
|
|
|
|
before(async () => {
|
|
apollo = {
|
|
apollo: await buildApolloServer(),
|
|
context: await createTestContext()
|
|
}
|
|
invokeBatchAction = buildBatchActionInvoker(apollo)
|
|
})
|
|
|
|
batchActionDataSet.forEach(({ display, type }) => {
|
|
it(`can't batch ${display} commits`, async () => {
|
|
const result = await invokeBatchAction(
|
|
type,
|
|
myCommits.map((c) => c.id)
|
|
)
|
|
|
|
expect(result).to.haveGraphQLErrors('Must provide an auth token')
|
|
})
|
|
})
|
|
})
|
|
})
|