Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: run database tests in CI #174

Merged
merged 13 commits into from
Dec 18, 2024
Merged
5 changes: 5 additions & 0 deletions .env.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LEASING_DATABASE__PASSWORD=Passw0rd!
LEASING_DATABASE__HOST=localhost
LEASING_DATABASE__USER=sa
LEASING_DATABASE__PORT=1433
LEASING_DATABASE__DATABASE=tenants-leases-test
11 changes: 11 additions & 0 deletions .github/workflows/lint-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ on:
workflow_call:
jobs:
test:
services:
sql:
image: mcr.microsoft.com/mssql/server:2019-latest
env:
SA_PASSWORD: Passw0rd!
ACCEPT_EULA: Y
ports:
- "1433:1433"
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand All @@ -15,6 +23,9 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Create db
run: |
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Passw0rd! -Q "CREATE DATABASE [tenants-leases-test];"
- name: Install dependencies
run: npm ci
- name: Run tests
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ config.json
# Jetbrains IDE
.idea/

.vscode/
.vscode/
27 changes: 27 additions & 0 deletions .jest/migrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import path from 'path'
import knex from 'knex'

import Config from '../src/common/config'

export default async function migrate() {
const db = knex({
client: 'mssql',
connection: Config.leasingDatabase,
useNullAsDefault: true,
migrations: {
tableName: 'migrations',
directory: path.join(__dirname, '../migrations'),
},
})

await db.migrate
.latest()
.then(() => {
console.log('Migrations applied')
})
.catch((error) => {
console.error('Error applying migrations', error)
})

await db.destroy()
}
26 changes: 26 additions & 0 deletions .jest/teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import path from 'path'
import knex from 'knex'

import Config from '../src/common/config'

export default async function teardown() {
const db = knex({
client: 'mssql',
connection: Config.leasingDatabase,
useNullAsDefault: true,
migrations: {
tableName: 'migrations',
directory: path.join(__dirname, '../migrations'),
},
})

try {
await db.migrate.rollback().then(() => {
console.log('Migrations rolled back')
})
} catch (error) {
console.error('Error rolling back migrations:', error)
}

await db.destroy()
}
21 changes: 4 additions & 17 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,11 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
modulePathIgnorePatterns: [
'<rootDir>/build/',
//todo: excludes the db tests so that GH-action does not remove data by accident
//todo: remove below line when test db connection exists for GH-action
].concat(
process.env.NODE_ENV === 'test-ci'
? [
'<rootDir>/src/services/lease-service/tests/adapters/',
'<rootDir>/src/services/lease-service/tests/sync-internal-parking-space-listings-from-xpand.test.ts',
'<rootDir>/src/services/lease-service/tests/offer-service.test.ts',
'<rootDir>/src/services/lease-service/tests/update-or-create-application-profile.test.ts',
]
: []
),
//todo: maxWorkers: 1 runs all tests in sequence so that we don't get deadlocks for db tests
//todo: implement a more elegant solution (run db tests in sequence, all other tests in parallel)
maxWorkers: 1,
modulePathIgnorePatterns: ['<rootDir>/build/'],
transformIgnorePatterns: ['node_modules/(?!(onecore-types)/)'],
extensionsToTreatAsEsm: ['.d.ts, .ts'],
setupFiles: ['<rootDir>/.jest/common.ts'],
maxWorkers: 1,
globalSetup: '<rootDir>/.jest/migrate.ts',
globalTeardown: '<rootDir>/.jest/teardown.ts',
}
1 change: 1 addition & 0 deletions knexfile.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Update with your config settings.

require('dotenv').config()

