test(auth): add unit env tests and E2E sign-up/sign-in flow
Unit tests on @ketopath/auth (added to the Vitest workspace): - readAuthEnv accepts a valid configuration - rejects secret < 32 chars and non-URL BETTER_AUTH_URL - preserves Google credentials when both vars are set Playwright e2e/auth.spec.ts: - happy path: sign-up creates a user, redirects home with welcome message, sign-out clears session, sign-in with the same credentials restores it - error path: invalid credentials surface a role="alert" message - each test uses a unique generated email to avoid DB collisions home.spec.ts: links instead of buttons (CTAs are now next/link). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5f17f95d6d
commit
c27e807c43
6 files changed
+110
-4
No files matched your search
@@ -0,0 +1,47 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
function uniqueEmail(): string {
|
||||||
|
const ts = Date.now();
|
||||||
|
const rand = Math.floor(Math.random() * 1e6);
|
||||||
|
return `e2e-${ts}-${rand}@ketopath.test`;
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('email/password authentication', () => {
|
||||||
|
test('sign-up, sign-out and sign-in restore the session', async ({ page }) => {
|
||||||
|
const email = uniqueEmail();
|
||||||
|
const password = 'Password!123';
|
||||||
|
const name = 'Utente E2E';
|
||||||
|
|
||||||
|
// Sign up
|
||||||
|
await page.goto('/sign-up');
|
||||||
|
await page.getByLabel('Nome').fill(name);
|
||||||
|
await page.getByLabel('Email').fill(email);
|
||||||
|
await page.getByLabel('Password').fill(password);
|
||||||
|
await page.getByRole('button', { name: 'Crea account' }).click();
|
||||||
|
|
||||||
|
await page.waitForURL('/');
|
||||||
|
await expect(page.getByText(`Accesso effettuato come ${name}`)).toBeVisible();
|
||||||
|
|
||||||
|
// Sign out
|
||||||
|
await page.getByRole('button', { name: 'Esci' }).click();
|
||||||
|
await expect(page.getByRole('link', { name: 'Inizia gratis' })).toBeVisible();
|
||||||
|
|
||||||
|
// Sign in with the same credentials
|
||||||
|
await page.goto('/sign-in');
|
||||||
|
await page.getByLabel('Email').fill(email);
|
||||||
|
await page.getByLabel('Password').fill(password);
|
||||||
|
await page.getByRole('button', { name: 'Accedi', exact: true }).click();
|
||||||
|
|
||||||
|
await page.waitForURL('/');
|
||||||
|
await expect(page.getByText(`Accesso effettuato come ${name}`)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows an error message on invalid credentials', async ({ page }) => {
|
||||||
|
await page.goto('/sign-in');
|
||||||
|
await page.getByLabel('Email').fill('non-esiste@ketopath.test');
|
||||||
|
await page.getByLabel('Password').fill('Password!123');
|
||||||
|
await page.getByRole('button', { name: 'Accedi', exact: true }).click();
|
||||||
|
|
||||||
|
await expect(page.getByRole('alert')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,7 +5,7 @@ test('home page renders Italian copy and CTAs', async ({ page }) => {
|
|||||||
|
|
||||||
await expect(page).toHaveTitle(/KetoPath/);
|
await expect(page).toHaveTitle(/KetoPath/);
|
||||||
await expect(page.getByRole('heading', { name: 'KetoPath' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'KetoPath' })).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'Inizia gratis' })).toBeVisible();
|
await expect(page.getByRole('link', { name: 'Inizia gratis' })).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'Scopri come funziona' })).toBeVisible();
|
await expect(page.getByRole('link', { name: 'Scopri come funziona' })).toBeVisible();
|
||||||
await expect(page.getByText(/non sostituiscono il parere di un medico/)).toBeVisible();
|
await expect(page.getByText(/non sostituiscono il parere di un medico/)).toBeVisible();
|
||||||
});
|
});
|
||||||
@@ -11,7 +11,9 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --max-warnings=0"
|
"lint": "eslint src --max-warnings=0",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ketopath/db": "workspace:*",
|
"@ketopath/db": "workspace:*",
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { readAuthEnv } from './env.js';
|
||||||
|
|
||||||
|
const VALID_SECRET = 'a'.repeat(32);
|
||||||
|
|
||||||
|
describe('readAuthEnv', () => {
|
||||||
|
it('accepts a valid configuration', () => {
|
||||||
|
const env = readAuthEnv({
|
||||||
|
BETTER_AUTH_SECRET: VALID_SECRET,
|
||||||
|
BETTER_AUTH_URL: 'http://localhost:3000',
|
||||||
|
} as NodeJS.ProcessEnv);
|
||||||
|
|
||||||
|
expect(env.BETTER_AUTH_SECRET).toBe(VALID_SECRET);
|
||||||
|
expect(env.BETTER_AUTH_URL).toBe('http://localhost:3000');
|
||||||
|
expect(env.GOOGLE_CLIENT_ID).toBeUndefined();
|
||||||
|
expect(env.GOOGLE_CLIENT_SECRET).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a secret shorter than 32 chars', () => {
|
||||||
|
expect(() =>
|
||||||
|
readAuthEnv({
|
||||||
|
BETTER_AUTH_SECRET: 'troppo-corto',
|
||||||
|
BETTER_AUTH_URL: 'http://localhost:3000',
|
||||||
|
} as NodeJS.ProcessEnv),
|
||||||
|
).toThrow(/almeno 32/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a non-URL BETTER_AUTH_URL', () => {
|
||||||
|
expect(() =>
|
||||||
|
readAuthEnv({
|
||||||
|
BETTER_AUTH_SECRET: VALID_SECRET,
|
||||||
|
BETTER_AUTH_URL: 'not-a-url',
|
||||||
|
} as NodeJS.ProcessEnv),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves Google credentials when provided', () => {
|
||||||
|
const env = readAuthEnv({
|
||||||
|
BETTER_AUTH_SECRET: VALID_SECRET,
|
||||||
|
BETTER_AUTH_URL: 'http://localhost:3000',
|
||||||
|
GOOGLE_CLIENT_ID: 'cid',
|
||||||
|
GOOGLE_CLIENT_SECRET: 'csec',
|
||||||
|
} as NodeJS.ProcessEnv);
|
||||||
|
expect(env.GOOGLE_CLIENT_ID).toBe('cid');
|
||||||
|
expect(env.GOOGLE_CLIENT_SECRET).toBe('csec');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
name: '@ketopath/auth',
|
||||||
|
environment: 'node',
|
||||||
|
include: ['src/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
import { defineWorkspace } from 'vitest/config';
|
import { defineWorkspace } from 'vitest/config';
|
||||||
|
|
||||||
export default defineWorkspace(['./packages/shared']);
|
export default defineWorkspace(['./packages/shared', './packages/auth']);
|
||||||
Reference in new issue
Block a user