mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
ci: init integration test (#771)
* ci: init integration test * ci: complete integration test setup * ci: remove pull request trigger
This commit is contained in:
parent
92039f948b
commit
1a664beeee
9 changed files with 114 additions and 72 deletions
77
.github/workflows/integration-test.yml
vendored
Normal file
77
.github/workflows/integration-test.yml
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
name: Integration Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: logto-io/actions-node-pnpm-run-steps@v1.2.1
|
||||
|
||||
- name: Package
|
||||
run: ./package.sh
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: integration-test-${{ github.sha }}
|
||||
path: /tmp/logto.tar.gz
|
||||
retention-days: 3
|
||||
|
||||
run-logto:
|
||||
needs: package
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-18.04, macos-11, macos-12]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Start Postgres (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo service postgresql start
|
||||
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';"
|
||||
|
||||
- name: Start Postgres (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
pg_ctl -D /usr/local/var/postgres start
|
||||
psql postgres -c "CREATE USER postgres WITH SUPERUSER PASSWORD 'postgres';"
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: integration-test-${{ github.sha }}
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: Extract
|
||||
run: tar -xzf logto.tar.gz
|
||||
|
||||
- name: Rebuild Argon2
|
||||
run: npx node-pre-gyp rebuild -C .
|
||||
working-directory: logto/packages/core/node_modules/argon2
|
||||
|
||||
- name: Run Logto
|
||||
run: node . --from-root --all-yes &
|
||||
working-directory: logto/packages/core
|
||||
env:
|
||||
NODE_ENV: production
|
||||
DB_URL_DEFAULT: postgres://postgres:postgres@localhost:5432
|
||||
|
||||
- name: Sleep for 5 seconds
|
||||
run: sleep 5
|
||||
|
||||
- name: Health check
|
||||
run: curl http://localhost:3001/api/status -If
|
|
@ -54,13 +54,20 @@ const postgresMajorVersion = 14;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Download and extract
|
||||
spawnSync(
|
||||
'sh',
|
||||
['-c', 'curl -L https://github.com/logto-io/logto/releases/latest/download/logto.tar.gz | tar -xz'],
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
|
||||
// Rebuild Argon2
|
||||
spawnSync(
|
||||
'sh',
|
||||
['-c', 'npx node-pre-gyp rebuild -C .'],
|
||||
{ stdio: 'inherit', cwd: './logto/packages/core/node_modules/argon2' },
|
||||
);
|
||||
|
||||
const startCommand = `cd ${directory} && npm start`;
|
||||
const answer = await confirm('Would you like to start Logto now?');
|
||||
|
||||
|
|
|
@ -2,16 +2,20 @@ import { readdir, readFile } from 'fs/promises';
|
|||
import path from 'path';
|
||||
|
||||
import { SchemaLike, seeds } from '@logto/schemas';
|
||||
import { conditionalString } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import decamelize from 'decamelize';
|
||||
import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
|
||||
import { createInterceptors } from 'slonik-interceptor-preset';
|
||||
import { raw } from 'slonik-sql-tag-raw';
|
||||
|
||||
import { fromRoot } from '@/env-set/parameters';
|
||||
|
||||
import { convertToPrimitiveOrSql } from './utils';
|
||||
|
||||
const { managementResource, defaultSignInExperience, createDefaultSetting } = seeds;
|
||||
const tableDirectory = 'node_modules/@logto/schemas/tables';
|
||||
const tableDirectory =
|
||||
conditionalString(fromRoot && 'packages/core/') + 'node_modules/@logto/schemas/tables';
|
||||
|
||||
export const replaceDsnDatabase = (dsn: string, databaseName: string): string =>
|
||||
stringifyDsn({ ...parseDsn(dsn), databaseName });
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { assertEnv, conditional, conditionalString, Optional } from '@silverhand/essentials';
|
||||
import {
|
||||
assertEnv,
|
||||
conditional,
|
||||
conditionalString,
|
||||
getEnv,
|
||||
Optional,
|
||||
} from '@silverhand/essentials';
|
||||
import inquirer from 'inquirer';
|
||||
import { createPool } from 'slonik';
|
||||
import { createInterceptors } from 'slonik-interceptor-preset';
|
||||
|
@ -6,12 +12,16 @@ import { createInterceptors } from 'slonik-interceptor-preset';
|
|||
import { createDatabase, createDatabaseCli } from '@/database/seed';
|
||||
|
||||
import { appendDotEnv } from './dot-env';
|
||||
import { noInquiry } from './parameters';
|
||||
import { allYes, noInquiry } from './parameters';
|
||||
|
||||
const defaultDatabaseUrl = 'postgres://localhost:5432';
|
||||
const defaultDatabaseUrl = getEnv('DB_URL_DEFAULT', 'postgres://@localhost:5432');
|
||||
const defaultDatabaseName = 'logto';
|
||||
|
||||
const inquireForLogtoDsn = async (key: string): Promise<[Optional<string>, boolean]> => {
|
||||
if (allYes) {
|
||||
return [await createDatabase(defaultDatabaseUrl, defaultDatabaseName), true];
|
||||
}
|
||||
|
||||
const setUp = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'value',
|
||||
|
|
|
@ -3,7 +3,6 @@ import { DatabasePool } from 'slonik';
|
|||
|
||||
import createPoolByEnv from './create-pool-by-env';
|
||||
import loadOidcValues from './oidc';
|
||||
import loadPasswordValues from './password';
|
||||
|
||||
export enum MountedApps {
|
||||
Api = 'api',
|
||||
|
@ -25,7 +24,6 @@ const loadEnvValues = async () => {
|
|||
port,
|
||||
developmentUserId: getEnv('DEVELOPMENT_USER_ID'),
|
||||
trustProxyHeader: getEnv('TRUST_PROXY_HEADER') === 'true',
|
||||
password: await loadPasswordValues(isTest),
|
||||
oidc: await loadOidcValues(port),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { readFileSync, writeFileSync } from 'fs';
|
|||
import { getEnv } from '@silverhand/essentials';
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
import { noInquiry } from './parameters';
|
||||
import { allYes, noInquiry } from './parameters';
|
||||
|
||||
/**
|
||||
* Try to read private key with the following order:
|
||||
|
@ -33,6 +33,7 @@ const readPrivateKey = async (): Promise<string> => {
|
|||
throw error;
|
||||
}
|
||||
|
||||
if (!allYes) {
|
||||
const answer = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
|
@ -42,6 +43,7 @@ const readPrivateKey = async (): Promise<string> => {
|
|||
if (!answer.confirm) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const { privateKey } = generateKeyPairSync('rsa', {
|
||||
modulusLength: 4096,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const parameters = new Set(process.argv.slice(2));
|
||||
export const noInquiry = parameters.has('--no-inquiry');
|
||||
export const fromRoot = parameters.has('--from-root');
|
||||
export const allYes = parameters.has('--all-yes');
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
import { assertEnv, getEnv } from '@silverhand/essentials';
|
||||
import inquirer from 'inquirer';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { number, string } from 'zod';
|
||||
|
||||
import { appendDotEnv } from './dot-env';
|
||||
import { noInquiry } from './parameters';
|
||||
|
||||
const loadPeppers = async (isTest: boolean): Promise<string[]> => {
|
||||
if (isTest) {
|
||||
return [nanoid()];
|
||||
}
|
||||
|
||||
const key = 'PASSWORD_PEPPERS';
|
||||
|
||||
try {
|
||||
return string()
|
||||
.array()
|
||||
.parse(JSON.parse(assertEnv(key)));
|
||||
} catch (error: unknown) {
|
||||
if (noInquiry) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!(error instanceof Error && error.message === `env variable ${key} not found`)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const answer = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `No password peppers (${key}) found in env variables, would you like to generate a new set and save it into \`.env\`?`,
|
||||
});
|
||||
|
||||
if (!answer.confirm) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const peppers = [nanoid(), nanoid(), nanoid()];
|
||||
appendDotEnv(key, JSON.stringify(peppers));
|
||||
|
||||
return peppers;
|
||||
}
|
||||
};
|
||||
|
||||
const loadPasswordValues = async (isTest: boolean) => {
|
||||
return Object.freeze({
|
||||
peppers: await loadPeppers(isTest),
|
||||
iterationCount: number()
|
||||
.min(100)
|
||||
.parse(Number(getEnv('PASSWORD_ITERATION_COUNT', '1000'))),
|
||||
});
|
||||
};
|
||||
|
||||
export default loadPasswordValues;
|
|
@ -51,7 +51,6 @@ describe('koaSpaProxy middleware', () => {
|
|||
const spy = jest.spyOn(envSet, 'values', 'get').mockReturnValue({
|
||||
...envSet.values,
|
||||
isProduction: true,
|
||||
password: { peppers: ['foo'], iterationCount: 1000 },
|
||||
});
|
||||
|
||||
const ctx = createContextWithRouteParameters({
|
||||
|
@ -69,7 +68,6 @@ describe('koaSpaProxy middleware', () => {
|
|||
const spy = jest.spyOn(envSet, 'values', 'get').mockReturnValue({
|
||||
...envSet.values,
|
||||
isProduction: true,
|
||||
password: { peppers: ['foo'], iterationCount: 1000 },
|
||||
});
|
||||
|
||||
const ctx = createContextWithRouteParameters({
|
||||
|
|
Loading…
Reference in a new issue