diff --git a/.dockerignore b/.dockerignore index 5b6ac9f..827970a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,3 +16,4 @@ jest* npm-debug.log yarn-error.log **/*.test.ts +src/test diff --git a/.env.test b/.env.ci similarity index 97% rename from .env.test rename to .env.ci index 9c9f5c0..499184a 100644 --- a/.env.test +++ b/.env.ci @@ -44,4 +44,3 @@ SMTP_PASS=password SMTP_USER=user SMTP_SECURE=false SMTP_SENDER=hbp@hbp.com -NOTIFY_EMAIL_CHANGE=true \ No newline at end of file diff --git a/.env.dockerdev.example b/.env.dockerdev.example deleted file mode 100644 index ad058a0..0000000 --- a/.env.dockerdev.example +++ /dev/null @@ -1,23 +0,0 @@ -# Environment variables that will be loaded in the docker-compose.dev hbp service -# It should contain secrets used in Jest and that SHOULD NEVER BE INCLUDED IN GIT!!! - -# OAuth Providers ids and secrets -GITHUB_CLIENT_ID=an_oauth_client_id -GITHUB_CLIENT_SECRET=the_given_secret_string - -GOOGLE_CLIENT_ID=an_oauth_client_id.apps.googleusercontent.com -GOOGLE_CLIENT_SECRET=the_given_secret_string - -LINKEDIN_CLIENT_ID=an_oauth_client_id -LINKEDIN_CLIENT_SECRET=the_given_secret_string - -# Credentials to test against the providers -TEST_GITHUB_USERNAME=bobthetester@gmail.com -TEST_GITHUB_PASSWORD=one.two.three.four.is.not.a.good.passord.even.for.a.test.account - -TEST_GOOGLE_USERNAME=bobthetester@gmail.com -TEST_GOOGLE_PASSWORD=one.two.three.four.is.not.a.good.passord.even.for.a.test.account - -TEST_LINKEDIN_USERNAME=bobthetester@gmail.com -TEST_LINKEDIN_PASSWORD=one.two.three.four.is.not.a.good.passord.even.for.a.test.account - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af22368..bd6902b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,6 +54,7 @@ jobs: container: image: node:12 env: + NODE_ENV: CI PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true HASURA_GRAPHQL_ADMIN_SECRET: ${{ env.HASURA_GRAPHQL_ADMIN_SECRET }} HASURA_ENDPOINT: http://graphql-engine:8080/v1/graphql @@ -88,14 +89,8 @@ jobs: && apt-get update && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf --no-install-recommends && rm -rf /var/lib/apt/lists/* - - name: Install Hasura CLI - run: yarn global add hasura-cli - - name: Create Hasura CLI config - run: "printf 'endpoint: http://graphql-engine:8080\nHASURA_GRAPHQL_ADMIN_SECRET: ${{ env.HASURA_GRAPHQL_ADMIN_SECRET }}\n' > config.yaml" - - name: Copy mock migrations - run: cp -r mock-config/migrations/* migrations/ - - name: Run migrations - run: hasura migrate apply + - name: Copy test migrations + run: cp -r test-mocks/migrations/* migrations/ - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" diff --git a/.gitignore b/.gitignore index 6588dbe..3e9bc74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ dist .env* -!.env.test +!.env.ci !.env*.example *.log hasura diff --git a/Dockerfile.dev b/Dockerfile.dev index 35d99ca..93c153e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,18 +1,5 @@ FROM node:10-slim -# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) -# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer installs, work. -RUN apt-get update \ - && apt-get install -y wget gnupg git \ - && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ - && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ - && apt-get update \ - && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \ - --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* - -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true - ARG NODE_ENV=development ENV NODE_ENV $NODE_ENV ENV PORT 3000 @@ -24,6 +11,4 @@ RUN yarn install COPY . . -HEALTHCHECK --interval=5s --timeout=5s --retries=3 CMD wget localhost:${PORT}/healthz -q -O - > /dev/null 2>&1 - -CMD ["yarn", "run", "dev-container"] +CMD ["yarn", "run", "dev:docker"] diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..947d97b --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,27 @@ +FROM node:10-slim + +# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) +# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer installs, work. +RUN apt-get update \ + && apt-get install -y wget gnupg git \ + && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ + && apt-get update \ + && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true + +ARG NODE_ENV=development +ENV NODE_ENV $NODE_ENV +ENV PORT 3000 + +WORKDIR /app + +COPY package.json yarn.lock ./ +RUN yarn install + +COPY . . + +CMD ["yarn", "run", "test"] diff --git a/bash-utils.sh b/bash-utils.sh deleted file mode 100644 index e018337..0000000 --- a/bash-utils.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -export-dotenv() { # export-env - value=$(cat $1 | sed 's/#.*//g' | grep -o "^${2}.*" | xargs) - value=$(echo "${value#*=}") - var_name=${2:-3} - initial_value=$(printf '%s' "${!var_name}") - export $(echo ${var_name})=${value:-$initial_value} -} - -wait-for() { # wait-for - printf "Waiting for the service ${2:-1} to be ready..." - until $(curl -X GET --output /dev/null --silent --head --fail ${1}); do - printf '.' - sleep 1 - done - echo -} diff --git a/docker-compose.dev.yaml b/docker-compose.development.yaml similarity index 72% rename from docker-compose.dev.yaml rename to docker-compose.development.yaml index cac8d8d..45ed90e 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.development.yaml @@ -4,10 +4,11 @@ services: build: context: . dockerfile: Dockerfile.dev - command: '${COMMAND_LINE:-yarn run dev-container}' - env_file: .env.dockerdev + env_file: '.env.development' + depends_on: + - 'mailhog' environment: - NODE_ENV: '${NODE_ENV:-test}' + NODE_ENV: development JWT_ALGORITHM: HS256 JWT_KEY: '${JWT_KEY}' SMTP_HOST: mailhog @@ -16,14 +17,11 @@ services: volumes: - .:/app - /app/node_modules - tty: true graphql-engine: ports: - '8080:8080' environment: HASURA_GRAPHQL_JWT_SECRET: '{"type": "HS256", "key": "${JWT_KEY}"}' - volumes: - - ./mock-config/migrations/1585679214182_custom_user_column:/hasura-migrations/1585679214182_custom_user_column minio: ports: - 8000:9000 # Do not use port 9000 in the host machine as developpers using portainer might already use it diff --git a/docker-compose.test.yaml b/docker-compose.test.yaml new file mode 100644 index 0000000..5bccbde --- /dev/null +++ b/docker-compose.test.yaml @@ -0,0 +1,25 @@ +version: '3.6' +services: + hasura-backend-plus: + build: + context: . + dockerfile: Dockerfile.test + env_file: '.env.test' + depends_on: + - 'mailhog' + environment: + NODE_ENV: test + JWT_ALGORITHM: HS256 + JWT_KEY: '${JWT_KEY}' + SMTP_HOST: mailhog + SMTP_PORT: 1025 + SMTP_SECURE: 'false' + volumes: + - .:/app + - /app/node_modules + - ./test-mocks/migrations/1585679214182_custom_user_column:/app/migrations/1585679214182_custom_user_column + graphql-engine: + environment: + HASURA_GRAPHQL_JWT_SECRET: '{"type": "HS256", "key": "${JWT_KEY}"}' + mailhog: + image: mailhog/mailhog diff --git a/docker-compose.yaml b/docker-compose.yaml index ce6f1fd..e61ba3c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,7 +8,7 @@ services: environment: POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-postgrespassword}' graphql-engine: - image: hasura/graphql-engine:v1.1.0.cli-migrations + image: hasura/graphql-engine:v1.1.0 depends_on: - 'postgres' restart: always @@ -16,14 +16,14 @@ services: HASURA_GRAPHQL_ADMIN_SECRET: '${HASURA_GRAPHQL_ADMIN_SECRET:?HASURA_GRAPHQL_ADMIN_SECRET}' HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-postgrespassword}@postgres:5432/postgres HASURA_GRAPHQL_JWT_SECRET: '{"type": "RS256", "jwk_url": "http://hasura-backend-plus:3000/auth/jwks"}' - volumes: - - ./migrations:/hasura-migrations hasura-backend-plus: image: nhost/hasura-backend-plus:latest + depends_on: + - 'graphql-engine' + - 'minio' ports: - - '3000:${PORT:-3000}' + - '3000:3000' environment: - PORT: '${PORT:-3000}' HASURA_GRAPHQL_ADMIN_SECRET: '${HASURA_GRAPHQL_ADMIN_SECRET:?HASURA_GRAPHQL_ADMIN_SECRET}' HASURA_ENDPOINT: http://graphql-engine:8080/v1/graphql S3_ENDPOINT: http://minio:9000 diff --git a/jest.config.js b/jest.config.js index 497c329..500b70c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,10 +1,11 @@ module.exports = { - globalSetup: './jest/setup.js', - globalTeardown: './jest/teardown.js', - testEnvironment: './jest/puppeteer_environment.js', + globalSetup: './src/test/setup.ts', + globalTeardown: './src/test/teardown.ts', + testEnvironment: './src/test/puppeteer_environment.js', verbose: true, moduleNameMapper: { - '^@shared/(.*)$': '/src/shared/$1' + '^@shared/(.*)$': '/src/shared/$1', + '^@test/(.*)$': '/src/test/$1' }, testPathIgnorePatterns: ['/dist/', '/node_modules/'], setupFilesAfterEnv: ['expect-puppeteer', 'jest-extended'], diff --git a/jest/getPuppeteer.js b/jest/getPuppeteer.js deleted file mode 100644 index ce213e0..0000000 --- a/jest/getPuppeteer.js +++ /dev/null @@ -1,8 +0,0 @@ -function getPuppeteer() { - const puppeteer = require('puppeteer-extra') - const StealthPlugin = require('puppeteer-extra-plugin-stealth') - puppeteer.use(StealthPlugin()) - return puppeteer -} - -module.exports.getPuppeteer = getPuppeteer diff --git a/jest/teardown.js b/jest/teardown.js deleted file mode 100644 index 6cdbbdc..0000000 --- a/jest/teardown.js +++ /dev/null @@ -1,7 +0,0 @@ -// const { teardown: teardownServer } = require('jest-dev-server') - -module.exports = async function teardown(jestConfig = {}) { - // if (!jestConfig.watch && !jestConfig.watchAll) { - // await teardownServer() - // } -} diff --git a/package.json b/package.json index 03b0ff8..bf5d696 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "src/start.ts", "scripts": { "dev": "ts-node-dev -r tsconfig-paths/register --no-notify src/start.ts", - "dev-container": "ts-node-dev -r tsconfig-paths/register --no-deps --respawn --poll --interval 1000 --no-notify src/start.ts", + "dev:docker": "ts-node-dev -r tsconfig-paths/register --no-deps --respawn --poll --interval 1000 --no-notify src/start.ts", "docs:build": "vuepress build docs", "docs:dev": "vuepress dev docs", "start": "ts-node -r tsconfig-paths/register src/start.ts", @@ -69,6 +69,7 @@ "graphql": "15.0.0", "graphql-request": "1.8.2", "graphql-tag": "2.10.3", + "hasura-cli": "^1.1.1", "helmet": "3.22.0", "hibp": "9.0.0", "jose": "1.26.0", @@ -164,4 +165,4 @@ "singleQuote": true, "trailingComma": "none" } -} +} \ No newline at end of file diff --git a/src/migrate.ts b/src/migrate.ts new file mode 100644 index 0000000..5fa2130 --- /dev/null +++ b/src/migrate.ts @@ -0,0 +1,70 @@ +import { spawnSync } from 'child_process' +import fetch from 'node-fetch' +import { writeFileSync } from 'fs' +import url from 'url' +import cors from 'cors' +import express from 'express' +import helmet from 'helmet' + +import { HASURA_ENDPOINT, HASURA_GRAPHQL_ADMIN_SECRET, AUTO_MIGRATE, PORT } from '@shared/config' +import getJwks from './routes/auth/jwks' + +const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)) + +const waitFor = async (path: string, attempts = 240): Promise => { + if (attempts > 0) { + try { + if ((await fetch(path)).status !== 200) { + await sleep(1000) + await waitFor(path, attempts--) + } + } catch (error) { + await sleep(1000) + await waitFor(path, attempts--) + } + } else throw Error(`Unable to reach ${path}`) +} + +export default async (): Promise => { + if (AUTO_MIGRATE) { + console.log('Applying migrations...') + await new Promise((resolve, reject) => { + const app = express() + app.use(helmet()) + app.use(cors()) + app.get('/auth/jwks', getJwks) + /** + * ! See: https://github.com/hasura/graphql-engine/issues/3636 + * ! When Hasura is set to use jwk_url with HBP, it needs to get the JWKS from HBP to start. + * ! As we need Hasura to be up to run the migrations, we provide a temporary server with only the JWKS endpoint. + */ + try { + const server = app.listen(PORT, async () => { + const { protocol, host } = url.parse(HASURA_ENDPOINT) + const hasuraURL = `${protocol}//${host}` + // * Wait for GraphQL Engine to be ready + console.log('Waiting for Hasura to be ready...') + await waitFor(`${hasuraURL}/healthz`) + // * Set the Hasura CLI config.yaml file + writeFileSync( + 'config.yaml', + `endpoint: ${hasuraURL}\nadmin_secret: ${HASURA_GRAPHQL_ADMIN_SECRET}\n`, + { encoding: 'utf8' } + ) + // * Apply migrations + const { error, stdout } = spawnSync('./node_modules/.bin/hasura', [ + 'migrate', + 'apply', + '--skip-update-check' + ]) + if (error) reject(error) + console.log(stdout.toString()) + server.close() + }) + server.on('close', () => resolve()) + } catch (err) { + reject(err) + } + }) + } +} diff --git a/src/routes/auth/auth.test.ts b/src/routes/auth/auth.test.ts index 65d5bcf..8226be3 100644 --- a/src/routes/auth/auth.test.ts +++ b/src/routes/auth/auth.test.ts @@ -8,14 +8,15 @@ import { HIBP_ENABLE, SMTP_ENABLE, REDIRECT_URL_ERROR, - JWT_CLAIMS_NAMESPACE + JWT_CLAIMS_NAMESPACE, + PORT } from '@shared/config' import { generateRandomString, selectAccountByEmail } from '@shared/helpers' -import { deleteMailHogEmail, mailHogSearch } from '@shared/test-utils' +import { deleteMailHogEmail, mailHogSearch } from '@test/test-utils' import { JWT } from 'jose' import { Token } from '@shared/types' -import { server } from '../../start' +import { app } from '../../server' import request from 'supertest' /** @@ -32,11 +33,11 @@ const password = generateRandomString() /** * Create agent for global state. */ +const server = app.listen(PORT) const agent = request(server) // * Code that is executed after any jest test file that imports test-utiles - afterAll(async () => { - await server.close() + server.close() }) it('should create an account', async () => { diff --git a/src/routes/auth/change-email/email.test.ts b/src/routes/auth/change-email/email.test.ts index 78145fa..bb35995 100644 --- a/src/routes/auth/change-email/email.test.ts +++ b/src/routes/auth/change-email/email.test.ts @@ -1,9 +1,9 @@ import 'jest-extended' import { generateRandomString } from '@shared/helpers' -import { account, request } from '@shared/test-mock-account' +import { account, request } from '@test/test-mock-account' -import { mailHogSearch, deleteMailHogEmail } from '@shared/test-utils' +import { mailHogSearch, deleteMailHogEmail } from '@test/test-utils' import { JWT } from 'jose' import { Token } from '@shared/types' import { JWT_CLAIMS_NAMESPACE, NOTIFY_EMAIL_CHANGE, SMTP_ENABLE } from '@shared/config' @@ -51,9 +51,7 @@ it('should reconnect using the new email', async () => { }) it('should receive an email notifying the email account has been changed', async () => { - console.log(SMTP_ENABLE, NOTIFY_EMAIL_CHANGE) if (SMTP_ENABLE && NOTIFY_EMAIL_CHANGE) { - console.log('HERE') const [message] = await mailHogSearch(account.email) expect(message).toBeTruthy() expect(message.Content.Headers.Subject).toInclude( diff --git a/src/routes/auth/change-password/password.test.ts b/src/routes/auth/change-password/password.test.ts index b596489..84abc5c 100644 --- a/src/routes/auth/change-password/password.test.ts +++ b/src/routes/auth/change-password/password.test.ts @@ -1,9 +1,8 @@ import 'jest-extended' import { generateRandomString } from '@shared/helpers' -import { account, request } from '@shared/test-mock-account' - -import { mailHogSearch, deleteMailHogEmail } from '@shared/test-utils' +import { account, request } from '@test/test-mock-account' +import { mailHogSearch, deleteMailHogEmail } from '@test/test-utils' let ticket: string diff --git a/src/routes/auth/mfa/mfa.test.ts b/src/routes/auth/mfa/mfa.test.ts index eb9164f..015aa6f 100644 --- a/src/routes/auth/mfa/mfa.test.ts +++ b/src/routes/auth/mfa/mfa.test.ts @@ -1,6 +1,6 @@ import 'jest-extended' -import { account, request } from '@shared/test-mock-account' +import { account, request } from '@test/test-mock-account' import { authenticator } from 'otplib' diff --git a/src/routes/auth/providers/providers.test.ts b/src/routes/auth/providers/providers.test.ts index ca7086c..8e3d608 100644 --- a/src/routes/auth/providers/providers.test.ts +++ b/src/routes/auth/providers/providers.test.ts @@ -1,10 +1,9 @@ /* eslint-disable jest/no-standalone-expect */ import 'jest-extended' -// import { initAgent } from '@shared/test-utils' -import { SERVER_URL, PROVIDER_SUCCESS_REDIRECT, PROVIDERS } from '@shared/config' +import { SERVER_URL, PROVIDER_SUCCESS_REDIRECT, PROVIDERS, PORT } from '@shared/config' import { agent } from 'supertest' -import { server } from '../../../start' -import { itif } from '@shared/test-utils' +import { app } from '../../../server' +import { itif } from '@test/test-utils' // it('Oauth routes should not exist when disabled', async () => { // const tempAgent = initAgent({ PROVIDERS: {} }) @@ -21,11 +20,11 @@ import { itif } from '@shared/test-utils' // }) // TODO test functions in ./utils.ts -agent(server) // * Create the SuperTest agent - +const server = app.listen(PORT) +agent(server) // * Code that is executed after any jest test file that imports test-utiles afterAll(async () => { - await server.close() + server.close() }) itif( diff --git a/src/routes/auth/token/token.test.ts b/src/routes/auth/token/token.test.ts index a7e2b73..dc249be 100644 --- a/src/routes/auth/token/token.test.ts +++ b/src/routes/auth/token/token.test.ts @@ -1,6 +1,6 @@ import 'jest-extended' -import { account, request } from '@shared/test-mock-account' +import { account, request } from '@test/test-mock-account' it('should refresh the token', async () => { const { body, status } = await request.post('/auth/token/refresh') diff --git a/src/routes/storage/storage.test.ts b/src/routes/storage/storage.test.ts index 9af98f6..7817210 100644 --- a/src/routes/storage/storage.test.ts +++ b/src/routes/storage/storage.test.ts @@ -1,6 +1,6 @@ import 'jest-extended' -import { account, getUserId, request } from '@shared/test-mock-account' +import { account, getUserId, request } from '@test/test-mock-account' import fs from 'fs' import { promisify } from 'util' diff --git a/src/shared/config/application.ts b/src/shared/config/application.ts index c6fc770..152fb04 100644 --- a/src/shared/config/application.ts +++ b/src/shared/config/application.ts @@ -11,7 +11,7 @@ export const { } = process.env export const PORT = castIntEnv('PORT', 3000) export const HASURA_ENDPOINT = process.env.HASURA_ENDPOINT as string - +export const AUTO_MIGRATE = castBooleanEnv('AUTO_MIGRATE', true) /** * * Rate limiter settings */ diff --git a/src/start.ts b/src/start.ts index e722c88..dfa7005 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,6 +1,5 @@ import { PORT } from '@shared/config' import { app } from './server' +import migrate from './migrate' -export const server = app.listen(PORT, () => { - console.log(`Running on http://localhost:${PORT}`) -}) +migrate().then(() => app.listen(PORT, () => console.log(`Running on http://localhost:${PORT}`))) diff --git a/src/test/getPuppeteer.ts b/src/test/getPuppeteer.ts new file mode 100644 index 0000000..e5694ff --- /dev/null +++ b/src/test/getPuppeteer.ts @@ -0,0 +1,8 @@ +require('tsconfig-paths/register') +import puppeteer, { PuppeteerExtra } from 'puppeteer-extra' +import StealthPlugin from 'puppeteer-extra-plugin-stealth' + +export const getPuppeteer = (): PuppeteerExtra => { + puppeteer.use(StealthPlugin()) + return puppeteer +} diff --git a/jest/puppeteer.config.js b/src/test/puppeteer.config.js similarity index 100% rename from jest/puppeteer.config.js rename to src/test/puppeteer.config.js diff --git a/src/test/puppeteer.config.json b/src/test/puppeteer.config.json new file mode 100644 index 0000000..c7185c7 --- /dev/null +++ b/src/test/puppeteer.config.json @@ -0,0 +1,10 @@ +{ + "launch": { + "headless": true, + "executablePath": "google-chrome-unstable", + "args": [ + "--no-sandbox", + "--disable-setuid-sandbox" + ] + } +} \ No newline at end of file diff --git a/jest/puppeteer_environment.js b/src/test/puppeteer_environment.js similarity index 98% rename from jest/puppeteer_environment.js rename to src/test/puppeteer_environment.js index 5ced5f6..5e08d59 100644 --- a/jest/puppeteer_environment.js +++ b/src/test/puppeteer_environment.js @@ -3,7 +3,7 @@ const NodeEnvironment = require('jest-environment-node') const chalk = require('chalk') const { getPuppeteer } = require('./getPuppeteer') -const config = require('./puppeteer.config.js') +const config = require('./puppeteer.config.json') const handleError = (error) => { process.emit('uncaughtException', error) diff --git a/jest/setup.js b/src/test/setup.ts similarity index 70% rename from jest/setup.js rename to src/test/setup.ts index c19d056..5fc9ad6 100644 --- a/jest/setup.js +++ b/src/test/setup.ts @@ -1,12 +1,20 @@ -// setup.js -const config = require('./puppeteer.config.js') -const { getPuppeteer } = require('./getPuppeteer.js') +require('tsconfig-paths/register') +import config from './puppeteer.config.json' +import { getPuppeteer } from './getPuppeteer' let didAlreadyRunInWatchMode = false let browser +import { Config } from '@jest/types' +import migrate from '../migrate' -module.exports = async function (jestConfig = {}) { +export default async (jestConfig: Config.InitialOptions = {}): Promise => { + console.log() + await migrate() const puppeteer = getPuppeteer() - browser = await puppeteer.launch(config.launch) + try { + browser = await puppeteer.launch(config.launch) + } catch { + browser = await puppeteer.launch({ ...config.launch, executablePath: undefined }) + } process.env.PUPPETEER_WS_ENDPOINT = browser.wsEndpoint() // If we are in watch mode, - only setupServer() once. diff --git a/src/test/teardown.ts b/src/test/teardown.ts new file mode 100644 index 0000000..fbd5afe --- /dev/null +++ b/src/test/teardown.ts @@ -0,0 +1,10 @@ +require('tsconfig-paths/register') +// const { teardown: teardownServer } = require('jest-dev-server') +import { Config } from '@jest/types' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default async (_jestConfig: Config.InitialOptions = {}): Promise => { + // if (!jestConfig.watch && !jestConfig.watchAll) { + // await teardownServer() + // } +} diff --git a/src/shared/test-mock-account.ts b/src/test/test-mock-account.ts similarity index 83% rename from src/shared/test-mock-account.ts rename to src/test/test-mock-account.ts index db9dc06..130e1b0 100644 --- a/src/shared/test-mock-account.ts +++ b/src/test/test-mock-account.ts @@ -1,16 +1,17 @@ import { SuperTest, Test, agent } from 'supertest' -import { TestAccount, createAccount, deleteEmailsOfAccount } from '@shared/test-utils' +import { TestAccount, createAccount, deleteEmailsOfAccount } from '@test/test-utils' -import { AUTO_ACTIVATE_NEW_USERS } from '@shared/config' -import { getClaims } from './jwt' -import { server } from '../start' -import { selectAccountByEmail } from './helpers' +import { AUTO_ACTIVATE_NEW_USERS, PORT } from '@shared/config' +import { getClaims } from '../shared/jwt' +import { app } from '../server' +import { selectAccountByEmail } from '../shared/helpers' export let request: SuperTest export let account: TestAccount export const getUserId = (): string => getClaims(account.token)['x-hasura-user-id'] +const server = app.listen(PORT) // * Code that is executed before any jest test file that imports this file beforeAll(async () => { @@ -40,5 +41,5 @@ afterAll(async () => { await request.post('/auth/account/delete').set('Authorization', `Bearer ${account.token}`) // * Remove any message sent to this account await deleteEmailsOfAccount(account.email) - await server.close() + server.close() }) diff --git a/src/shared/test-utils.ts b/src/test/test-utils.ts similarity index 100% rename from src/shared/test-utils.ts rename to src/test/test-utils.ts diff --git a/types/@nicokaiser/passport-apple.d.ts b/src/types/@nicokaiser/passport-apple.d.ts similarity index 100% rename from types/@nicokaiser/passport-apple.d.ts rename to src/types/@nicokaiser/passport-apple.d.ts diff --git a/types/notevil.d.ts b/src/types/notevil.d.ts similarity index 100% rename from types/notevil.d.ts rename to src/types/notevil.d.ts diff --git a/types/passport-generic-oauth.d.ts b/src/types/passport-generic-oauth.d.ts similarity index 100% rename from types/passport-generic-oauth.d.ts rename to src/types/passport-generic-oauth.d.ts diff --git a/types/passport-windowslive.d.ts b/src/types/passport-windowslive.d.ts similarity index 100% rename from types/passport-windowslive.d.ts rename to src/types/passport-windowslive.d.ts diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..caa85c4 --- /dev/null +++ b/start.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# source bash-utils.sh + +function help() { + echo "Usage:" + printf "\t${0} [flags]\n" + echo "Flags:" + printf "\t-h, --help\t\tShow help\n" + printf "\t-b, --build\t\tForce build the docker file\n" + printf "\t-r, --remove-volumes\t\Remove all volumes after shuting down\n" + echo "Commands:" + printf "\tdev\t Development without runnig any test\n" + printf "\twatch\t Development with Jest watching\n" + printf "\ttest\t Run Jest tests\n" + exit +} + +# * Get script arguments +while :; do + case $1 in + -h|-\?|--help) + help $0 + ;; + -b|--build) + build="yes" + ;; + -r|--remove-volumes) + remove="-v" + ;; + --) + shift + break + ;; + -?*) + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + ;; + test) + mode="test" + remove='-v' + ;; + dev) + mode="development" + ;; + watch) + mode="test" + COMMAND="yarn run test:watch" + ;; + *) + break + esac + shift +done + +# * If not mode has been given in the CLI, print help and exit +if [ -z "$mode" ]; then + help $0 +fi + +# * Load variables from .env file so it is used anywhere in the docker-compose files +if [ -f .env.$mode ] +then + export $(cat .env.$mode | sed 's/#.*//g' | xargs) +fi + +await_console() { + if [ "$mode" != "test" ]; then + # Wait for Hasura Graphql Engine" before starting the console + echo "Waiting for Hasura Graphql Engine to be ready..." + until $(curl -X GET --output /dev/null --silent --head --fail http://localhost:8080/healthz); do + sleep 1 + done + # Set the Hasura config.yaml file + printf 'endpoint: http://localhost:8080\nadmin_secret: %s\n' $HASURA_GRAPHQL_ADMIN_SECRET > config.yaml + hasura console + fi +} + +echo "Running mode '$mode'..." + +trap clean_exit INT +clean_exit() { + echo "Cleaning up..." + if [ "$mode" != "test" ]; then # Kill Hasura Console + ps -ef | grep 'hasura console' | grep -v grep | awk '{print $2}' | xargs kill -9 + fi + # Stop all docker services + docker-compose -p "hbp_${mode}" down $remove --remove-orphans + exit +} + +# * Build the docker images first, if the build option has been passed on +if [ -n "$build" ]; then + docker-compose -p "hbp_$mode" -f docker-compose.yaml -f docker-compose.$mode.yaml build +fi + +# * Start on background in waiting for Hasura to be ready so the console can be launched +await_console & + +# * Start docker services +docker-compose -p "hbp_$mode" -f docker-compose.yaml -f docker-compose.$mode.yaml run --service-ports --use-aliases hasura-backend-plus $COMMAND + +clean_exit \ No newline at end of file diff --git a/start_dev.sh b/start_dev.sh deleted file mode 100755 index 90705c3..0000000 --- a/start_dev.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -source bash-utils.sh -function help() { - echo "Usage:" - printf "\t${0} [flags]\n" - echo "Flags:" - printf "\t-h, --help\t\tShow help\n" - printf "\t-b, --build\t\tForce build the docker file\n" - echo "Commands:" - printf "\tdev\t Development without runnig any test\n" - printf "\twatch\t Development with Jest watching\n" - printf "\ttest\t Run Jest tests\n" -} - -export NODE_ENV="test" -while :; do - case $1 in - -h|-\?|--help) - help $0 - exit - ;; - -b|--build) - build="--build" - ;; - --) - shift - break - ;; - -?*) - printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 - ;; - test) - mode="test" - export COMMAND_LINE="yarn run test" - ;; - dev) - mode="dev" - export NODE_ENV="development" - ;; - watch) - mode="watch" - # Use another internal port (4000) to run the dev server so Puppeteer Jest can use the default port (3000) in the local docker context - export PORT=4000 - ;; - *) - break - esac - shift -done - -if [ -n "$mode" ]; then - echo "Running mode '$mode'..." -else - help $0 - exit -fi - -if [ ! -f .env.dockerdev ]; then - echo "File .env.dockerdev not found! Creating an empty file." - echo "Please set the secrets as per described in .env.dockerdev.example. Otherwise some tests could be skipped." - touch .env.dockerdev -fi - -# Fetch variables from .env.test so it is used in the docker-compose files -export-dotenv ".env.$NODE_ENV" HASURA_GRAPHQL_ADMIN_SECRET -export-dotenv ".env.$NODE_ENV" JWT_KEY -# Load the variables required for the Minio service -export-dotenv ".env.$NODE_ENV" S3_SECRET_ACCESS_KEY -export-dotenv ".env.$NODE_ENV" S3_ACCESS_KEY_ID - -# Start docker services -docker-compose -p "hbp_${mode}" -f docker-compose.yaml -f docker-compose.dev.yaml up -d $build - -trap exit_script INT -function exit_script() { - echo "Cleaning up..." - if [ "$mode" != "test" ]; then - # Kill Hasura Console - ps -ef | grep 'hasura console' | grep -v grep | awk '{print $2}' | xargs kill -9 - fi - # Stop and remove all docker images, volumes and networks - docker-compose -p "hbp_${mode}" down -v --remove-orphans - exit -} - -if [ "$mode" != "test" ]; then - # NOTE: The Hasura console should accessed from the CLI so the migration files can be automatically generated - # Waith for Hasura Graphql Engine" before starting the console - wait-for http://localhost:8080/healthz "Hasura Graphql Engine" - # Set the Hasura config.yaml file - printf 'endpoint: http://localhost:8080\nHASURA_GRAPHQL_ADMIN_SECRET: %s\n' $HASURA_GRAPHQL_ADMIN_SECRET > config.yaml - hasura console & -fi - -if [ "$mode" == "watch" ]; then - # Run Jest on watch mode - docker exec -it -e PORT=3000 -e NODE_ENV=test "hbp_${mode}_hasura-backend-plus_1" yarn test:watch -else - docker container logs -f "hbp_${mode}_hasura-backend-plus_1" -fi -exit_script diff --git a/mock-config/migrations/1585679214182_custom_user_column/down.yaml b/test-mocks/migrations/1585679214182_custom_user_column/down.yaml similarity index 100% rename from mock-config/migrations/1585679214182_custom_user_column/down.yaml rename to test-mocks/migrations/1585679214182_custom_user_column/down.yaml diff --git a/mock-config/migrations/1585679214182_custom_user_column/up.yaml b/test-mocks/migrations/1585679214182_custom_user_column/up.yaml similarity index 100% rename from mock-config/migrations/1585679214182_custom_user_column/up.yaml rename to test-mocks/migrations/1585679214182_custom_user_column/up.yaml diff --git a/tsconfig.json b/tsconfig.json index 8a1f385..594bbb7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,9 @@ "compilerOptions": { "lib": ["dom", "es2020"], "paths": { - "*": ["types/*"], - "@shared/*": ["src/shared/*"] + "*": ["src/types/*"], + "@shared/*": ["src/shared/*"], + "@test/*": ["src/test/*"] }, "module": "commonjs", "outDir": "dist", @@ -15,6 +16,7 @@ "noUnusedLocals": true, "esModuleInterop": true, "moduleResolution": "node", - "strictNullChecks": true + "strictNullChecks": true, + "resolveJsonModule": true } } diff --git a/yarn.lock b/yarn.lock index 6ffc1b9..d827ba0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2323,6 +2323,12 @@ aws4@^1.8.0: version "1.9.1" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + dependencies: + follow-redirects "1.5.10" + babel-jest@^25.3.0: version "25.3.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.3.0.tgz#999d0c19e8427f66b796bf9ea233eedf087b957c" @@ -3795,18 +3801,18 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" +debug@=3.1.0, debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" dependencies: ms "^2.1.1" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - decamelize@^1.0.0, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4889,6 +4895,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + dependencies: + debug "=3.1.0" + follow-redirects@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" @@ -5335,6 +5347,13 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasura-cli@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.1.1.tgz#d1c89e4232641f3b0f51019072d4d1042568dea4" + dependencies: + axios "^0.19.0" + chalk "^2.4.2" + he@1.2.0, he@1.2.x, he@^1.1.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"