-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update history gql queries, add integration tests
- Loading branch information
1 parent
a116660
commit 62eefdb
Showing
8 changed files
with
231 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { ApolloServer } from 'apollo-server' | ||
import muuid from 'uuid-mongodb' | ||
import { jest } from '@jest/globals' | ||
import MutableAreaDataSource, { createInstance as createAreaInstance } from '../model/MutableAreaDataSource.js' | ||
import MutableOrganizationDataSource, { createInstance as createOrgInstance } from '../model/MutableOrganizationDataSource.js' | ||
import MutableClimbDataSource, { createInstance as createClimbInstance } from '../model/MutableClimbDataSource.js' | ||
import { AreaType } from '../db/AreaTypes.js' | ||
import { OrgType, OrganizationType } from '../db/OrganizationTypes.js' | ||
import { muuidToString } from '../utils/helpers.js' | ||
import { queryAPI, setUpServer } from '../utils/testUtils.js' | ||
|
||
jest.setTimeout(60000) | ||
|
||
describe('history API', () => { | ||
let server: ApolloServer | ||
let user: muuid.MUUID | ||
let userUuid: string | ||
let inMemoryDB | ||
|
||
// Mongoose models for mocking pre-existing state. | ||
let areas: MutableAreaDataSource | ||
let organizations: MutableOrganizationDataSource | ||
let climbs: MutableClimbDataSource | ||
|
||
beforeAll(async () => { | ||
({ server, inMemoryDB } = 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 = createAreaInstance() | ||
organizations = createOrgInstance() | ||
climbs = createClimbInstance() | ||
}) | ||
|
||
afterAll(async () => { | ||
await server.stop() | ||
await inMemoryDB.close() | ||
}) | ||
|
||
describe('queries', () => { | ||
const FRAGMENT_CHANGE_HISTORY = ` | ||
fragment ChangeHistoryFields on History { | ||
id | ||
createdAt | ||
operation | ||
editedBy | ||
changes { | ||
dbOp | ||
changeId | ||
updateDescription { | ||
updatedFields | ||
} | ||
fullDocument { | ||
... on Area { | ||
areaName | ||
uuid | ||
metadata { | ||
leaf | ||
areaId | ||
} | ||
} | ||
... on Climb { | ||
id | ||
name | ||
uuid | ||
} | ||
... on Organization { | ||
orgId | ||
} | ||
} | ||
} | ||
} | ||
` | ||
|
||
const QUERY_RECENT_CHANGE_HISTORY = ` | ||
${FRAGMENT_CHANGE_HISTORY} | ||
query ($filter: AllHistoryFilter) { | ||
getChangeHistory(filter: $filter) { | ||
...ChangeHistoryFields | ||
} | ||
} | ||
` | ||
|
||
let usa: AreaType | ||
let ca: AreaType | ||
let alphaOrg: OrganizationType | ||
let climbIds: string[] | ||
|
||
it('queries recent change history successfully', async () => { | ||
// Make changes to be tracked. | ||
usa = await areas.addCountry('usa') | ||
ca = await areas.addArea(user, 'CA', usa.metadata.area_id) | ||
const alphaFields = { | ||
displayName: 'Alpha OpenBeta Club', | ||
associatedAreaIds: [usa.metadata.area_id], | ||
email: '[email protected]' | ||
} | ||
alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields) | ||
climbIds = await climbs.addOrUpdateClimbs(user, ca.metadata.area_id, [{ name: 'Alpha Climb' }]) | ||
|
||
// Query for changes and ensure they are tracked. | ||
const resp = await queryAPI({ | ||
query: QUERY_RECENT_CHANGE_HISTORY, | ||
variables: { filter: {} }, | ||
userUuid | ||
}) | ||
expect(resp.statusCode).toBe(200) | ||
const histories = resp.body.data.getChangeHistory | ||
expect(histories.length).toBe(3) | ||
|
||
// Latest change first | ||
// Note: addCountry is not captured by history. | ||
const [climbChange, orgChange, areaChange] = histories | ||
|
||
expect(climbChange.operation).toBe('updateClimb') | ||
expect(climbChange.editedBy).toBe(userUuid) | ||
// Two changes: Insert the climb, update the parent area | ||
// Ordering is non-deterministic. | ||
expect(climbChange.changes.length).toBe(2) | ||
const insertChange = climbChange.changes.filter(c => c.dbOp === 'insert')[0] | ||
const updateChange = climbChange.changes.filter(c => c.dbOp === 'update')[0] | ||
expect(insertChange.fullDocument.uuid).toBe(climbIds[0]) | ||
expect(updateChange.fullDocument.uuid).toBe(muuidToString(ca.metadata.area_id)) | ||
|
||
expect(orgChange.operation).toBe('addOrganization') | ||
expect(orgChange.editedBy).toBe(userUuid) | ||
expect(orgChange.changes[0].fullDocument.orgId).toBe(muuidToString(alphaOrg.orgId)) | ||
|
||
expect(areaChange.operation).toBe('addArea') | ||
expect(areaChange.editedBy).toBe(userUuid) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import jwt from 'jsonwebtoken' | ||
import { jest } from '@jest/globals' | ||
import request from 'supertest' | ||
import inMemoryDB from './inMemoryDB.js' | ||
import type { InMemoryDB } from './inMemoryDB.js' | ||
import { createServer } from '../server.js' | ||
import { ApolloServer } from 'apollo-server' | ||
|
||
const PORT = 4000 | ||
|
||
interface QueryAPIProps { | ||
query: string | ||
operationName?: string | ||
variables: any | ||
userUuid: string | ||
roles?: string[] | ||
port?: number | ||
} | ||
|
||
/* | ||
* Helper function for querying the locally-served API. It mocks JWT verification | ||
* so we can pretend to have an role we want when calling the API. | ||
*/ | ||
export const queryAPI = async ({ query, operationName, variables, userUuid, roles = [], port = PORT }: QueryAPIProps): Promise<request.Response> => { | ||
// Avoid needing to pass in actual signed tokens. | ||
const jwtSpy = jest.spyOn(jwt, 'verify') | ||
jwtSpy.mockImplementation(() => { | ||
return { | ||
// Roles defined at https://manage.auth0.com/dashboard/us/dev-fmjy7n5n/roles | ||
'https://tacos.openbeta.io/roles': roles, | ||
'https://tacos.openbeta.io/uuid': userUuid | ||
} | ||
}) | ||
|
||
const queryObj = { query, operationName, variables } | ||
const response = await request(`http://localhost:${port}`) | ||
.post('/') | ||
.send(queryObj) | ||
.set('Authorization', 'Bearer placeholder-jwt-see-SpyOn') | ||
|
||
return response | ||
} | ||
|
||
interface SetUpServerReturnType { | ||
server: ApolloServer | ||
inMemoryDB: InMemoryDB | ||
} | ||
/* | ||
* Starts Apollo server and has Mongo inMemory replset connect to it. | ||
*/ | ||
export const setUpServer = async (port = PORT): Promise<SetUpServerReturnType> => { | ||
const server = await createServer() | ||
await inMemoryDB.connect() | ||
await server.listen({ port }) | ||
return { server, inMemoryDB } | ||
} |