Files
speckle-server/packages/server/modules/serverinvites/tests/invites.spec.js
T
Iain Sproat a537d34dcc Rate limit all endpoints (#1213)
* Demonstration of bug to test when middleware added
- Adding middleware, even no-op, causes test to fail

* Make middleware async, but introduce delay.
Revert test back to original.

* Revert tests

* Add a 1ms sleep to the test to reduce likelihood of flakiness

* Rate limiting on all express endpoints using middleware

* Adds all configuration for existing rate limited endpoints

* It is helpful to add the package to yarn first

* Implements respectsLimits using Redis rate limiter

* Fix for test `Should rate-limit user creation`
- if rate limit error, post to `/auth/local/register` will return a 429 status code

* All rate limiting provided by new ratelimiter.ts

* Consolidate typescript interfaces

* Amend signature of function to require source to be passed in, and not try to guess it from the request

* Rename respectsLimits to isWithinRateLimits

* Throw within catch of Promise

* Replace rejectsRequestWithRatelimitStatusIfNeeded throughout code

* Sending rate limit response should deal with other types of error
- Sentry notified of the error

* Express middleware rate limits by a 3 second burst or a daily rate
- Provide action when generating 429 response

* Prevent DOS of Redis

* Add 'Retry-After' for all cases when responding with 429 status code
- default of 1 day, but dynamic based on available information

* Generate rate limiters once, on init
- Improved and consistent handling of exit from functions
- fixed environment variable names

* WIP Refactor rate limiting setup

Co-authored-by: Iain Sproat <iainsproat@users.noreply.github.com>

* WIP: fixed references, now runs but tests fail

* Use getSourceFromRequest where possible

* WIP: unit tests for rate limiter

* Unit tests for ratelimiter

* feat(IFC): WIP IFC parser improvements

* Revert "feat(IFC): WIP IFC parser improvements"

This reverts commit 093089a2c4.

* refactor authz, rate limiting middleware to global

Co-authored-by: Kristaps Fabians Geikins <fabis94@users.noreply.github.com>
Co-authored-by: Iain Sproat <iainsproat@users.noreply.github.com>

* invites tests fix

* fix(server ratelimiter): export public interfaces

* Unit test for rate limiter use in memory rate limiter
- in memory rate limiter is configured with zero limit by default

* Fixed #1219 (#1221)

* WIP: improve auth test for rate limiting user creation

* ci(circleci config): publishing was broken when main branch was tagged (i.e. for releases) (#1224)

* Gitignore CPU profiles

* All tests are  now passing locally

* Fixed an issue in the frontend which was causing the views not to work. Fixed an issue with object selection camera animation where the dolly lerp factor was much too high for smooth animation (#1225)

* feat(structured logging): implements structured logging for backend (#1217)

* each log line is a json object
* structured logging allows logs to be ingested by machines and the logs to be indexed and queried addresses #1105
* structured logging allows arbitrary properties to be appended to each log line, and ingestion of logs to remain robust
* Structured logging provided by `pino` library
* Add `express-pino-logger` dependency
* Remove `debug`, `morgan`, and `morgan-debug` and replace with structured logging
* `console.log` & `console.error` replaced with structured logging in backend
* Remove `DEBUG` environment variable and replace with `LOG_LEVEL`
- Note that there is a test which reads from a logged line on `stdout`. This is not robust, it would be better to use the childProcess.pid to look up the port number.
* Log errors at points we explicitly send error to Sentry
* Amend indentation of a couple of log messages to align indentation with others

* Revert "feat(structured logging): implements structured logging for backend (#1217)" (#1227)

This reverts commit 84cb74e8b3.

* Move error to core/errors
- augmented typescript types moved to type-augmentations

* Added a missing wait in the screenshot generation loop (#1228)

* refactor(server rest api): remove duplicate rate limit requests

* feat(server rate limits): increase rate limits for the upload endpoints

* chore(server rate limits): final cleanup

Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
Co-authored-by: Iain Sproat <iainsproat@users.noreply.github.com>
Co-authored-by: Dimitrie Stefanescu <didimitrie@gmail.com>
Co-authored-by: Kristaps Fabians Geikins <fabis94@users.noreply.github.com>
Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
Co-authored-by: Alexandru Popovici <alexandrupopoviciioan@gmail.com>
2022-11-29 17:06:11 +01:00

805 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,
deleteInvite,
getStreamInvite,
useUpStreamInvite,
cancelStreamInvite,
getStreamPendingCollaborators,
getStreamInvites
} = require('@/test/graphql/serverInvites')
const { truncateTables } = require('@/test/hooks')
const { expect } = require('chai')
const {
createStream,
grantPermissionsStream
} = require('@/modules/core/services/streams')
const {
getInviteByToken,
getInvite: getInviteFromDB
} = require('@/modules/serverinvites/repositories')
const { getUserStreamRole } = require('@/test/speckle-helpers/streamHelper')
const { createInviteDirectly } = require('@/test/speckle-helpers/inviteHelper')
const { buildAuthenticatedApolloServer } = require('@/test/serverHelper')
const { EmailSendingServiceMock } = require('@/test/mocks/global')
async function cleanup() {
await truncateTables([ServerInvites.name, Streams.name, Users.name])
}
function getInviteTokenFromEmailParams(emailParams) {
const { text } = emailParams
const [, inviteId] = text.match(/\?token=(.*)\s/i)
return inviteId
}
async function validateInviteExistanceFromEmail(emailParams) {
// Validate that invite exists
const token = getInviteTokenFromEmailParams(emailParams)
expect(token).to.be.ok
const invite = await getInviteByToken(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 do not have access to 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) => getInviteFromDB(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 getInviteFromDB(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 getInviteFromDB(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 do not have access'
)
})
})
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
})
})
})
})