mirror of
https://github.com/zhigang1992/hasura-backend-plus.git
synced 2026-01-12 22:47:51 +08:00
Clean docker and migrate (#189)
* feat: autoload migrations and simplify start script no need to get the migrations from the migration files from the repo and to mount a specific volume to the Hasura GE service. By default, when HBP starts, it checks the migrations and installs it if required. This can be disabled by setting AUTO_MIGRATE to false. This system could be extended to set AUTO_MIGRATE=v1 so it runs another set of migrations that would transform schema and data from HBP v1 to HBP v2 * fix: include mock migrations used for testing * fix: run migrations in a separate node script * refactor: better general repo structure, and migration that works * ci: change test mock migration folder in GH actions
This commit is contained in:
@@ -16,3 +16,4 @@ jest*
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
**/*.test.ts
|
||||
src/test
|
||||
|
||||
@@ -44,4 +44,3 @@ SMTP_PASS=password
|
||||
SMTP_USER=user
|
||||
SMTP_SECURE=false
|
||||
SMTP_SENDER=hbp@hbp.com
|
||||
NOTIFY_EMAIL_CHANGE=true
|
||||
@@ -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
|
||||
|
||||
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@@ -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)"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
dist
|
||||
.env*
|
||||
!.env.test
|
||||
!.env.ci
|
||||
!.env*.example
|
||||
*.log
|
||||
hasura
|
||||
|
||||
@@ -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"]
|
||||
|
||||
27
Dockerfile.test
Normal file
27
Dockerfile.test
Normal file
@@ -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"]
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
export-dotenv() { # export-env <dotenv-file> <var-name> <original-var-name(optional)>
|
||||
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 <url> <service-name(optional)>
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
25
docker-compose.test.yaml
Normal file
25
docker-compose.test.yaml
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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/(.*)$': '<rootDir>/src/shared/$1'
|
||||
'^@shared/(.*)$': '<rootDir>/src/shared/$1',
|
||||
'^@test/(.*)$': '<rootDir>/src/test/$1'
|
||||
},
|
||||
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/node_modules/'],
|
||||
setupFilesAfterEnv: ['expect-puppeteer', 'jest-extended'],
|
||||
|
||||
@@ -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
|
||||
@@ -1,7 +0,0 @@
|
||||
// const { teardown: teardownServer } = require('jest-dev-server')
|
||||
|
||||
module.exports = async function teardown(jestConfig = {}) {
|
||||
// if (!jestConfig.watch && !jestConfig.watchAll) {
|
||||
// await teardownServer()
|
||||
// }
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
70
src/migrate.ts
Normal file
70
src/migrate.ts
Normal file
@@ -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<void> => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const waitFor = async (path: string, attempts = 240): Promise<void> => {
|
||||
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<void> => {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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}`)))
|
||||
|
||||
8
src/test/getPuppeteer.ts
Normal file
8
src/test/getPuppeteer.ts
Normal file
@@ -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
|
||||
}
|
||||
10
src/test/puppeteer.config.json
Normal file
10
src/test/puppeteer.config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"launch": {
|
||||
"headless": true,
|
||||
"executablePath": "google-chrome-unstable",
|
||||
"args": [
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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<void> => {
|
||||
console.log()
|
||||
await migrate()
|
||||
const puppeteer = getPuppeteer()
|
||||
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.
|
||||
10
src/test/teardown.ts
Normal file
10
src/test/teardown.ts
Normal file
@@ -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<void> => {
|
||||
// if (!jestConfig.watch && !jestConfig.watchAll) {
|
||||
// await teardownServer()
|
||||
// }
|
||||
}
|
||||
@@ -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<Test>
|
||||
|
||||
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()
|
||||
})
|
||||
102
start.sh
Executable file
102
start.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# source bash-utils.sh
|
||||
|
||||
function help() {
|
||||
echo "Usage:"
|
||||
printf "\t${0} <command> [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
|
||||
101
start_dev.sh
101
start_dev.sh
@@ -1,101 +0,0 @@
|
||||
#!/bin/bash
|
||||
source bash-utils.sh
|
||||
function help() {
|
||||
echo "Usage:"
|
||||
printf "\t${0} <command> [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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
31
yarn.lock
31
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"
|
||||
|
||||
Reference in New Issue
Block a user