Basic Unit Tests (#15)

* added basic api testing framework

* added credential signup test

* added current user test

* added github action

* fixed bugs in action file

* updated action

* added pnpm setup

* added dependency install

* updated pnpm lock

* only run server tests

* added new package for e2e test

* removed unused tests

* updated action

* updated test command

* added env var reading

* fixed typo

* fixed typo

* fixed unit tests with staging

* added delay e2e test

* added start server to action

* fixed typo

* fix aciton

* updated github action

* fixed bugs

* fixed eslint error
This commit is contained in:
Zai Shi 2024-04-24 14:24:56 +02:00 committed by GitHub
parent 8dae0fd60c
commit 66f6c86ddf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1476 additions and 77 deletions

53
.github/workflows/e2e-api-tests.yaml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Runs E2E API Tests
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
jobs:
build:
runs-on: ubuntu-latest
env:
SERVER_BASE_URL: http://localhost:8101
PROJECT_CLIENT_ID: ${{ secrets.PROJECT_CLIENT_ID }}
PROJECT_CLIENT_KEY: ${{ secrets.PROJECT_CLIENT_KEY }}
NEXT_PUBLIC_STACK_URL: http://localhost:8101
NEXT_PUBLIC_STACK_PROJECT_ID: ${{ secrets.PROJECT_CLIENT_ID }}
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: ${{ secrets.PROJECT_CLIENT_KEY }}
STACK_SECRET_SERVER_KEY: test
SERVER_SECRET: 23-wuNpik0gIW4mruTz25rbIvhuuvZFrLOLtL7J4tyo
EMAIL_HOST: 0.0.0.0
EMAIL_PORT: 2500
EMAIL_SECURE: false
EMAIL_USERNAME: test
EMAIL_PASSWORD: none
EMAIL_SENDER: noreply@test.com
DATABASE_CONNECTION_STRING: ${{ secrets.DATABASE_CONNECTION_STRING }}
DIRECT_DATABASE_CONNECTION_STRING: ${{ secrets.DATABASE_CONNECTION_STRING }}
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install && pnpm install -g wait-on
- name: Build
run: pnpm build:server
- name: Start server & run tests
run: pnpm -C packages/stack-server start & wait-on http://localhost:8101 && pnpm -C apps/e2e test:ci

3
apps/e2e/.env Normal file
View File

@ -0,0 +1,3 @@
PROJECT_CLIENT_ID=internal
PROJECT_CLIENT_KEY=client_key
SERVER_BASE_URL=http://localhost:8101

14
apps/e2e/package.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "e2e-tests",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "dotenv -c -- vitest run",
"test:ci": "vitest run"
},
"dependencies": {},
"devDependencies": {
"dotenv-cli": "7.4.1"
}
}

View File

@ -0,0 +1,74 @@
import { describe, expect, test } from "vitest";
import request from "supertest";
import { BASE_URL, PROJECT_CLIENT_KEY, PROJECT_ID } from "./helpers";
const AUTH_HEADER = {
"x-stack-project-id": PROJECT_ID,
"x-stack-publishable-client-key": PROJECT_CLIENT_KEY,
};
const JSON_HEADER = {
"content-type": "application/json"
}
function randomString() {
return Math.random().toString(36);
}
async function signUpWithEmailPassword() {
const email = randomString() + "@example.com";
const password = randomString();
const response = await request(BASE_URL).post("/api/v1/auth/signup").set(AUTH_HEADER).set(JSON_HEADER).send({
email,
password,
emailVerificationRedirectUrl: 'https://localhost:3000/verify-email',
});
return { email, password, response };
}
async function signInWithEmailPassword(email: string, password: string) {
const response = await request(BASE_URL).post("/api/v1/auth/signin").set(AUTH_HEADER).set(JSON_HEADER).send({
email,
password,
});
return { email, password, response };
}
describe("Basic", () => {
test("Main Page", async () => {
const response = await request(BASE_URL).get("/");
expect(response.status).toBe(307);
});
test("Test API", async () => {
const response = await request(BASE_URL).get("/api/v1");
expect(response.status).toBe(200);
expect(response.text).contains("Stack API")
});
test("Credential Sign Up", async () => {
const { response } = await signUpWithEmailPassword();
expect(response.status).toBe(200);
});
test("Credential Sign In", async () => {
const { email, password } = await signUpWithEmailPassword();
const { response } = await signInWithEmailPassword(email, password);
expect(response.status).toBe(200);
});
test("Get Current User", async () => {
const { email, password, response } = await signUpWithEmailPassword();
await signInWithEmailPassword(email, password);
const response2 = await request(BASE_URL).get("/api/v1/current-user").set({
...AUTH_HEADER,
'authorization': 'StackSession ' + response.body.accessToken,
});
expect(response2.status).toBe(200);
expect(response2.body.primaryEmail).toBe(email)
});
});

