templating api
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { QueryTypes } from "@sequelize/core"
|
||||
|
||||
import { isNil } from "lodash"
|
||||
|
||||
import db from "@/db/db-client"
|
||||
|
||||
async function getTableNames() {
|
||||
const query = /* sql */ `
|
||||
SELECT
|
||||
table_name as "tableName"
|
||||
FROM
|
||||
information_schema.tables
|
||||
WHERE
|
||||
table_schema = 'public'
|
||||
AND table_type = 'BASE TABLE'
|
||||
AND table_name != 'SequelizeMeta'
|
||||
AND table_name != 'knex_migrations'
|
||||
AND table_name != 'knex_migrations_lock';
|
||||
`
|
||||
|
||||
try {
|
||||
const result = await db.query<{ tableName: string }>(query, { type: QueryTypes.SELECT })
|
||||
const tableNames = result.map((row) => row.tableName)
|
||||
return tableNames
|
||||
} catch (error) {
|
||||
console.error("Error fetching table names:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function buildCleanDatabaseQuery() {
|
||||
const tableNames = await getTableNames()
|
||||
const quotedTableNames = tableNames.map((name) => `"${name}"`)
|
||||
return /* sql */ `
|
||||
TRUNCATE TABLE ${quotedTableNames.join(",\n ")} RESTART IDENTITY CASCADE;
|
||||
`
|
||||
}
|
||||
|
||||
let cleanDatabaseQuery: string | null = null
|
||||
|
||||
export async function cleanDatabase() {
|
||||
if (isNil(cleanDatabaseQuery)) {
|
||||
cleanDatabaseQuery = await buildCleanDatabaseQuery()
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: once all tables are in Sequelize models, use this instead:
|
||||
// await db.truncate({ cascade: true, restartIdentity: true })
|
||||
await db.query(cleanDatabaseQuery, { raw: true })
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default cleanDatabase
|
||||
@@ -0,0 +1,6 @@
|
||||
export { cleanDatabase } from "./clean-database"
|
||||
export { loadTestData } from "./load-test-data"
|
||||
export { mockCurrentUser } from "./mock-current-user"
|
||||
export { mockedAxios } from "./mock-axios"
|
||||
export { request } from "./request"
|
||||
export { testWithCustomLogLevel } from "./test-with-custom-log-level"
|
||||
@@ -0,0 +1,39 @@
|
||||
import path from "path"
|
||||
import { readFileSync } from "fs"
|
||||
|
||||
import { APP_ROOT_PATH } from "@/config"
|
||||
import arrayWrap from "@/utils/array-wrap"
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* - `testDataPath('path/to/my-file.json')`
|
||||
* - `testDataPath('path', 'to', 'my-file.json')`
|
||||
*/
|
||||
export function testDataPath(pathOrPathSegment: string | string[]): string {
|
||||
const pathSegments = arrayWrap(pathOrPathSegment)
|
||||
return path.join(APP_ROOT_PATH, "tests", "data", ...pathSegments)
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* - `specData('path/to/my-file.json')`
|
||||
* - `specData('path', 'to', 'my-file.json')`
|
||||
*
|
||||
* Returns:
|
||||
* - JSON parsed object if file is a JSON file
|
||||
* - Raw file content otherwise
|
||||
*/
|
||||
export function loadTestData(pathOrPathSegment: string | string[]): string | object {
|
||||
const filePath = testDataPath(pathOrPathSegment)
|
||||
const rawData = readFileSync(filePath)
|
||||
|
||||
const extension = path.extname(filePath)
|
||||
switch (extension) {
|
||||
case ".json":
|
||||
return JSON.parse(rawData.toString())
|
||||
default:
|
||||
return rawData.toString()
|
||||
}
|
||||
}
|
||||
|
||||
export default loadTestData
|
||||
@@ -0,0 +1,6 @@
|
||||
import axios from "axios"
|
||||
import MockAdapter from "axios-mock-adapter"
|
||||
|
||||
export const mockedAxios = new MockAdapter(axios)
|
||||
|
||||
export default mockedAxios
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Request, Response, NextFunction } from "express"
|
||||
|
||||
import {
|
||||
findAndAuthorizeCurrentUserMiddleware,
|
||||
type AuthorizationRequest,
|
||||
} from "@/middlewares/find-and-authorize-current-user-middleware"
|
||||
|
||||
import { User } from "@/models"
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* At the top level of a test file import:
|
||||
* import { mockCurrentUser } from "@/support"
|
||||
*
|
||||
* Then where you want to set the current user:
|
||||
* mockCurrentUser(currentUser)
|
||||
*
|
||||
* @param newCurrentUser - The user to set as the current user
|
||||
*/
|
||||
export function mockCurrentUser(newCurrentUser: User) {
|
||||
vi.mock("@/middlewares/jwt-middleware", () => ({
|
||||
default: async (_req: Request, _res: Response, next: NextFunction) => next(),
|
||||
jwtMiddleware: async (_req: Request, _res: Response, next: NextFunction) => next(),
|
||||
}))
|
||||
|
||||
vi.mock("@/middlewares/find-and-authorize-current-user-middleware")
|
||||
const findAndAuthorizeCurrentUserMiddlewareMock = vi.mocked(findAndAuthorizeCurrentUserMiddleware)
|
||||
findAndAuthorizeCurrentUserMiddlewareMock.mockImplementation(
|
||||
async (req: AuthorizationRequest, _res: Response, next: NextFunction) => {
|
||||
const currentUser = await User.withScope(["asCurrentUser"]).findByPk(newCurrentUser.id, {
|
||||
rejectOnEmpty: true,
|
||||
})
|
||||
|
||||
req.currentUser = currentUser
|
||||
next()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import supertest, { AgentOptions } from "supertest"
|
||||
import { App } from "supertest/types"
|
||||
|
||||
import defaultApp from "@/app"
|
||||
|
||||
export function request(options?: AgentOptions | undefined, app: App = defaultApp) {
|
||||
return supertest(app, options)
|
||||
}
|
||||
|
||||
export default request
|
||||
@@ -0,0 +1,19 @@
|
||||
import logger from "@/utils/logger"
|
||||
|
||||
function setLogLevel(level: string) {
|
||||
logger.level = level
|
||||
}
|
||||
|
||||
export const testWithCustomLogLevel = test.extend<{
|
||||
setLogLevel: (level: string) => void
|
||||
}>({
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
setLogLevel: async ({}, use) => {
|
||||
const originalLogLevel = logger.level
|
||||
try {
|
||||
await use(setLogLevel)
|
||||
} finally {
|
||||
setLogLevel(originalLogLevel)
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user