/**
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
"node": "20.*"
},
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test": "DOTENV_CONFIG_PATH=.env.test node -r dotenv/config node_modules/jest/bin/jest --config jest.config.js",
"test:watch": "DOTENV_CONFIG_PATH=.env.test jest --watch",
"build": "tsc",
"start": "npm run migrate:up && node index",
"dev": "npm run migrate:up && nodemon -e ts,js --exec ts-node src/index ",
"start": "npm run migrate:up && node -r dotenv/config index",
"dev": "DOTENV_CONFIG_PATH=.env npm run migrate:up && nodemon -e ts,js --exec ts-node -r dotenv/config src/index ",
"lint": "eslint src/",
"migrate:make": "knex migrate:make",
"migrate:up": "knex migrate:latest --env dev",
"migrate:down": "knex migrate:down --env dev",
"seed": "knex seed:run --env dev",
"script:expire-listings": "node scripts/expire-listings.js",
"ts:watch": "tsc --watch --noEmit",
"test:ci": "NODE_ENV=test-ci jest --silent",
"test:ci": "DOTENV_CONFIG_PATH=.env.ci node -r dotenv/config node_modules/jest/bin/jest --config jest.config.js",
"ts:ci": "tsc --noEmit"
},
"author": "",
Expand Down
9 changes: 1 addition & 8 deletions src/common/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import configPackage from '@iteam/config'
import dotenv from 'dotenv'
import path from 'path'

if (process.env.NODE_ENV == 'test') {
dotenv.config({ path: path.join(__dirname, '../../.env.test') })
} else {
dotenv.config()
}
import 'dotenv/config'

export interface Config {
port: number
Expand Down
52 changes: 4 additions & 48 deletions src/services/lease-service/adapters/db.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,11 @@
import knex from 'knex'
import Config from '../../../common/config'
import * as path from 'path'

const getStandardConfig = () => ({
client: 'mssql',
connection: Config.leasingDatabase,
})
import Config from '../../../common/config'

const getTestConfig = () => {
return {
export const createDbClient = () =>
knex({
client: 'mssql',
connection: Config.leasingDatabase,
useNullAsDefault: true,
migrations: {
tableName: 'migrations',
directory: path.join(__dirname, '../../../../migrations'),
},
}
}

const getConfigBasedOnEnvironment = () => {
const environment = process.env.NODE_ENV || 'dev'
return environment === 'test' ? getTestConfig() : getStandardConfig()
}

export const db = knex(getConfigBasedOnEnvironment())

const migrate = async () => {
await db.migrate
.latest()
.then(() => {
console.log('Migrations applied')
})
.catch((error) => {
console.error('Error applying migrations', error)
})
}

const teardown = async () => {
console.log('Rolling back migrations')
try {
await db.migrate.rollback().then(() => {
console.log('Migrations rolled back')
})
} catch (error) {
console.error('Error rolling back migrations:', error)
}

await db.destroy().then(() => {
console.log('Database destroyed')
})
}

export { migrate, teardown }
export const db = createDbClient()
61 changes: 39 additions & 22 deletions src/services/lease-service/adapters/listing-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@
}

const createListing = async (
listingData: Omit<Listing, 'id'>
listingData: Omit<Listing, 'id'>,
dbConnection = db
): Promise<AdapterResult<Listing, 'conflict-active-listing' | 'unknown'>> => {
try {
const insertedRow = await db<DbListing>('Listing')
const insertedRow = await dbConnection<DbListing>('Listing')
.insert({
RentalObjectCode: listingData.rentalObjectCode,
Address: listingData.address,
Expand Down Expand Up @@ -109,9 +110,10 @@
* @returns {Promise<Listing>} - Promise that resolves to the existing listing if it exists.
*/
const getActiveListingByRentalObjectCode = async (
rentalObjectCode: string
rentalObjectCode: string,
dbConnection = db
): Promise<Listing | undefined> => {
const listing = await db<DbListing>('Listing')
const listing = await dbConnection<DbListing>('Listing')
.where({
RentalObjectCode: rentalObjectCode,
Status: ListingStatus.Active,
Expand All @@ -125,10 +127,11 @@
}

const getListingById = async (
listingId: number
listingId: number,
dbConnection = db
): Promise<Listing | undefined> => {
logger.info({ listingId }, `Getting listing ${listingId} from leasing DB`)
const result = await db
const result = await dbConnection
.from('listing AS l')
.select<DbListing & { applicants: string | null }>(
'l.*',
Expand Down Expand Up @@ -184,10 +187,11 @@
* @returns {Promise<Applicant | undefined>} - Returns the applicant.
*/
const getApplicantById = async (
applicantId: number
applicantId: number,
dbConnection = db
): Promise<Applicant | undefined> => {
logger.info({ applicantId }, 'Getting applicant from leasing DB')
const applicant = await db<DbApplicant>('Applicant')
const applicant = await dbConnection<DbApplicant>('Applicant')
.where({
Id: applicantId,
})
Expand All @@ -206,13 +210,16 @@
return transformDbApplicant(applicant)
}

const createApplication = async (applicationData: Omit<Applicant, 'id'>) => {
const createApplication = async (
applicationData: Omit<Applicant, 'id'>,
dbConnection = db
) => {
logger.info(
{ contactCode: applicationData.contactCode },
'Creating application in listing DB'
)

const insertedRow = await db('applicant')
const insertedRow = await dbConnection('applicant')
.insert({
Name: applicationData.name,
NationalRegistrationNumber: applicationData.nationalRegistrationNumber,
Expand All @@ -234,7 +241,7 @@
const updateApplicantStatus = async (
applicantId: number,
status: ApplicantStatus,
dbConnection: Knex<any, unknown[]> = db

Check warning on line 244 in src/services/lease-service/adapters/listing-adapter.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
): Promise<AdapterResult<null, 'no-update' | 'unknown'>> => {
try {
const query = await dbConnection('applicant')
Expand All @@ -253,6 +260,7 @@
}

const getListingsWithApplicants = async (
db: Knex,
opts?: GetListingsWithApplicantsFilterParams
): Promise<AdapterResult<Array<Listing>, 'unknown'>> => {
try {
Expand All @@ -274,7 +282,7 @@
)
.with({ type: 'ready-for-offer' }, () =>
db.raw(
`WHERE l.Status = ?
`WHERE l.Status = ?
AND EXISTS (
SELECT 1
FROM applicant a
Expand All @@ -291,7 +299,7 @@
)
.with({ type: 'offered' }, () =>
db.raw(
`WHERE l.Status = ?
`WHERE l.Status = ?
AND EXISTS (
SELECT 1
FROM offer o
Expand Down Expand Up @@ -362,8 +370,11 @@
* @returns {Promise<Applicant | undefined>} - Returns the applicants.
*/

const getApplicantsByContactCode = async (contactCode: string) => {
const result = await db('Applicant')
const getApplicantsByContactCode = async (
contactCode: string,
dbConnection = db
) => {
const result = await dbConnection('Applicant')
.where({ ContactCode: contactCode })
.select<Array<DbApplicant>>('*')

Expand All @@ -379,9 +390,10 @@
*/
const getApplicantByContactCodeAndListingId = async (
contactCode: string,
listingId: number
listingId: number,
dbConnection = db
) => {
const result = await db<DbApplicant>('Applicant')
const result = await dbConnection<DbApplicant>('Applicant')
.where({
ContactCode: contactCode,
ListingId: listingId,
Expand All @@ -400,8 +412,12 @@
* @param {number} listingId - The ID of the listing the applicant belongs to.
* @returns {Promise<boolean>} - Returns true if applicant belongs to listing, false if not.
*/
const applicationExists = async (contactCode: string, listingId: number) => {
const result = await db<DbApplicant>('applicant')
const applicationExists = async (
contactCode: string,
listingId: number,
dbConnection = db
) => {
const result = await dbConnection<DbApplicant>('applicant')
.where({
ContactCode: contactCode,
ListingId: listingId,
Expand All @@ -415,9 +431,9 @@
return true
}

const getExpiredListings = async () => {
const getExpiredListings = async (dbConnection = db) => {
const currentDate = new Date()
const listings = await db('listing')
const listings = await dbConnection('listing')
.where('PublishedTo', '<', currentDate)
.andWhere('Status', '=', ListingStatus.Active)
return listings
Expand Down Expand Up @@ -446,7 +462,7 @@
const updateListingStatuses = async (
listingIds: number[],
status: ListingStatus,
dbConnection: Knex<any, unknown[]> = db

Check warning on line 465 in src/services/lease-service/adapters/listing-adapter.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
): Promise<AdapterResult<null, 'no-update' | 'unknown'>> => {
try {
const query = await dbConnection('listing')
Expand All @@ -464,10 +480,11 @@
}

const deleteListing = async (
listingId: number
listingId: number,
dbConnection = db
): Promise<AdapterResult<null, 'unknown' | 'conflict'>> => {
try {
await db('listing').delete().where('Id', listingId)
await dbConnection('listing').delete().where('Id', listingId)
return { ok: true, data: null }
} catch (err) {
logger.error(err, 'listingAdapter.deleteListing')
Expand Down
Loading
Loading