Skip to content

Commit

Permalink
feat: API Tests skeleton (#100)
Browse files Browse the repository at this point in the history
* feat: API Tests skeleton

* chore: create .env.test for testing env vars

* fix: test by providing .env.test on main components

---------

Co-authored-by: Alejo Thomas Ortega <[email protected]>
  • Loading branch information
kevinszuchet and aleortega authored Oct 7, 2024
1 parent b57b6eb commit 6a2dbbb
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 28 deletions.
5 changes: 4 additions & 1 deletion api/.env.default
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
HTTP_SERVER_PORT=3000
HTTP_SERVER_HOST=0.0.0.0

# PG_COMPONENT_PSQL_PORT=''
# PG_COMPONENT_PSQL_HOST=''
# PG_COMPONENT_PSQL_DATABASE=''
# PG_COMPONENT_PSQL_USER=''
# PG_COMPONENT_PSQL_PASSWORD=''
# PG_COMPONENT_PSQL_PASSWORD=''

# CDN_HOST=''
10 changes: 10 additions & 0 deletions api/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP_SERVER_PORT=3000
HTTP_SERVER_HOST=0.0.0.0

PG_COMPONENT_PSQL_PORT='5450'
PG_COMPONENT_PSQL_HOST='localhost'
PG_COMPONENT_PSQL_DATABASE='badges'
PG_COMPONENT_PSQL_USER='postgres'
PG_COMPONENT_PSQL_PASSWORD='pass1234'

CDN_HOST='https://host.tld'
11 changes: 11 additions & 0 deletions api/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
moduleFileExtensions: ['ts', 'js'],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'test/tsconfig.json' }]
},
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.ts'],
coveragePathIgnorePatterns: ['/node_modules/', '/src/index.ts'],
testMatch: ['**/*.spec.(ts)'],
testEnvironment: 'node'
}
2 changes: 1 addition & 1 deletion api/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { createBackfillMergerComponent } from './logic/backfill-merger'

// Initialize all the components of the app
export async function initComponents(): Promise<AppComponents> {
const config = await createDotEnvConfigComponent({ path: ['.env.default', '.env'] })
const config = await createDotEnvConfigComponent({ path: ['.env.test', '.env.default', '.env'] })
const logs = await createLogComponent({ config })

const logger = logs.getLogger('components')
Expand Down
62 changes: 62 additions & 0 deletions api/test/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// This file is the "test-environment" analogous for src/components.ts
// Here we define the test components to be used in the testing environment
import path from 'path'

import { createRunner, createLocalFetchCompoment } from '@well-known-components/test-helpers'
import { createDotEnvConfigComponent } from '@well-known-components/env-config-provider'
import { createTestMetricsComponent } from '@well-known-components/metrics'
import { createPgComponent } from '@well-known-components/pg-component'


import { createDbComponent } from '@badges/common'
import { initComponents as originalInitComponents } from '../src/components'
import { metricDeclarations } from '../src/metrics'
import { TestComponents } from '../src/types'
import { main } from '../src/service'

/**
* Behaves like Jest "describe" function, used to describe a test for a
* use case, it creates a whole new program and components to run an
* isolated test.
*
* State is persistent within the steps of the test.
*/
export const test = createRunner<TestComponents>({
main,
initComponents
})

async function initComponents(): Promise<TestComponents> {
const components = await originalInitComponents()

const config = await createDotEnvConfigComponent({ path: ['.env.test'] })

let databaseUrl: string | undefined = await config.getString('PG_COMPONENT_PSQL_CONNECTION_STRING')
if (!databaseUrl) {
const dbUser = await config.requireString('PG_COMPONENT_PSQL_USER')
const dbDatabaseName = await config.requireString('PG_COMPONENT_PSQL_DATABASE')
const dbPort = await config.requireString('PG_COMPONENT_PSQL_PORT')
const dbHost = await config.requireString('PG_COMPONENT_PSQL_HOST')
const dbPassword = await config.requireString('PG_COMPONENT_PSQL_PASSWORD')
databaseUrl = `postgres://${dbUser}:${dbPassword}@${dbHost}:${dbPort}/${dbDatabaseName}`
}
// This worker writes to the database, so it runs the migrations
const pg = await createPgComponent(components, {
migration: {
databaseUrl,
dir: path.resolve(__dirname, '../../processor/src/migrations'),
migrationsTable: 'pgmigrations',
ignorePattern: '.*\\.map',
direction: 'up'
}
})
const db = createDbComponent({ pg })

return {
...components,
config,
db,
metrics: createTestMetricsComponent(metricDeclarations),
localFetch: await createLocalFetchCompoment(config)
}
}
22 changes: 22 additions & 0 deletions api/test/integration/get-badges.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { test } from '../components'

test('GET /categories', function ({ components }) {
const endpointPath = '/badges'

it('should return all badges', async function () {
const response = await components.localFetch.fetch(endpointPath, {
method: 'GET',
redirect: 'manual',
headers: {
'Content-Type': 'application/json'
}
})

const body = await response.json()

expect(response.status).toBe(200)
expect(body).toMatchObject({
data: components.badgeService.getAllBadges()
})
})
})
25 changes: 25 additions & 0 deletions api/test/integration/get-categories.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BadgeCategory } from '@badges/common'
import { test } from '../components'

