Skip to content

Commit

Permalink
Passing most tests stable with vitest fixtures
Browse files Browse the repository at this point in the history
The test speed is up roughly 10x but my expectation is that it should be
able to come lower than that. The beauty of the way test containment is
implemented here means that there is not much need for isolation - which
is good for simulating our use case because we make use of a lot of
singletons (though thankfully not TOO much shared state)

It was all relatively trivial with the exception of histories. I am
intensely anxious with the implementation that has been used for
document history, as it obscures control flow and does not look like it
will scale - especially if we were ever to want horizontal deployment.

All in all the tests are faster, and can tighten up development feedback
loops BUT the un-strict nature of our existing test runners means that
there are some inconsistencies in execution that I haven't yet uncovered
  • Loading branch information
CocoisBuggy committed Dec 14, 2024
1 parent d2a9503 commit d7b432a
Show file tree
Hide file tree
Showing 28 changed files with 1,641 additions and 3,276 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ yarn-error.log*
lerna-debug.log*
.DS_Store

# test profiling data
profiling

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

Expand Down
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,17 @@
],
"console": "integratedTerminal"
},
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"type": "node",
"request": "launch",
"name": "Debug Tests",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
"args": ["run"],
"smartStep": true,
"console": "integratedTerminal"
}
]
}
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
"supertest": "^6.3.3",
"ts-standard": "^12.0.0",
"typescript": "4.9.5",
"vitest": "^2.1.8",
"wait-for-expect": "^3.0.2"
"vitest": "^2.1.8"
},
"dependencies": {
"@apollo/server": "^4.11.2",
Expand Down Expand Up @@ -70,7 +69,7 @@
"scripts": {
"lint": "yarn ts-standard",
"fix": "yarn ts-standard --fix",
"test": "vitest run --no-file-parallelism",
"test": "vitest run --silent",
"build": "tsc -p tsconfig.json",
"build-release": "tsc -p tsconfig.release.json",
"clean": "tsc -b --clean && rm -rf build/*",
Expand Down
121 changes: 53 additions & 68 deletions src/__tests__/areas.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,35 @@
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 { OrganizationEditableFieldsType, OrganizationType, OrgType } from '../db/OrganizationTypes.js'
import { queryAPI, setUpServer } from '../utils/testUtils.js'
import { muuidToString } from '../utils/helpers.js'
import { InMemoryDB } from '../utils/inMemoryDB.js'
import express from 'express'
import { gqlTest } from './fixtures/gql.fixtures.js'
interface LocalContext {
includedChild: AreaType
excludedArea: AreaType
alphaFields: OrganizationEditableFieldsType
alphaOrg: OrganizationType
}

describe('areas 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)
})
const it = gqlTest.extend<LocalContext>({
includedChild: async ({ addArea, area }, use) => await use(await addArea(undefined, { parent: area })),
excludedArea: async ({ addArea, area }, use) => await use(await addArea(undefined, { parent: area })),
alphaFields: async ({ excludedArea, task, area }, use) => await use({
displayName: task.id,
associatedAreaIds: [area.metadata.area_id],
excludedAreaIds: [excludedArea.metadata.area_id]
}),
alphaOrg: async ({ organizations, user, alphaFields }, use) => {
const org = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields)
.then((res: OrganizationType | null) => {
if (res === null) throw new Error('Failure mocking organization.')
return res
})

afterAll(async () => {
await server.stop()
await inMemoryDB.close()
})
await use(org)
await organizations.deleteFromCacheById(org._id)
}
})

