Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
name: Test

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
NODE_ENV: test
SERVER_MODE: standalone
DATABASE_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
POSTGRES_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
REDIS_URL: redis://localhost:6379/0
Comment on lines +17 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Environment variable inconsistency: POSTGRES_URL is defined but migration steps use DATABASE_URL

Suggested change
POSTGRES_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
REDIS_URL: redis://localhost:6379/0
DATABASE_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
REDIS_URL: redis://localhost:6379/0
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/test.yml
Line: 15:16

Comment:
**logic:** Environment variable inconsistency: `POSTGRES_URL` is defined but migration steps use `DATABASE_URL`

```suggestion
  DATABASE_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
  REDIS_URL: redis://localhost:6379/0
```

How can I resolve this? If you propose a fix, please make it concise.

STORAGE_S3_ENDPOINT: http://localhost:9000
STORAGE_S3_ACCESS_KEY: minioadmin
STORAGE_S3_SECRET_KEY: minioadmin
STORAGE_S3_BUCKET: brainbox
STORAGE_S3_REGION: us-east-1
JWT_SECRET: test-jwt-secret
ACCOUNT_VERIFICATION_TYPE: automatic

jobs:
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint

- name: Type check
run: npm run compile

unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
needs: lint
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run unit tests
run: npm run test -- --reporter=verbose
working-directory: apps/server

integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: brainbox
POSTGRES_PASSWORD: brainbox
POSTGRES_DB: brainbox
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Start MinIO
run: |
docker run -d --name minio \
-p 9000:9000 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data
sleep 5

- name: Create MinIO bucket
run: |
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
./mc alias set myminio http://localhost:9000 minioadmin minioadmin
./mc mb myminio/brainbox || true

- name: Run integration tests
run: npm run test -- --reporter=verbose
working-directory: apps/server

e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: brainbox
POSTGRES_PASSWORD: brainbox
POSTGRES_DB: brainbox
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Start MinIO
run: |
docker run -d --name minio \
-p 9000:9000 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data
sleep 5

- name: Create MinIO bucket
run: |
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
./mc alias set myminio http://localhost:9000 minioadmin minioadmin
./mc mb myminio/brainbox || true

- name: Build all packages
run: npm run build

- name: Start server
run: node dist/index.js &
working-directory: apps/server

- name: Start web app
run: npx vite preview &
working-directory: apps/web

- name: Wait for services
run: |
sleep 10
npx wait-on http://localhost:3000/config http://localhost:4000 --timeout 120000

- name: Run E2E tests
run: npm run e2e -- --project=chromium
env:
E2E_BASE_URL: http://localhost:4000
CI: true

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7

- name: Upload test screenshots
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-screenshots
path: e2e/screenshots/
retention-days: 7
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,9 @@ apps/web/public/assets/brainbox-logo-black-192.png
apps/web/public/assets/brainbox-logo-black-512.png
nul
.claude/settings.local.json

# Playwright
playwright-report/
test-results/
e2e/screenshots/
blob-report/
6 changes: 5 additions & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@
"build": "npm run compile && tsup-node",
"clean": "del-cli dist isolate tsconfig.tsbuildinfo",
"lint": "eslint . --max-warnings 0",
"test": "vitest",
"dev": "tsx watch --env-file .env src/index.ts"
},
"description": "",
"devDependencies": {
"@testcontainers/postgresql": "^10.18.0",
"@types/node": "^24.2.0",
"@types/nodemailer": "^6.4.17",
"@types/pg": "^8.15.5",
"@types/ws": "^8.18.1",
"nodemon": "^3.1.10",
"testcontainers": "^10.18.0",
"tsup": "^8.5.0",
"tsx": "^4.20.3"
"tsx": "^4.20.3",
"vite-tsconfig-paths": "^6.0.3"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.863.0",
Expand Down
58 changes: 58 additions & 0 deletions apps/server/src/lib/__tests__/accounts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, it, expect } from 'vitest';

import { generatePasswordHash, verifyPassword } from '../accounts';

describe('accounts - password', () => {
describe('generatePasswordHash', () => {
it('should generate argon2 hash', async () => {
const hash = await generatePasswordHash('TestPassword123');
expect(hash).toMatch(/^\$argon2/);
});

it('should generate different hashes for same password', async () => {
const hash1 = await generatePasswordHash('TestPassword123');
const hash2 = await generatePasswordHash('TestPassword123');
expect(hash1).not.toBe(hash2);
});

it('should generate hash with expected format', async () => {
const hash = await generatePasswordHash('TestPassword123');
expect(hash).toMatch(/^\$argon2id\$v=\d+\$m=\d+,t=\d+,p=\d+\$/);
});
});

describe('verifyPassword', () => {
it('should verify correct password', async () => {
const password = 'TestPassword123';
const hash = await generatePasswordHash(password);
const result = await verifyPassword(password, hash);
expect(result).toBe(true);
});

it('should reject incorrect password', async () => {
const hash = await generatePasswordHash('TestPassword123');
const result = await verifyPassword('WrongPassword', hash);
expect(result).toBe(false);
});

it('should reject empty password', async () => {
const hash = await generatePasswordHash('TestPassword123');
const result = await verifyPassword('', hash);
expect(result).toBe(false);
});

it('should handle special characters in password', async () => {
const password = 'Test@Password!123#$%';
const hash = await generatePasswordHash(password);
const result = await verifyPassword(password, hash);
expect(result).toBe(true);
});

it('should handle unicode characters in password', async () => {
const password = 'Test\u00e9\u00e0\u00fc123';
const hash = await generatePasswordHash(password);
const result = await verifyPassword(password, hash);
expect(result).toBe(true);
});
});
});
Loading
Loading