test('GET /categories', function ({ components }) {
const endpointPath = '/categories'

it('should return all categories', async function () {
const response = await components.localFetch.fetch(endpointPath, {
method: 'GET',
redirect: 'manual',
headers: {
'Content-Type': 'application/json'
}
})

const body = await response.json()

expect(response.status).toBe(200)
expect(body).toMatchObject({
data: {
categories: Object.values(BadgeCategory)
}
})
})
})
38 changes: 14 additions & 24 deletions api/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
{
"compilerOptions": {
"strict": true,
"module": "commonjs",
"target": "ESNext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"skipLibCheck": true,
"composite": true,
"baseUrl": "./src",
"rootDir": "./src",
"outDir": "./dist",
"lib": [
"ES2020"
]
},
"include": [
"src"
]
}
"compilerOptions": {
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
"types": ["node", "jest"],
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"noEmit": true
},
"references": [{ "path": "../../common" }]
}
17 changes: 17 additions & 0 deletions api/test/unit/logic/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BadgeId } from '@badges/common'
import { parseBadgeId } from '../../../src/logic/utils'

describe('Utils', () => {
describe('when parsing a badge id', () => {
it('should return the badge id correctly typed when it exists', () => {
const id = BadgeId.EMOTIONISTA
const result = parseBadgeId(id)
expect(result).toBe(id)
})

it('should return undefined when the badge id does not exist', () => {
const result = parseBadgeId('non-existent-badge-id')
expect(result).toBeUndefined()
})
})
})
2 changes: 2 additions & 0 deletions processor/.env.default
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# PG_COMPONENT_PSQL_DATABASE=''
# PG_COMPONENT_PSQL_USER=''
# PG_COMPONENT_PSQL_PASSWORD=''

# CDN_HOST=''
# AWS_SNS_ARN=''
# AWS_SNS_ENDPOINT=''
# AWS_SQS_ENDPOINT=''
7 changes: 7 additions & 0 deletions processor/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
PG_COMPONENT_PSQL_PORT='5450'
PG_COMPONENT_PSQL_HOST='localhost'
PG_COMPONENT_PSQL_DATABASE='badges'
PG_COMPONENT_PSQL_USER='postgres'
PG_COMPONENT_PSQL_PASSWORD='pass1234'

CDN_HOST='https://any-host.tld'
2 changes: 1 addition & 1 deletion processor/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function reportInitialMetrics({ metrics }: Pick<AppComponents, 'metrics'>): void

// Initialize all the components of the app
export async function initComponents(): Promise<AppComponents> {
const config = await createDotEnvConfigComponent({ path: ['.env.default', '.env.local', '.env'] })
const config = await createDotEnvConfigComponent({ path: ['.env.test', '.env.default', '.env'] })
const logs = await createLogComponent({ config })

const logger = logs.getLogger('components')
Expand Down
2 changes: 1 addition & 1 deletion processor/test/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const test = createRunner<TestComponents>({
async function initComponents(): Promise<TestComponents> {
const components = await originalInitComponents()

const config = await createDotEnvConfigComponent({ path: ['.env.default'] })
const config = await createDotEnvConfigComponent({ path: ['.env.test'] })

return {
...components,
Expand Down

0 comments on commit 6a2dbbb

Please sign in to comment.