-
-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathorganizations.ts
More file actions
344 lines (318 loc) · 13.5 KB
/
organizations.ts
File metadata and controls
344 lines (318 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
import { ApolloServer } from '@apollo/server'
import muuid from 'uuid-mongodb'
import MutableAreaDataSource from '../model/MutableAreaDataSource.js'
import MutableOrganizationDataSource from '../model/MutableOrganizationDataSource.js'
import { AreaType } from '../db/AreaTypes.js'
import { OperationType, OrganizationEditableFieldsType, OrganizationType, OrgType } from '../db/OrganizationTypes.js'
import ChangeLogDataSource from '../model/ChangeLogDataSource.js'
import { queryAPI, setUpServer } from '../utils/testUtils.js'
import { muuidToString } from '../utils/helpers.js'
import { validate as validateMuuid } from 'uuid'
import { InMemoryDB } from '../utils/inMemoryDB.js'
import express from 'express'
describe('organizations API', () => {
let server: ApolloServer
let user: muuid.MUUID
let userUuid: string
let app: express.Application
let inMemoryDB: InMemoryDB
// Mongoose models for mocking pre-existing state.
let areas: MutableAreaDataSource
let organizations: MutableOrganizationDataSource
let usa: AreaType
let ca: AreaType
let wa: AreaType
beforeAll(async () => {
({ server, inMemoryDB, app } = await setUpServer())
// Auth0 serializes uuids in "relaxed" mode, resulting in this hex string format
// "59f1d95a-627d-4b8c-91b9-389c7424cb54" instead of base64 "WfHZWmJ9S4yRuTicdCTLVA==".
user = muuid.mode('relaxed').v4()
userUuid = muuidToString(user)
})
beforeEach(async () => {
await inMemoryDB.clear()
areas = MutableAreaDataSource.getInstance()
organizations = MutableOrganizationDataSource.getInstance()
usa = await areas.addCountry('usa')
ca = await areas.addArea(user, 'CA', usa.metadata.area_id)
wa = await areas.addArea(user, 'WA', usa.metadata.area_id)
})
afterAll(async () => {
await server?.stop()
await inMemoryDB?.close()
})
describe('mutations', () => {
const createQuery = `
mutation addOrganization($input: AddOrganizationInput!) {
organization: addOrganization(input: $input) {
orgId
orgType
displayName
associatedAreaIds
excludedAreaIds
createdBy
updatedBy
}
}
`
const updateQuery = `
mutation updateOrganization($input: OrganizationEditableFieldsInput!) {
organization: updateOrganization(input: $input) {
orgId
associatedAreaIds
excludedAreaIds
displayName
content {
website
email
donationLink
instagramLink
facebookLink
hardwareReportLink
description
}
}
}
`
it('creates and updates an organization', async () => {
const createResponse = await queryAPI({
query: createQuery,
operationName: 'addOrganization',
variables: { input: { displayName: 'Friends of Openbeta', orgType: 'LOCAL_CLIMBING_ORGANIZATION' } },
userUuid,
roles: ['user_admin'],
app
})
expect(createResponse.statusCode).toBe(200)
const orgId = createResponse.body.data.organization.orgId
// orgId is MUUID scalar type which should always be serialized into the muuid hex string format.
expect(validateMuuid(orgId)).toBeTruthy()
expect(createResponse.body.data.organization.orgType).toBe('LOCAL_CLIMBING_ORGANIZATION')
expect(createResponse.body.data.organization.displayName).toBe('Friends of Openbeta')
expect(createResponse.body.data.organization.associatedAreaIds).toStrictEqual([])
expect(createResponse.body.data.organization.excludedAreaIds).toStrictEqual([])
expect(createResponse.body.data.organization.createdBy).toBe(userUuid)
expect(createResponse.body.data.organization.updatedBy).toBe(userUuid)
const updateResponse = await queryAPI({
query: updateQuery,
operationName: 'updateOrganization',
variables: {
input: {
orgId,
associatedAreaIds: [muuidToString(usa.metadata.area_id)],
excludedAreaIds: [muuidToString(wa.metadata.area_id)],
displayName: 'Allies of Openbeta',
website: 'https://alliesofopenbeta.com',
email: 'admin@alliesofopenbeta.com',
donationLink: 'https://donate.alliesofopenbeta.com',
instagramLink: 'https://instagram.com/alliesofopenbeta',
facebookLink: 'https://www.facebook.com/alliesofopenbeta',
hardwareReportLink: 'https://www.alliesofopenbeta.com/reporthardware',
description: 'We are allies of OpenBeta!'
}
},
userUuid,
roles: ['user_admin'],
app
})
expect(updateResponse.statusCode).toBe(200)
expect(updateResponse.body.errors).toBeUndefined()
const orgResult = updateResponse.body.data.organization
expect(orgResult.orgId).toBe(orgId)
expect(orgResult.associatedAreaIds).toEqual([muuidToString(usa.metadata.area_id)])
expect(orgResult.excludedAreaIds).toEqual([muuidToString(wa.metadata.area_id)])
expect(orgResult.displayName).toBe('Allies of Openbeta')
expect(orgResult.content.website).toBe('https://alliesofopenbeta.com')
expect(orgResult.content.email).toBe('admin@alliesofopenbeta.com')
expect(orgResult.content.donationLink).toBe('https://donate.alliesofopenbeta.com')
expect(orgResult.content.instagramLink).toBe('https://instagram.com/alliesofopenbeta')
expect(orgResult.content.facebookLink).toBe('https://www.facebook.com/alliesofopenbeta')
expect(orgResult.content.hardwareReportLink).toBe('https://www.alliesofopenbeta.com/reporthardware')
expect(orgResult.content.description).toBe('We are allies of OpenBeta!')
// eslint-disable-next-line
await new Promise(res => setTimeout(res, 1000))
const orgHistory = await ChangeLogDataSource.getInstance().getOrganizationChangeSets()
expect(orgHistory).toHaveLength(2)
// verify changes in most recent order
expect(orgHistory[0].operation).toEqual(OperationType.updateOrganization)
expect(orgHistory[1].operation).toEqual(OperationType.addOrganization)
// history is shown most recent first
const updateRecord = orgHistory[0].changes
expect(updateRecord[0].dbOp).toEqual('update')
expect(updateRecord[0].fullDocument._change?.historyId).toEqual(orgHistory[0]._id) // should point to current change
expect(updateRecord[0].fullDocument.displayName).toBe('Allies of Openbeta')
const createRecord = orgHistory[1].changes
expect(createRecord[0].dbOp).toEqual('insert')
expect(createRecord[0].fullDocument._change?.historyId).toEqual(orgHistory[1]._id) // should point to current change
expect(createRecord[0].fullDocument.displayName).toBe('Friends of Openbeta')
})
it('throws an error if a non-user_admin tries to add an organization', async () => {
const response = await queryAPI({
query: createQuery,
operationName: 'addOrganization',
variables: { input: { displayName: 'Friends of Openbeta', orgType: 'LOCAL_CLIMBING_ORGANIZATION' } },
userUuid,
roles: ['editor'],
app
})
expect(response.statusCode).toBe(401)
expect(response.body.data.organization).toBeNull()
expect(response.body.errors[0].message).toBe('Not Authorised!')
})
})
describe('queries', () => {
const organizationQuery = `
query organization($input: MUUID) {
organization(muuid: $input) {
orgId
associatedAreaIds
excludedAreaIds
displayName
content {
website
email
donationLink
instagramLink
facebookLink
hardwareReportLink
description
}
}
}
`
const organizationsQuery = `
query organizations($filter: OrgFilter, $sort: OrgSort, $limit: Int) {
organizations(filter: $filter, sort: $sort, limit: $limit) {
orgId
associatedAreaIds
displayName
}
}
`
let alphaFields: OrganizationEditableFieldsType
let deltaFields: OrganizationEditableFieldsType
let gammaFields: OrganizationEditableFieldsType
let alphaOrg: OrganizationType
let deltaOrg: OrganizationType
let gammaOrg: OrganizationType
beforeEach(async () => {
alphaFields = {
displayName: 'Alpha OpenBeta Club',
associatedAreaIds: [ca.metadata.area_id, wa.metadata.area_id],
email: 'admin@alphaopenbeta.com',
facebookLink: 'https://www.facebook.com/alphaopenbeta',
instagramLink: 'https://www.instagram.com/alphaopenbeta',
hardwareReportLink: 'https://alphaopenbeta.com/reporthardware'
}
alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields)
.then((res: OrganizationType | null) => {
if (res === null) throw new Error('Failure mocking organization.')
return res
})
deltaFields = {
displayName: 'Delta OpenBeta Club',
email: 'admin@deltaopenbeta.com'
}
deltaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, deltaFields)
.then((res: OrganizationType | null) => {
if (res === null) throw new Error('Failure mocking organization.')
return res
})
gammaFields = {
displayName: 'Delta Gamma OpenBeta Club',
description: 'We are an offshoot of the delta club.\nSee our website for more details.',
excludedAreaIds: [wa.metadata.area_id]
}
gammaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, gammaFields)
.then((res: OrganizationType | null) => {
if (res === null) throw new Error('Failure mocking organization.')
return res
})
})
it('retrieves an organization with an MUUID', async () => {
const response = await queryAPI({
query: organizationQuery,
operationName: 'organization',
variables: { input: muuidToString(alphaOrg.orgId) },
userUuid,
app
})
expect(response.statusCode).toBe(200)
const orgResult = response.body.data.organization
expect(orgResult.orgId).toBe(muuidToString(alphaOrg.orgId))
expect(orgResult.displayName).toBe(alphaFields.displayName)
expect(orgResult.associatedAreaIds.sort()).toEqual([muuidToString(ca.metadata.area_id), muuidToString(wa.metadata.area_id)].sort())
expect(orgResult.content.email).toBe(alphaFields.email)
expect(orgResult.content.instagramLink).toBe(alphaFields.instagramLink)
expect(orgResult.content.facebookLink).toBe(alphaFields.facebookLink)
expect(orgResult.content.hardwareReportLink).toBe(alphaFields.hardwareReportLink)
})
it('retrieves organizations using an exactMatch displayName filter', async () => {
const response = await queryAPI({
query: organizationsQuery,
operationName: 'organizations',
variables: { filter: { displayName: { match: 'Delta OpenBeta Club', exactMatch: true } } },
userUuid,
app
})
expect(response.statusCode).toBe(200)
const dataResult = response.body.data.organizations
expect(dataResult.length).toBe(1)
expect(dataResult[0].orgId).toBe(muuidToString(deltaOrg.orgId))
})
it('retrieves organizations using a non-exactMatch displayName filter', async () => {
const response = await queryAPI({
query: organizationsQuery,
operationName: 'organizations',
variables: { filter: { displayName: { match: 'delta', exactMatch: false } } },
userUuid,
app
})
expect(response.statusCode).toBe(200)
const dataResult = response.body.data.organizations
expect(dataResult.length).toBe(2)
expect(dataResult.map(o => o.orgId).sort()).toEqual([muuidToString(deltaOrg.orgId), muuidToString(gammaOrg.orgId)].sort())
})
it('limits organizations returned', async () => {
const response = await queryAPI({
query: organizationsQuery,
operationName: 'organizations',
variables: {
limit: 1
},
userUuid,
app
})
expect(response.statusCode).toBe(200)
const dataResult = response.body.data.organizations
expect(dataResult.length).toBe(1) // Three matching orgs, but only return one.
})
it('retrieves organizations using an associatedAreaIds filter', async () => {
const response = await queryAPI({
query: organizationsQuery,
operationName: 'organizations',
variables: { filter: { associatedAreaIds: { includes: [muuidToString(ca.metadata.area_id)] } } },
userUuid,
app
})
// Graphql should convert `includes` from a string[] to MUUID[]
expect(response.statusCode).toBe(200)
const dataResult = response.body.data.organizations
expect(dataResult.length).toBe(1)
expect(dataResult[0].orgId).toBe(muuidToString(alphaOrg.orgId))
})
it('excludes organizations using an excludedAreaIds filter', async () => {
const response = await queryAPI({
query: organizationsQuery,
operationName: 'organizations',
variables: { filter: { excludedAreaIds: { excludes: [muuidToString(wa.metadata.area_id)] } } },
userUuid,
app
})
expect(response.statusCode).toBe(200)
const dataResult = response.body.data.organizations
expect(dataResult.length).toBe(2)
expect(dataResult.map((o: OrganizationType) => o.orgId).includes(muuidToString(gammaOrg.orgId))).toBeFalsy()
})
})
})