c6cd4c311d
* chore(serverinvites): repository refactor for multiregion * chore(serverinvites): remove migrated functions from old repository * chore(serverinvites): refactor serverInviteForToken resolver for multiregion * chore(serverinvites): invite processing service refactor for multiregion * chore(serverinvites): subscription refactor for multiregion * chore(serverinvites): move buildEmailContents to dedicated file * chore(serverinvites): deleteAllStreamInvites function multiregion refactor * chore(serverinvites): refactor deleteServerOnlyInvites multiregion repository * chore(serverinvites): complete repository refactor for multiregion * feat(serverinvites): create domain module in server invites * fix(serverinvites): no relative imports * feat(serverinvites): extract individual types from repository * feat(serverinvites): move interfaces to operations * fix(serverinvites): update imports referencing old interfaces file * fix(serverinvites): type mismatch for insert invite and delete old * chore(serverinvites): refactor to single repo function * test(serverinvites): fix tests * fix(serverinvites): use domain types in all places * feat(serverinvites): WIP unity * feat(serverinvites): move to new facory names and types * feat(serverinvites): fix tests * fix(serverinvites): use factory name --------- Co-authored-by: Alessandro Magionami <alessandro.magionami@gmail.com>
812 lines
25 KiB
JavaScript
812 lines
25 KiB
JavaScript
const crs = require('crypto-random-string')
|
|
const { buildApolloServer } = require('@/app')
|
|
const { Streams, Users, ServerInvites } = require('@/modules/core/dbSchema')
|
|
const { Roles, AllScopes } = require('@/modules/core/helpers/mainConstants')
|
|
const { createUser } = require('@/modules/core/services/users')
|
|
const { addLoadersToCtx } = require('@/modules/shared/middleware')
|
|
const {
|
|
createServerInvite,
|
|
createStreamInvite,
|
|
resendInvite,
|
|
batchCreateServerInvites,
|
|
batchCreateStreamInvites,
|
|
getStreamInvite,
|
|
useUpStreamInvite,
|
|
cancelStreamInvite,
|
|
getStreamPendingCollaborators,
|
|
getStreamInvites,
|
|
deleteInvite
|
|
} = require('@/test/graphql/serverInvites')
|
|
const { truncateTables } = require('@/test/hooks')
|
|
const { expect } = require('chai')
|
|
const {
|
|
createStream,
|
|
grantPermissionsStream
|
|
} = require('@/modules/core/services/streams')
|
|
const { getUserStreamRole } = require('@/test/speckle-helpers/streamHelper')
|
|
const { createInviteDirectlyFactory } = require('@/test/speckle-helpers/inviteHelper')
|
|
const { buildAuthenticatedApolloServer } = require('@/test/serverHelper')
|
|
const { EmailSendingServiceMock } = require('@/test/mocks/global')
|
|
const db = require('@/db/knex')
|
|
const {
|
|
findInviteByTokenFactory,
|
|
findInviteFactory
|
|
} = require('@/modules/serverinvites/repositories/serverInvites')
|
|
|
|
async function cleanup() {
|
|
await truncateTables([ServerInvites.name, Streams.name, Users.name])
|
|
}
|
|
|
|
const findInviteByToken = findInviteByTokenFactory({ db })
|
|
const findInvite = findInviteFactory({ db })
|
|
|
|
function getInviteTokenFromEmailParams(emailParams) {
|
|
const { text } = emailParams
|
|
const [, inviteId] = text.match(/\?token=(.*?)(\s|&)/i)
|
|
return inviteId
|
|
}
|
|
const createInviteDirectly = createInviteDirectlyFactory({ db })
|
|
|
|
async function validateInviteExistanceFromEmail(emailParams) {
|
|
// Validate that invite exists
|
|
const token = getInviteTokenFromEmailParams(emailParams)
|
|
expect(token).to.be.ok
|
|
const invite = await findInviteByToken(token)
|
|
expect(invite).to.be.ok
|
|
|
|
return invite
|
|
}
|
|
|
|
const mailerMock = EmailSendingServiceMock
|
|
|
|
describe('[Stream & Server Invites]', () => {
|
|
const me = {
|
|
name: 'Authenticated server invites guy',
|
|
email: 'serverinvitesguy@gmail.com',
|
|
password: 'sn3aky-1337-b1m',
|
|
id: undefined
|
|
}
|
|
|
|
const otherGuy = {
|
|
name: 'Some Other DUde',
|
|
email: 'otherguy111@gmail.com',
|
|
password: 'sn3aky-1337-b1m',
|
|
id: undefined
|
|
}
|
|
|
|
const myPrivateStream = {
|
|
name: 'My Private Stream 1',
|
|
isPublic: false,
|
|
id: undefined
|
|
}
|
|
|
|
const otherGuysStream = {
|
|
name: 'Other guys stream 1',
|
|
isPublic: false,
|
|
id: undefined
|
|
}
|
|
|
|
before(async () => {
|
|
await cleanup()
|
|
|
|
// Seeding
|
|
await Promise.all([
|
|
createUser(me).then((id) => (me.id = id)),
|
|
createUser(otherGuy).then((id) => (otherGuy.id = id))
|
|
])
|
|
|
|
await Promise.all([
|
|
createStream({ ...myPrivateStream, ownerId: me.id }).then(
|
|
(id) => (myPrivateStream.id = id)
|
|
),
|
|
createStream({ ...otherGuysStream, ownerId: otherGuy.id }).then(
|
|
(id) => (otherGuysStream.id = id)
|
|
)
|
|
])
|
|
})
|
|
|
|
after(async () => {
|
|
await cleanup()
|
|
})
|
|
|
|
afterEach(() => {
|
|
mailerMock.resetMockedFunctions()
|
|
})
|
|
|
|
describe('When user authenticated', () => {
|
|
/** @type {import('apollo-server-express').ApolloServer} */
|
|
let apollo
|
|
|
|
before(async () => {
|
|
apollo = await buildApolloServer({
|
|
context: () =>
|
|
addLoadersToCtx({
|
|
auth: true,
|
|
userId: me.id,
|
|
role: Roles.Server.User,
|
|
token: 'asd',
|
|
scopes: AllScopes
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('and inviting to server', () => {
|
|
const createInvite = (input) => createServerInvite(apollo, input)
|
|
|
|
it("can't invite an already registered user", async () => {
|
|
const { errors, data } = await createInvite({
|
|
email: otherGuy.email,
|
|
message: 'hey dude'
|
|
})
|
|
|
|
expect(data?.serverInviteCreate).to.be.not.ok
|
|
expect(errors).to.be.ok
|
|
expect(errors.map((e) => e.message).join('|')).to.contain(
|
|
'email is already associated with an account'
|
|
)
|
|
})
|
|
|
|
it('can invite new user', async () => {
|
|
const targetEmail = 'randomguy@random.com'
|
|
|
|
const messagePart1 = '1234hiiiiduuuuude'
|
|
const messagePart2 = 'yepppppp'
|
|
const unsanitaryMessage = `<a href="https://google.com">${messagePart1}</a> <script>${messagePart2}</script>`
|
|
|
|
const sendEmailInvocations = mailerMock.hijackFunction(
|
|
'sendEmail',
|
|
async () => true
|
|
)
|
|
|
|
const result = await createInvite({
|
|
email: targetEmail,
|
|
message: unsanitaryMessage
|
|
})
|
|
|
|
// Check that operation was successful
|
|
expect(result.data?.serverInviteCreate).to.be.ok
|
|
expect(result.errors).to.be.not.ok
|
|
|
|
// Check that email was sent out
|
|
expect(sendEmailInvocations.args).to.have.lengthOf(1)
|
|
const emailParams = sendEmailInvocations.args[0][0]
|
|
expect(emailParams).to.be.ok
|
|
expect(emailParams.to).to.eq(targetEmail)
|
|
expect(emailParams.subject).to.be.ok
|
|
|
|
// Check that message was sanitized
|
|
expect(emailParams.text).to.contain(messagePart1)
|
|
expect(emailParams.text).to.not.contain(messagePart2)
|
|
expect(emailParams.html).to.contain(messagePart1)
|
|
expect(emailParams.html).to.not.contain(messagePart2)
|
|
|
|
// Validate that invite exists
|
|
await validateInviteExistanceFromEmail(emailParams)
|
|
})
|
|
|
|
it("can't invite a user whose email is already registered", async () => {
|
|
const result = await createInvite({
|
|
email: otherGuy.email
|
|
})
|
|
|
|
expect(result.data).to.not.be.ok
|
|
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
|
'This email is already associated with an account'
|
|
)
|
|
})
|
|
|
|
it("can't generate a message that is too long", async () => {
|
|
const result = await createInvite({
|
|
email: 'aaggaggg@asdasd.com',
|
|
message: crs({ length: 1025 })
|
|
})
|
|
|
|
expect(result.data).to.not.be.ok
|
|
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
|
'Personal message too long'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('and inviting to stream', () => {
|
|
const otherGuyAlreadyInvitedStream = {
|
|
name: 'Other guy is already here stream',
|
|
isPublic: false,
|
|
id: undefined
|
|
}
|
|
|
|
const createInvite = (input) => createStreamInvite(apollo, input)
|
|
|
|
before(async () => {
|
|
// Create a stream and make sure otherGuy is already a contributor there
|
|
await createStream({ ...otherGuyAlreadyInvitedStream, ownerId: me.id }).then(
|
|
(id) => (otherGuyAlreadyInvitedStream.id = id)
|
|
)
|
|
await grantPermissionsStream({
|
|
streamId: otherGuyAlreadyInvitedStream.id,
|
|
role: Roles.Stream.Contributor,
|
|
userId: otherGuy.id
|
|
})
|
|
})
|
|
|
|
const alreadyInvitedUserDataSet = [
|
|
{ display: 'by user id', userId: true },
|
|
{ display: 'by email', userId: false }
|
|
]
|
|
|
|
alreadyInvitedUserDataSet.forEach(({ display, userId }) => {
|
|
it(`can't invite an already added user ${display}`, async () => {
|
|
const { errors, data } = await createInvite({
|
|
email: userId ? null : otherGuy.email,
|
|
userId: userId ? otherGuy.id : null,
|
|
message: 'hey dude come to my stream',
|
|
streamId: otherGuyAlreadyInvitedStream.id
|
|
})
|
|
|
|
expect(data?.serverInviteCreate).to.be.not.ok
|
|
expect(errors).to.be.ok
|
|
expect(errors.map((e) => e.message).join('|')).to.contain(
|
|
'user is already a collaborator'
|
|
)
|
|
})
|
|
})
|
|
|
|
it("can't invite with an invalid role", async () => {
|
|
const result = await createInvite({
|
|
email: 'badroleguy@speckle.com',
|
|
streamId: myPrivateStream.id,
|
|
role: 'aaa'
|
|
})
|
|
|
|
expect(result.data?.streamInviteCreate).to.be.not.ok
|
|
expect(result.errors).to.be.ok
|
|
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
|
'Unexpected stream invite role'
|
|
)
|
|
})
|
|
|
|
const userTypesDataSet = [
|
|
{
|
|
display: 'registered user',
|
|
user: otherGuy,
|
|
stream: myPrivateStream,
|
|
email: null
|
|
},
|
|
{
|
|
display: 'registered user (with custom role)',
|
|
user: otherGuy,
|
|
stream: myPrivateStream,
|
|
email: null,
|
|
role: Roles.Stream.Owner
|
|
},
|
|
{
|
|
display: 'unregistered user',
|
|
user: null,
|
|
stream: myPrivateStream,
|
|
email: 'randomer22@lool.com'
|
|
},
|
|
{
|
|
display: 'unregistered user (with custom role)',
|
|
user: null,
|
|
stream: myPrivateStream,
|
|
email: 'randomer22@lool.com',
|
|
Role: Roles.Stream.Reviewer
|
|
}
|
|
]
|
|
|
|
userTypesDataSet.forEach(({ display, user, stream, email, role }) => {
|
|
it(`can invite a ${display}`, async () => {
|
|
const messagePart1 = '1234hiiiiduuuuude'
|
|
const messagePart2 = 'yepppppp'
|
|
const unsanitaryMessage = `<a href="https://google.com">${messagePart1}</a> <script>${messagePart2}</script>`
|
|
const targetEmail = email || user.email
|
|
|
|
const sendEmailInvocations = mailerMock.hijackFunction(
|
|
'sendEmail',
|
|
async () => true
|
|
)
|
|
|
|
const result = await createInvite({
|
|
email,
|
|
message: unsanitaryMessage,
|
|
userId: user?.id || null,
|
|
streamId: stream?.id || null,
|
|
role: role || null
|
|
})
|
|
|
|
// Check that operation was successful
|
|
expect(result.data?.streamInviteCreate).to.be.ok
|
|
expect(result.errors).to.be.not.ok
|
|
|
|
// Check that email was sent out
|
|
const emailParams = sendEmailInvocations.args[0][0]
|
|
expect(emailParams).to.be.ok
|
|
expect(emailParams.to).to.eq(targetEmail)
|
|
expect(emailParams.subject).to.be.ok
|
|
|
|
// Check that message was sanitized
|
|
expect(emailParams.text).to.contain(messagePart1)
|
|
expect(emailParams.text).to.not.contain(messagePart2)
|
|
expect(emailParams.html).to.contain(messagePart1)
|
|
expect(emailParams.html).to.not.contain(messagePart2)
|
|
|
|
// Validate that invite exists
|
|
const invite = await validateInviteExistanceFromEmail(emailParams)
|
|
expect(invite.role).to.eq(role || Roles.Stream.Contributor)
|
|
})
|
|
})
|
|
|
|
it("can't invite user to a nonexistant stream", async () => {
|
|
const result = await createInvite({
|
|
email: 'whocares@really.com',
|
|
streamId: 'ayoooooooo'
|
|
})
|
|
|
|
expect(result.data).to.not.be.ok
|
|
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
|
'not found'
|
|
)
|
|
})
|
|
|
|
it("can't invite user to a stream, if not its owner", async () => {
|
|
const result = await createInvite({
|
|
email: 'whocares@really.com',
|
|
streamId: otherGuysStream.id
|
|
})
|
|
|
|
expect(result.data).to.not.be.ok
|
|
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
|
'You are not authorized to access this resource'
|
|
)
|
|
})
|
|
|
|
it("can't invite a nonexistant user ID to a stream", async () => {
|
|
const result = await createInvite({
|
|
userId: 'bababooey',
|
|
streamId: myPrivateStream.id
|
|
})
|
|
|
|
expect(result.data).to.not.be.ok
|
|
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
|
'Attempting to invite an invalid user'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('and administrating invites', () => {
|
|
const serverInvite1 = {
|
|
message: 'some server invite1',
|
|
email: 'serverinvite1recipient@google.com',
|
|
inviteId: undefined,
|
|
token: undefined
|
|
}
|
|
|
|
const streamInvite1 = {
|
|
message: 'some stream invite1',
|
|
email: 'somestreaminvite1recipient@google.com',
|
|
stream: myPrivateStream,
|
|
inviteId: undefined,
|
|
token: undefined
|
|
}
|
|
|
|
const streamInvite2 = {
|
|
message: 'some stream invite2',
|
|
user: otherGuy,
|
|
stream: myPrivateStream,
|
|
inviteId: undefined,
|
|
token: undefined
|
|
}
|
|
|
|
const invites = [serverInvite1, streamInvite1, streamInvite2]
|
|
|
|
before(async () => {
|
|
apollo = await buildApolloServer({
|
|
context: () =>
|
|
addLoadersToCtx({
|
|
auth: true,
|
|
userId: me.id,
|
|
role: Roles.Server.Admin, // Marking the user as an admin
|
|
token: 'asd',
|
|
scopes: AllScopes
|
|
})
|
|
})
|
|
|
|
// Creating some invites
|
|
await Promise.all(
|
|
invites.map((i) =>
|
|
createInviteDirectly(i, me.id).then((o) => {
|
|
i.inviteId = o.inviteId
|
|
i.token = o.token
|
|
})
|
|
)
|
|
)
|
|
})
|
|
|
|
it('they can resend pre-existing invites irregardless of type', async () => {
|
|
const sendEmailInvocations = mailerMock.hijackFunction(
|
|
'sendEmail',
|
|
async () => true,
|
|
{ times: invites.length }
|
|
)
|
|
|
|
const inviteIds = invites.map((i) => i.inviteId)
|
|
|
|
const results = await Promise.all(
|
|
inviteIds.map((inviteId) => resendInvite(apollo, { inviteId }))
|
|
)
|
|
|
|
for (const result of results) {
|
|
expect(result.data?.inviteResend).to.be.ok
|
|
expect(result.errors).to.not.be.ok
|
|
}
|
|
|
|
expect(sendEmailInvocations.length()).to.eq(inviteIds.length)
|
|
})
|
|
|
|
it('they can delete pre-existing invites irregardless of type', async () => {
|
|
// Create a couple of invites and resolve their IDs
|
|
const deletableInvites = [
|
|
{
|
|
message: 'some server invite1',
|
|
email: 'serverinvite1recipient@google.com',
|
|
inviteId: undefined,
|
|
token: undefined
|
|
},
|
|
{
|
|
message: 'some stream invite1',
|
|
email: 'somestreaminvite1recipient@google.com',
|
|
stream: myPrivateStream,
|
|
inviteId: undefined,
|
|
token: undefined
|
|
}
|
|
]
|
|
await Promise.all(
|
|
deletableInvites.map((i) =>
|
|
createInviteDirectly(i, me.id).then((o) => {
|
|
i.inviteId = o.inviteId
|
|
i.token = o.token
|
|
})
|
|
)
|
|
)
|
|
|
|
// Delete all invites
|
|
for (const invite of deletableInvites) {
|
|
const result = await deleteInvite(apollo, {
|
|
inviteId: invite.inviteId
|
|
})
|
|
expect(result.data?.inviteDelete).to.be.ok
|
|
expect(result.errors).to.not.be.ok
|
|
}
|
|
|
|
// Validate that invites no longer exist
|
|
const invitesInDb = await Promise.all(
|
|
deletableInvites.map((i) => findInvite(i.inviteId))
|
|
)
|
|
expect(invitesInDb.every((i) => !i)).to.be.true
|
|
})
|
|
|
|
it('they can batch create server invites', async () => {
|
|
const emails = ['abababa1@mail.com', 'abababa2@mail.com', 'abababa3@mail.com']
|
|
const message = 'ayyoyoyoyoy'
|
|
|
|
const sendEmailInvocations = mailerMock.hijackFunction(
|
|
'sendEmail',
|
|
async () => true,
|
|
{ times: emails.length }
|
|
)
|
|
|
|
const result = await batchCreateServerInvites(apollo, {
|
|
message,
|
|
emails
|
|
})
|
|
|
|
expect(result.data?.serverInviteBatchCreate).to.be.ok
|
|
expect(result.errors).to.not.be.ok
|
|
|
|
expect(sendEmailInvocations.length()).to.eq(emails.length)
|
|
for (const email of emails) {
|
|
const emailParams = sendEmailInvocations.args.find(([p]) => p.to === email)[0]
|
|
|
|
expect(emailParams).to.be.ok
|
|
expect(emailParams.html).to.contain(message)
|
|
expect(emailParams.text).to.contain(message)
|
|
await validateInviteExistanceFromEmail(emailParams)
|
|
}
|
|
})
|
|
|
|
it('they can batch create stream invites', async () => {
|
|
/** @type {import('@/test/graphql/serverInvites').StreamInviteCreateInput[]} */
|
|
const inputs = [
|
|
{
|
|
email: 'ayyayyyyyyy@asdasdad.com',
|
|
message: 'yoo bruh',
|
|
streamId: myPrivateStream.id
|
|
},
|
|
{
|
|
email: 'ayyayasdadsasdyy@asdasdad.com',
|
|
message: 'yoo bruh',
|
|
streamId: myPrivateStream.id
|
|
},
|
|
{
|
|
userId: otherGuy.id,
|
|
message: 'waddup',
|
|
streamId: myPrivateStream.id
|
|
},
|
|
{
|
|
email: 'someroleguy@asdasdad.com',
|
|
message: 'yoo bruh',
|
|
streamId: myPrivateStream.id,
|
|
role: Roles.Stream.Reviewer
|
|
}
|
|
]
|
|
|
|
const sendEmailInvocations = mailerMock.hijackFunction(
|
|
'sendEmail',
|
|
async () => false,
|
|
{ times: inputs.length }
|
|
)
|
|
|
|
const result = await batchCreateStreamInvites(apollo, inputs)
|
|
|
|
expect(result.data?.streamInviteBatchCreate).to.be.ok
|
|
expect(result.errors).to.not.be.ok
|
|
|
|
expect(sendEmailInvocations.length()).to.eq(inputs.length)
|
|
for (const inputData of inputs) {
|
|
const emailParams = sendEmailInvocations.args.find(([p]) =>
|
|
inputData.email ? p.to === inputData.email : p.to === otherGuy.email
|
|
)[0]
|
|
expect(emailParams).to.be.ok
|
|
expect(emailParams.html).to.contain(inputData.message)
|
|
expect(emailParams.text).to.contain(inputData.message)
|
|
|
|
const invite = await validateInviteExistanceFromEmail(emailParams)
|
|
expect(invite.role).to.eq(inputData.role || Roles.Stream.Contributor)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('and they are looking at a stream invite', async () => {
|
|
const inviteFromOtherGuy = {
|
|
message: 'some stream invite3',
|
|
user: me,
|
|
stream: otherGuysStream,
|
|
inviteId: undefined,
|
|
token: undefined
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
// Create an invite before each test so that we can mutate them
|
|
// in each test as needed
|
|
await createInviteDirectly(inviteFromOtherGuy, otherGuy.id).then((o) => {
|
|
inviteFromOtherGuy.inviteId = o.inviteId
|
|
inviteFromOtherGuy.token = o.token
|
|
})
|
|
})
|
|
|
|
const inviteRetrievalDataset = [
|
|
{ display: 'by token', withId: true },
|
|
{ display: 'without a token', withId: false }
|
|
]
|
|
inviteRetrievalDataset.forEach(({ display, withId }) => {
|
|
it(`the invite can be retrieved ${display}`, async () => {
|
|
const result = await getStreamInvite(apollo, {
|
|
streamId: inviteFromOtherGuy.stream.id,
|
|
token: withId ? inviteFromOtherGuy.token : null
|
|
})
|
|
|
|
expect(result.data?.streamInvite).to.be.ok
|
|
expect(result.errors).to.not.be.ok
|
|
|
|
const data = result.data.streamInvite
|
|
expect(data.inviteId).to.eq(inviteFromOtherGuy.inviteId)
|
|
expect(data.token).to.eq(inviteFromOtherGuy.token)
|
|
expect(data.streamId).to.eq(inviteFromOtherGuy.stream.id)
|
|
expect(data.title).to.eq(me.name)
|
|
|
|
expect(data.user.id).eq(me.id)
|
|
expect(data.user.name).to.eq(me.name)
|
|
|
|
expect(data.invitedBy.id).eq(otherGuy.id)
|
|
expect(data.invitedBy.name).eq(otherGuy.name)
|
|
})
|
|
})
|
|
|
|
const useUpDataSet = [
|
|
{ display: 'declined', accept: false },
|
|
{ display: 'accepted', accept: true }
|
|
]
|
|
useUpDataSet.forEach(({ display, accept }) => {
|
|
it(`the invite can be ${display}`, async () => {
|
|
const token = inviteFromOtherGuy.token
|
|
const inviteId = inviteFromOtherGuy.inviteId
|
|
const streamId = inviteFromOtherGuy.stream.id
|
|
|
|
const { data, errors } = await useUpStreamInvite(apollo, {
|
|
accept,
|
|
token,
|
|
streamId
|
|
})
|
|
|
|
expect(data?.streamInviteUse).to.be.ok
|
|
expect(errors).to.not.be.ok
|
|
expect(await findInvite(inviteId)).to.be.not.ok
|
|
|
|
const userStreamRole = await getUserStreamRole(me.id, streamId)
|
|
expect(userStreamRole).to.eq(accept ? Roles.Stream.Contributor : null)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('and they are managing their own stream collaborators', async () => {
|
|
// Streams
|
|
const myPublicStream = {
|
|
name: 'My public stream 1',
|
|
isPublic: true,
|
|
id: undefined
|
|
}
|
|
|
|
const otherGuysPublicStream = {
|
|
name: 'Other guys public stream 1',
|
|
isPublic: true,
|
|
id: undefined
|
|
}
|
|
|
|
// Invites
|
|
const dynamicInvite = {
|
|
message: 'some stream invite i did3',
|
|
user: otherGuy,
|
|
stream: myPublicStream,
|
|
inviteId: undefined
|
|
}
|
|
|
|
const myInvite = {
|
|
message: 'another of my streams',
|
|
user: otherGuy,
|
|
stream: myPublicStream,
|
|
inviteId: undefined
|
|
}
|
|
|
|
const otherGuysInvite = {
|
|
message: 'a stream belonging to the other guy',
|
|
user: me,
|
|
stream: otherGuysPublicStream,
|
|
inviteId: undefined
|
|
}
|
|
|
|
before(async () => {
|
|
// Create streams
|
|
await Promise.all([
|
|
createStream({ ...myPublicStream, ownerId: me.id }).then(
|
|
(id) => (myPublicStream.id = id)
|
|
),
|
|
createStream({ ...otherGuysPublicStream, ownerId: otherGuy.id }).then(
|
|
(id) => (otherGuysPublicStream.id = id)
|
|
)
|
|
])
|
|
|
|
// Create a couple of static invites that shouldn't be mutated in tests
|
|
await Promise.all([
|
|
createInviteDirectly(myInvite, me.id).then((o) => {
|
|
myInvite.inviteId = o.inviteId
|
|
myInvite.token = o.token
|
|
}),
|
|
createInviteDirectly(otherGuysInvite, otherGuy.id).then((o) => {
|
|
otherGuysInvite.inviteId = o.inviteId
|
|
otherGuysInvite.token = o.token
|
|
})
|
|
])
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
// Create an invite before each test so that we can mutate them
|
|
// in each test as needed
|
|
await createInviteDirectly(dynamicInvite, me.id).then((o) => {
|
|
dynamicInvite.inviteId = o.inviteId
|
|
dynamicInvite.token = o.token
|
|
})
|
|
})
|
|
|
|
it('a pending invite can be deleted', async () => {
|
|
const inviteId = dynamicInvite.inviteId
|
|
|
|
const { data, errors } = await cancelStreamInvite(apollo, {
|
|
streamId: dynamicInvite.stream.id,
|
|
inviteId
|
|
})
|
|
|
|
expect(data?.streamInviteCancel).to.be.ok
|
|
expect(errors).to.be.not.ok
|
|
expect(await findInvite(inviteId)).to.be.not.ok
|
|
})
|
|
|
|
it('own pending collaborators can be retrieved', async () => {
|
|
const streamId = myPublicStream.id
|
|
const { data, errors } = await getStreamPendingCollaborators(apollo, {
|
|
streamId
|
|
})
|
|
|
|
expect(errors).to.be.not.ok
|
|
expect(data.stream).to.be.ok
|
|
expect(data.stream.id).to.eq(streamId)
|
|
|
|
const pendingCollaborators = data.stream.pendingCollaborators || []
|
|
expect(pendingCollaborators).to.have.length(1)
|
|
|
|
const pendingCollaborator = pendingCollaborators[0]
|
|
expect(pendingCollaborator.user?.id).to.eq(otherGuy.id)
|
|
|
|
// tokens shouldn't be resolved, as they're for other people
|
|
expect(pendingCollaborator.token).to.be.null
|
|
})
|
|
|
|
it("a foreign stream's pending collaborators can't be retrieved", async () => {
|
|
const streamId = otherGuysPublicStream.id
|
|
const { data, errors } = await getStreamPendingCollaborators(apollo, {
|
|
streamId
|
|
})
|
|
|
|
expect(data.stream).to.be.ok
|
|
expect(data.stream.id).to.eq(streamId)
|
|
expect(data.stream.pendingCollaborators).to.be.not.ok
|
|
expect(errors).to.be.ok
|
|
expect(errors.map((e) => e.message).join('|')).to.contain(
|
|
'You are not authorized to access this resource'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('and they are looking at all of their stream invites', async () => {
|
|
/** @type {import('apollo-server-express').ApolloServer} */
|
|
let apollo
|
|
|
|
const ownInvitesGuy = {
|
|
name: "Some guy who's invited a lot",
|
|
email: 'mrinvitedguy111@gmail.com',
|
|
password: 'sn3aky-1337-b1m',
|
|
id: undefined
|
|
}
|
|
|
|
before(async () => {
|
|
// Create the user
|
|
await createUser(ownInvitesGuy).then((id) => (ownInvitesGuy.id = id))
|
|
|
|
// Invite him to a few streams
|
|
await Promise.all([
|
|
createInviteDirectly(
|
|
{
|
|
user: ownInvitesGuy,
|
|
stream: myPrivateStream
|
|
},
|
|
me.id
|
|
),
|
|
createInviteDirectly(
|
|
{
|
|
user: ownInvitesGuy,
|
|
stream: otherGuysStream
|
|
},
|
|
otherGuy.id
|
|
)
|
|
])
|
|
|
|
// Build authenticated apollo instance
|
|
apollo = await buildAuthenticatedApolloServer(ownInvitesGuy.id)
|
|
})
|
|
|
|
it('all invites can be retrieved successfully', async () => {
|
|
const { data, errors } = await getStreamInvites(apollo)
|
|
|
|
expect(errors).to.be.not.ok
|
|
expect(data.streamInvites).to.be.ok
|
|
expect(data.streamInvites.length).to.eq(2)
|
|
|
|
const expectedStreamIds = [myPrivateStream.id, otherGuysStream.id]
|
|
const firstInvite = data.streamInvites[0]
|
|
const secondInvite = data.streamInvites[1]
|
|
expect(expectedStreamIds.includes(firstInvite.streamId)).to.be.ok
|
|
expect(expectedStreamIds.includes(secondInvite.streamId)).to.be.ok
|
|
})
|
|
})
|
|
})
|
|
})
|