describe('areas API', () => {
describe('queries', () => {
const areaQuery = `
query area($input: ID) {
Expand All @@ -56,50 +41,50 @@ describe('areas API', () => {
}
}
`
let alphaFields: OrganizationEditableFieldsType
let alphaOrg: OrganizationType

beforeEach(async () => {
alphaFields = {
displayName: 'USA without CA Org',
associatedAreaIds: [usa.metadata.area_id],
excludedAreaIds: [ca.metadata.area_id]
}
alphaOrg = await organizations.addOrganization(user, OrgType.localClimbingOrganization, alphaFields)
.then((res: OrganizationType | null) => {
if (res === null) throw new Error('Failure mocking organization.')
return res
})
})

it('retrieves an area omitting organizations that exclude it', async () => {
const response = await queryAPI({
it('retrieves an area omitting organizations that exclude it', async ({ query, userUuid, excludedArea }) => {
const response = await query({
query: areaQuery,
operationName: 'area',
variables: { input: ca.metadata.area_id },
userUuid,
app
variables: { input: muuidToString(excludedArea.metadata.area_id) },
userUuid
})

expect(response.statusCode).toBe(200)
const areaResult = response.body.data.area
expect(areaResult.uuid).toBe(muuidToString(ca.metadata.area_id))
expect(areaResult).toBeTruthy()
expect(areaResult.uuid).toBe(muuidToString(excludedArea.metadata.area_id))
// Even though alphaOrg associates with ca's parent, usa, it excludes
// ca and so should not be listed.
expect(areaResult.organizations).toHaveLength(0)
})

it.each([userUuid, undefined])('retrieves an area and lists associated organizations', async (userId) => {
const response = await queryAPI({
it('retrieves an area and lists associated organizations', async ({ query, userUuid, includedChild, alphaOrg }) => {
const response = await query({
query: areaQuery,
operationName: 'area',
variables: { input: muuidToString(includedChild.metadata.area_id) },
userUuid
})

expect(response.statusCode).toBe(200)
const areaResult = response.body.data.area
expect(areaResult.uuid).toBe(muuidToString(includedChild.metadata.area_id))
expect(areaResult.organizations).toHaveLength(1)
expect(areaResult.organizations[0].orgId).toBe(muuidToString(alphaOrg.orgId))
})

it('retrieves an area and lists associated organizations, even with no auth context', async ({ query, includedChild, alphaOrg }) => {
const response = await query({
query: areaQuery,
operationName: 'area',
variables: { input: wa.metadata.area_id },
userUuid: userId,
app
variables: { input: muuidToString(includedChild.metadata.area_id) }
})

expect(response.statusCode).toBe(200)
const areaResult = response.body.data.area
expect(areaResult.uuid).toBe(muuidToString(wa.metadata.area_id))
expect(areaResult.uuid).toBe(muuidToString(includedChild.metadata.area_id))
console.log(areaResult)
expect(areaResult.organizations).toHaveLength(1)
expect(areaResult.organizations[0].orgId).toBe(muuidToString(alphaOrg.orgId))
})
Expand Down
97 changes: 36 additions & 61 deletions src/__tests__/bulkImport.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import {ApolloServer} from "@apollo/server";
import muuid from "uuid-mongodb";
import express from "express";
import {InMemoryDB} from "../utils/inMemoryDB.js";
import {queryAPI, setUpServer} from "../utils/testUtils.js";
import {muuidToString} from "../utils/helpers.js";
import exampleImportData from './import-example.json' assert {type: 'json'};
import {AreaType} from "../db/AreaTypes.js";
import {BulkImportResultType} from "../db/BulkImportTypes.js";
import MutableClimbDataSource from "../model/MutableClimbDataSource.js";
import BulkImportDataSource from "../model/BulkImportDataSource.js";
import { gqlTest } from "./fixtures/gql.fixtures.js";
import { muuidToString } from "../utils/helpers";

interface LocalContext {
importData: typeof exampleImportData
}

const it = gqlTest.extend<LocalContext>({
importData: async ({ country }, use) => await use(
{ areas: exampleImportData.areas.map(x => {
if (x.countryCode) {
return { ...x, countryCode: country.shortCode}
}

return { ...x }
}) as typeof exampleImportData['areas']
})
})

describe('bulkImportAreas', () => {
const query = `
const bulkQuery = `
mutation bulkImportAreas($input: BulkImportInput!) {
bulkImportAreas(input: $input) {
addedAreas {
Expand All @@ -33,85 +43,50 @@ describe('bulkImportAreas', () => {
}
`

let server: ApolloServer
let user: muuid.MUUID
let userUuid: string
let app: express.Application
let inMemoryDB: InMemoryDB
let testArea: AreaType

let bulkImport: BulkImportDataSource
let climbs: MutableClimbDataSource

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)
bulkImport = BulkImportDataSource.getInstance()
climbs = MutableClimbDataSource.getInstance()
})

beforeEach(async () => {
await inMemoryDB.clear()
await bulkImport.addCountry('usa')
testArea = await bulkImport.addArea(user, "Test Area", null, "us")
})

afterAll(async () => {
await server.stop()
await inMemoryDB.close()
})

it('should return 403 if no user', async () => {
const res = await queryAPI({
app,
query,
it('should return 403 if no user', async ( { query, importData }) => {
const res = await query({
query: bulkQuery,
operationName: 'bulkImportAreas',
variables: {input: exampleImportData}
variables: {input: importData}
})
expect(res.statusCode).toBe(200)
expect(res.body.errors[0].message).toBe('Not Authorised!')
})

it('should return 403 if user is not an editor', async () => {
const res = await queryAPI({
app,
it('should return 403 if user is not an editor', async ({ query, userUuid, importData }) => {
const res = await query({
userUuid,
query,
query: bulkQuery,
operationName: 'bulkImportAreas',
variables: {input: exampleImportData}
variables: {input: importData}
})
expect(res.statusCode).toBe(200)
expect(res.body.errors[0].message).toBe('Not Authorised!')
})

it('should return 200 if user is an editor', async () => {
const res = await queryAPI({
app,
it('should return 200 if user is an editor', async ({ query, importData, userUuid}) => {
const res = await query({
userUuid,
roles: ['editor'],
query,
query: bulkQuery,
operationName: 'bulkImportAreas',
variables: {input: exampleImportData}
variables: {input: importData}
})
expect(res.status).toBe(200)
})

it('should import data', async () => {
const res = await queryAPI({
app,
it('should import data', async ({ query, userUuid, area, bulkImport, climbs, importData }) => {
const res = await query({
userUuid,
roles: ['editor'],
query,
query: bulkQuery,
operationName: 'bulkImportAreas',
variables: {
input: {
areas: [
...exampleImportData.areas,
...importData.areas,
{
uuid: testArea.metadata.area_id,
uuid: muuidToString(area.metadata.area_id),
areaName: "Updated Test Area",
}
]
Expand Down
Loading

0 comments on commit d7b432a

Please sign in to comment.