11
apps/e2e/tests/helpers.ts Normal file
View File

@ -0,0 +1,11 @@
function getEnvVar(name: string): string {
const value = process.env[name]
if (!value) {
throw new Error(`Missing environment variable: ${name}`)
}
return value
}
export const BASE_URL = getEnvVar("SERVER_BASE_URL")
export const PROJECT_ID = getEnvVar("PROJECT_CLIENT_ID")
export const PROJECT_CLIENT_KEY = getEnvVar("PROJECT_CLIENT_KEY")

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'node',
},
})

View File

@ -22,13 +22,17 @@
"lint": "turbo run lint --no-cache -- --max-warnings=0",
"release": "release",
"peek": "pnpm release --peek",
"changeset": "changeset"
"changeset": "changeset",
"test": "turbo run test"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@testing-library/react": "^15.0.2",
"@types/node": "^20.8.10",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^6.x",
"@typescript-eslint/parser": "^6.x",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "8.30.0",
"eslint-config-next": "^14",
"eslint-config-standard-with-typescript": "^43",
@ -36,17 +40,19 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.31.11",
"jsdom": "^24.0.0",
"rimraf": "^5.0.5",
"supertest": "^6.3.4",
"turbo": "^1.11.3",
"typescript": "5.3.3"
},
"engines": {
"node": "^20.8.0"
"typescript": "5.3.3",
"vitest": "^1.5.0"
},
"packageManager": "pnpm@8.9.2",
"pnpm": {
"overrides": {}
},
"dependencies": {
"engines": {
"npm": ">=10.0.0",
"node": ">=20.0.0"
}
}

View File

@ -2,6 +2,7 @@
"name": "@stackframe/stack-server",
"version": "0.3.3",
"private": true,
"type": "module",
"scripts": {
"clean": "rimraf .next && rimraf node_modules",
"typecheck": "tsc --noEmit",
@ -26,10 +27,6 @@
"prisma": {
"seed": "npm run with-env -- ts-node prisma/seed.ts"
},
"engines": {
"npm": ">=10.0.0",
"node": ">=20.0.0"
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",

View File

@ -6,7 +6,7 @@ import { useEffect } from "react";
export function Confetti() {
useEffect(() => {
confetti.default();
confetti.default()?.catch((e) => console.error(e));
}, []);
return (<></>);

View File

@ -0,0 +1,25 @@
// server.js
import { Server, createServer } from 'http';
import next from 'next';
const app = next({ dev: true });
const handle = app.getRequestHandler();
let serverInstance: Server | null = null;
export function startServer(port: number) {
return app.prepare().then(() => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const server = createServer((req, res) => handle(req, res).catch((err) => console.error(err)));
serverInstance = server.listen(port);
console.log(`> Ready on http://localhost:${port}`);
return server;
});
}
export function stopServer() {
if (serverInstance) {
serverInstance.close();
console.log('> Server stopped');
}
}

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,9 @@
},
"email": {
"cache": false
},
"test": {
"cache": false
}
}
}