ba3d9ade22
* feat: moved project roles to activity * fix: added previous project role filed on activity * fix: remvoke does not get logged as an actvity for every project in a workspace when removed from workspace * fix: on delete user account, emit also seat and project event Co-authored-by: Charles Driesler <chuck@speckle.systems>
515 lines
16 KiB
TypeScript
515 lines
16 KiB
TypeScript
/* istanbul ignore file */
|
|
import chai from 'chai'
|
|
import assert from 'assert'
|
|
|
|
import { beforeEachContext } from '@/test/hooks'
|
|
import { sleep } from '@/test/helpers'
|
|
|
|
const expect = chai.expect
|
|
|
|
import { knex } from '@/db/knex'
|
|
|
|
import {
|
|
updateBranchAndNotifyFactory,
|
|
deleteBranchAndNotifyFactory
|
|
} from '@/modules/core/services/branch/management'
|
|
import {
|
|
getBranchByIdFactory,
|
|
getStreamBranchByNameFactory,
|
|
createBranchFactory,
|
|
updateBranchFactory,
|
|
deleteBranchByIdFactory,
|
|
markCommitBranchUpdatedFactory,
|
|
getPaginatedStreamBranchesPageFactory,
|
|
getStreamBranchCountFactory
|
|
} from '@/modules/core/repositories/branches'
|
|
import {
|
|
getStreamFactory,
|
|
createStreamFactory,
|
|
markBranchStreamUpdatedFactory,
|
|
markCommitStreamUpdatedFactory,
|
|
grantStreamPermissionsFactory,
|
|
getStreamRolesFactory
|
|
} from '@/modules/core/repositories/streams'
|
|
import {
|
|
createCommitByBranchIdFactory,
|
|
createCommitByBranchNameFactory
|
|
} from '@/modules/core/services/commit/management'
|
|
import {
|
|
createCommitFactory,
|
|
insertStreamCommitsFactory,
|
|
insertBranchCommitsFactory
|
|
} from '@/modules/core/repositories/commits'
|
|
import {
|
|
getObjectFactory,
|
|
storeSingleObjectIfNotFoundFactory
|
|
} from '@/modules/core/repositories/objects'
|
|
import {
|
|
legacyCreateStreamFactory,
|
|
createStreamReturnRecordFactory
|
|
} from '@/modules/core/services/streams/management'
|
|
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
|
|
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
|
|
import {
|
|
findUserByTargetFactory,
|
|
insertInviteAndDeleteOldFactory,
|
|
deleteServerOnlyInvitesFactory,
|
|
updateAllInviteTargetsFactory,
|
|
findInviteFactory,
|
|
deleteInvitesByTargetFactory
|
|
} from '@/modules/serverinvites/repositories/serverInvites'
|
|
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
|
|
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
|
import { getEventBus } from '@/modules/shared/services/eventBus'
|
|
import {
|
|
getUsersFactory,
|
|
getUserFactory,
|
|
storeUserFactory,
|
|
countAdminUsersFactory,
|
|
storeUserAclFactory
|
|
} from '@/modules/core/repositories/users'
|
|
import {
|
|
findEmailFactory,
|
|
createUserEmailFactory,
|
|
ensureNoPrimaryEmailForUserFactory
|
|
} from '@/modules/core/repositories/userEmails'
|
|
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
|
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
|
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
|
import { sendEmail } from '@/modules/emails/services/sending'
|
|
import { createUserFactory } from '@/modules/core/services/users/management'
|
|
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
|
import {
|
|
finalizeInvitedServerRegistrationFactory,
|
|
finalizeResourceInviteFactory
|
|
} from '@/modules/serverinvites/services/processing'
|
|
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
|
import { getPaginatedStreamBranchesFactory } from '@/modules/core/services/branch/retrieval'
|
|
import { createObjectFactory } from '@/modules/core/services/objects/management'
|
|
import { ensureError } from '@speckle/shared'
|
|
import { ModelEvents } from '@/modules/core/domain/branches/events'
|
|
import {
|
|
processFinalizedProjectInviteFactory,
|
|
validateProjectInviteBeforeFinalizationFactory
|
|
} from '@/modules/serverinvites/services/coreFinalization'
|
|
import {
|
|
addOrUpdateStreamCollaboratorFactory,
|
|
validateStreamAccessFactory
|
|
} from '@/modules/core/services/streams/access'
|
|
import { authorizeResolver } from '@/modules/shared'
|
|
|
|
const db = knex
|
|
const Commits = () => knex('commits')
|
|
|
|
const getUser = getUserFactory({ db })
|
|
const getUsers = getUsersFactory({ db })
|
|
const markCommitStreamUpdated = markCommitStreamUpdatedFactory({ db })
|
|
const markBranchStreamUpdated = markBranchStreamUpdatedFactory({ db })
|
|
const getStream = getStreamFactory({ db: knex })
|
|
const getBranchById = getBranchByIdFactory({ db: knex })
|
|
const getStreamBranchByName = getStreamBranchByNameFactory({ db: knex })
|
|
const createBranch = createBranchFactory({ db: knex })
|
|
const updateBranchAndNotify = updateBranchAndNotifyFactory({
|
|
getBranchById: getBranchByIdFactory({ db: knex }),
|
|
updateBranch: updateBranchFactory({ db: knex }),
|
|
eventEmit: getEventBus().emit
|
|
})
|
|
const deleteBranchAndNotify = deleteBranchAndNotifyFactory({
|
|
getStream,
|
|
getBranchById: getBranchByIdFactory({ db: knex }),
|
|
emitEvent: getEventBus().emit,
|
|
markBranchStreamUpdated,
|
|
deleteBranchById: deleteBranchByIdFactory({ db: knex })
|
|
})
|
|
|
|
const getServerInfo = getServerInfoFactory({ db })
|
|
const getObject = getObjectFactory({ db: knex })
|
|
const createCommitByBranchId = createCommitByBranchIdFactory({
|
|
createCommit: createCommitFactory({ db }),
|
|
getObject,
|
|
getBranchById: getBranchByIdFactory({ db }),
|
|
insertStreamCommits: insertStreamCommitsFactory({ db }),
|
|
insertBranchCommits: insertBranchCommitsFactory({ db }),
|
|
markCommitStreamUpdated,
|
|
markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }),
|
|
emitEvent: getEventBus().emit
|
|
})
|
|
|
|
const createCommitByBranchName = createCommitByBranchNameFactory({
|
|
createCommitByBranchId,
|
|
getStreamBranchByName: getStreamBranchByNameFactory({ db }),
|
|
getBranchById: getBranchByIdFactory({ db })
|
|
})
|
|
|
|
const buildFinalizeProjectInvite = () =>
|
|
finalizeResourceInviteFactory({
|
|
findInvite: findInviteFactory({ db }),
|
|
validateInvite: validateProjectInviteBeforeFinalizationFactory({
|
|
getProject: getStream
|
|
}),
|
|
processInvite: processFinalizedProjectInviteFactory({
|
|
getProject: getStream,
|
|
addProjectRole: addOrUpdateStreamCollaboratorFactory({
|
|
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
|
|
getUser,
|
|
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
|
|
getStreamRoles: getStreamRolesFactory({ db }),
|
|
emitEvent: getEventBus().emit
|
|
})
|
|
}),
|
|
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
|
|
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
|
|
emitEvent: (...args) => getEventBus().emit(...args),
|
|
findEmail: findEmailFactory({ db }),
|
|
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
|
createUserEmail: createUserEmailFactory({ db }),
|
|
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
|
findEmail: findEmailFactory({ db }),
|
|
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
|
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
|
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
|
}),
|
|
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
|
findEmail: findEmailFactory({ db }),
|
|
getUser,
|
|
getServerInfo,
|
|
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
|
db
|
|
}),
|
|
renderEmail,
|
|
sendEmail
|
|
})
|
|
}),
|
|
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
|
|
getStream
|
|
}),
|
|
getUser,
|
|
getServerInfo
|
|
})
|
|
|
|
const createStream = legacyCreateStreamFactory({
|
|
createStreamReturnRecord: createStreamReturnRecordFactory({
|
|
inviteUsersToProject: inviteUsersToProjectFactory({
|
|
createAndSendInvite: createAndSendInviteFactory({
|
|
findUserByTarget: findUserByTargetFactory({ db }),
|
|
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
|
|
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
|
|
getStream
|
|
}),
|
|
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
|
|
getStream
|
|
}),
|
|
emitEvent: ({ eventName, payload }) =>
|
|
getEventBus().emit({
|
|
eventName,
|
|
payload
|
|
}),
|
|
getUser,
|
|
getServerInfo,
|
|
finalizeInvite: buildFinalizeProjectInvite()
|
|
}),
|
|
getUsers
|
|
}),
|
|
createStream: createStreamFactory({ db }),
|
|
createBranch: createBranchFactory({ db }),
|
|
emitEvent: getEventBus().emit
|
|
})
|
|
})
|
|
|
|
const findEmail = findEmailFactory({ db })
|
|
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
|
findEmail,
|
|
getUser: getUserFactory({ db }),
|
|
getServerInfo,
|
|
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
|
renderEmail,
|
|
sendEmail
|
|
})
|
|
const createUser = createUserFactory({
|
|
getServerInfo,
|
|
findEmail,
|
|
storeUser: storeUserFactory({ db }),
|
|
countAdminUsers: countAdminUsersFactory({ db }),
|
|
storeUserAcl: storeUserAclFactory({ db }),
|
|
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
|
createUserEmail: createUserEmailFactory({ db }),
|
|
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
|
findEmail,
|
|
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
|
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
|
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
|
}),
|
|
requestNewEmailVerification
|
|
}),
|
|
emitEvent: getEventBus().emit
|
|
})
|
|
const getBranchesByStreamId = getPaginatedStreamBranchesFactory({
|
|
getPaginatedStreamBranchesPage: getPaginatedStreamBranchesPageFactory({ db }),
|
|
getStreamBranchCount: getStreamBranchCountFactory({ db })
|
|
})
|
|
const createObject = createObjectFactory({
|
|
storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ db })
|
|
})
|
|
|
|
describe('Branches @core-branches', () => {
|
|
const user = {
|
|
name: 'Dimitrie Stefanescu',
|
|
email: 'didimitrie4342@example.org',
|
|
password: 'sn3aky-1337-b1m',
|
|
id: ''
|
|
}
|
|
|
|
const stream = {
|
|
name: 'Test Stream References',
|
|
description: 'Whatever goes in here usually...',
|
|
id: ''
|
|
}
|
|
|
|
const testObject = {
|
|
foo: 'bar',
|
|
baz: 'qux',
|
|
id: ''
|
|
}
|
|
|
|
let quitters: (() => void)[] = []
|
|
|
|
before(async () => {
|
|
await beforeEachContext()
|
|
|
|
user.id = await createUser(user)
|
|
stream.id = await createStream({ ...stream, ownerId: user.id })
|
|
testObject.id = await createObject({ streamId: stream.id, object: testObject })
|
|
})
|
|
|
|
afterEach(() => {
|
|
quitters.forEach((quit) => quit())
|
|
quitters = []
|
|
})
|
|
|
|
const branch = { name: 'dim/dev', id: '', description: null }
|
|
|
|
it('Should create a branch', async () => {
|
|
branch.id = (
|
|
await createBranch({
|
|
...branch,
|
|
streamId: stream.id,
|
|
authorId: user.id
|
|
})
|
|
).id
|
|
expect(branch.id).to.be.not.null
|
|
expect(branch.id).to.be.a.string
|
|
})
|
|
|
|
it('Should not allow duplicate branch names', async () => {
|
|
try {
|
|
await createBranch({
|
|
name: 'main',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
assert.fail('Duplicate branches should not be allowed.')
|
|
} catch (err) {
|
|
expect(ensureError(err).message).to.contain(
|
|
'duplicate key value violates unique constraint'
|
|
)
|
|
}
|
|
})
|
|
|
|
it('Should not allow branch names starting with # or /, or branches that have "//" in their name', async () => {
|
|
try {
|
|
await createBranch({
|
|
name: '/pasta',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
assert.fail('Illegal branch name passed through.')
|
|
} catch (err) {
|
|
expect(ensureError(err).message).to.contain('Model names cannot start with')
|
|
}
|
|
|
|
try {
|
|
await createBranch({
|
|
name: '#rice',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
assert.fail('Illegal branch name passed through.')
|
|
} catch (err) {
|
|
expect(ensureError(err).message).to.contain('Model names cannot start with')
|
|
}
|
|
|
|
try {
|
|
await updateBranchAndNotify(
|
|
{
|
|
id: branch.id,
|
|
name: '/super/part/two',
|
|
streamId: stream.id
|
|
},
|
|
user.id
|
|
)
|
|
assert.fail('Illegal branch name passed through in update operation.')
|
|
} catch (err) {
|
|
expect(ensureError(err).message).to.contain('Model names cannot start with')
|
|
}
|
|
|
|
try {
|
|
await updateBranchAndNotify(
|
|
{
|
|
id: branch.id,
|
|
name: '#super#part#three',
|
|
streamId: stream.id
|
|
},
|
|
user.id
|
|
)
|
|
assert.fail('Illegal branch name passed through in update operation.')
|
|
} catch (err) {
|
|
expect(ensureError(err).message).to.contain('Model names cannot start with')
|
|
}
|
|
|
|
try {
|
|
await createBranch({
|
|
name: 'pasta//rice',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
assert.fail('Illegal branch name passed through.')
|
|
} catch (err) {
|
|
expect(ensureError(err).message).to.contain('Model names cannot start with')
|
|
}
|
|
})
|
|
|
|
it('Branch names should be case insensitive (always lowercase)', async () => {
|
|
const id = (
|
|
await createBranch({
|
|
name: 'CaseSensitive',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
).id
|
|
|
|
const b = await getStreamBranchByName(stream.id, 'casesensitive')
|
|
expect(b!.name).to.equal('casesensitive')
|
|
|
|
const bb = await getStreamBranchByName(stream.id, 'CaseSensitive')
|
|
expect(bb!.name).to.equal('casesensitive')
|
|
|
|
const bbb = await getStreamBranchByName(stream.id, 'CASESENSITIVE')
|
|
expect(bbb!.name).to.equal('casesensitive')
|
|
|
|
// cleanup
|
|
await deleteBranchAndNotify({ id, streamId: stream.id }, user.id)
|
|
})
|
|
|
|
it('Should get a branch', async () => {
|
|
const myBranch = await getBranchById(branch.id)
|
|
expect(myBranch!.authorId).to.equal(user.id)
|
|
expect(myBranch!.streamId).to.equal(stream.id)
|
|
})
|
|
|
|
it('Should update a branch', async () => {
|
|
await updateBranchAndNotify(
|
|
{
|
|
id: branch.id,
|
|
description: 'lorem ipsum',
|
|
streamId: stream.id
|
|
},
|
|
user.id
|
|
)
|
|
|
|
const b1 = await getBranchById(branch.id)
|
|
expect(b1!.description).to.equal('lorem ipsum')
|
|
})
|
|
|
|
it('Should get all stream branches', async () => {
|
|
await createBranch({
|
|
name: 'main-faster',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
await sleep(1)
|
|
await createBranch({
|
|
name: 'main-blaster',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
await sleep(1)
|
|
await createBranch({
|
|
name: 'blaster-farter',
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
|
|
const { items, cursor, totalCount } = await getBranchesByStreamId(stream.id)
|
|
expect(items).to.have.lengthOf(5)
|
|
expect(cursor).to.exist
|
|
expect(totalCount).to.exist
|
|
})
|
|
|
|
it('Should delete a branch', async () => {
|
|
let deleteEventFired = false
|
|
quitters.push(
|
|
getEventBus().listen(ModelEvents.Deleted, async ({ payload }) => {
|
|
if (payload.model.id === branch.id) {
|
|
deleteEventFired = true
|
|
}
|
|
})
|
|
)
|
|
|
|
await deleteBranchAndNotify({ id: branch.id, streamId: stream.id }, user.id)
|
|
const { items } = await getBranchesByStreamId(stream.id)
|
|
expect(items).to.have.lengthOf(4)
|
|
expect(deleteEventFired).to.be.true
|
|
})
|
|
|
|
it('Deleting a branch should delete the commit', async () => {
|
|
const branchName = 'pasta'
|
|
|
|
const branchId = (
|
|
await createBranch({
|
|
name: branchName,
|
|
streamId: stream.id,
|
|
authorId: user.id,
|
|
description: null
|
|
})
|
|
).id
|
|
|
|
const { id: tempCommitId } = await createCommitByBranchName({
|
|
streamId: stream.id,
|
|
branchName,
|
|
message: 'temp commit',
|
|
sourceApplication: 'tests',
|
|
objectId: testObject.id,
|
|
authorId: user.id
|
|
})
|
|
await deleteBranchAndNotify({ id: branchId, streamId: stream.id }, user.id)
|
|
|
|
const commit = await Commits().where({ id: tempCommitId }).first()
|
|
expect(commit).to.be.undefined
|
|
})
|
|
|
|
it('Should NOT delete the main branch', async () => {
|
|
const b = await getStreamBranchByName(stream.id, 'main')
|
|
try {
|
|
await deleteBranchAndNotify({ id: b!.id, streamId: stream.id }, user.id)
|
|
assert.fail()
|
|
} catch {
|
|
// pass
|
|
}
|
|
})
|
|
|
|
it('Should return branches in time createdAt order, MAIN first', async () => {
|
|
const { items } = await getBranchesByStreamId(stream.id)
|
|
|
|
expect(items[0].name).to.equal('main')
|
|
expect(items[1].createdAt < items[2].createdAt).to.equal(true)
|
|
})
|
|
})
|