This commit is contained in:
77
e2e/api/app-questionnaires.spec.ts
Normal file
77
e2e/api/app-questionnaires.spec.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
|
||||
|
||||
const API = '/api';
|
||||
|
||||
const PASSWORD = 'testpass123';
|
||||
|
||||
|
||||
|
||||
async function login(request: import('@playwright/test').APIRequestContext, username: string) {
|
||||
|
||||
const res = await request.post(`${API}/auth/login`, {
|
||||
|
||||
data: { username, password: PASSWORD },
|
||||
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
expect(json.ok).toBe(true);
|
||||
|
||||
return json.data.token as string;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
test.describe('Android API – app_questionnaires (HTTP smoke)', () => {
|
||||
|
||||
test('list active questionnaires over HTTP', async ({ request }) => {
|
||||
|
||||
const token = await login(request, 'testcoach1');
|
||||
|
||||
const res = await request.get(`${API}/app_questionnaires`, {
|
||||
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
expect(json.ok).toBe(true);
|
||||
|
||||
expect(json.data.some((q: { id: string }) => q.id === 'questionnaire_test_demo')).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('missing bearer returns 401', async ({ request }) => {
|
||||
|
||||
const res = await request.get(`${API}/app_questionnaires`);
|
||||
|
||||
expect(res.status()).toBe(401);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('password change flow', async ({ request }) => {
|
||||
|
||||
const loginRes = await request.post(`${API}/auth/login`, {
|
||||
|
||||
data: { username: 'testmustchange', password: PASSWORD },
|
||||
|
||||
});
|
||||
|
||||
const loginJson = await loginRes.json();
|
||||
|
||||
expect(loginJson.data.mustChangePassword).toBe(true);
|
||||
|
||||
const tempToken = loginJson.data.token as string;
|
||||
|
||||
let fullToken: string | undefined;
|
||||
25
e2e/api/auth.spec.ts
Normal file
25
e2e/api/auth.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const API = '/api';
|
||||
|
||||
test.describe('Android API – auth', () => {
|
||||
test('login returns token', async ({ request }) => {
|
||||
const res = await request.post(`${API}/auth/login`, {
|
||||
data: { username: 'testadmin', password: 'testpass123' },
|
||||
});
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const json = await res.json();
|
||||
expect(json.ok).toBe(true);
|
||||
expect(json.data.token).toBeTruthy();
|
||||
expect(json.data.role).toBe('admin');
|
||||
});
|
||||
|
||||
test('bad password returns error', async ({ request }) => {
|
||||
const res = await request.post(`${API}/auth/login`, {
|
||||
data: { username: 'testadmin', password: 'wrong' },
|
||||
});
|
||||
expect(res.status()).toBe(401);
|
||||
const json = await res.json();
|
||||
expect(json.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
16
e2e/global-setup.js
Normal file
16
e2e/global-setup.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
export default async function globalSetup() {
|
||||
execSync('php tests/bin/seed-test-db.php', {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
QDB_MASTER_KEY: 'dGVzdC1tYXN0ZXIta2V5LXRoaXQtMzJieXRzIQ==',
|
||||
},
|
||||
});
|
||||
}
|
||||
16
e2e/website/helpers.ts
Normal file
16
e2e/website/helpers.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
export const TEST_PASSWORD = 'testpass123';
|
||||
|
||||
export async function loginAs(page: Page, username: string, password = TEST_PASSWORD) {
|
||||
await page.goto('/website/#/login');
|
||||
await page.locator('#username').fill(username);
|
||||
await page.locator('#password').fill(password);
|
||||
await page.locator('#loginForm button[type="submit"]').click();
|
||||
await expect(page.locator('h1')).toContainText('Questionnaires', { timeout: 15000 });
|
||||
}
|
||||
|
||||
export async function logout(page: Page) {
|
||||
await page.locator('#logoutBtn').click();
|
||||
await expect(page.locator('#loginForm')).toBeVisible();
|
||||
}
|
||||
21
e2e/website/login.spec.ts
Normal file
21
e2e/website/login.spec.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Website – login', () => {
|
||||
test('shows login form', async ({ page }) => {
|
||||
await page.goto('/website/#/login');
|
||||
await expect(page.locator('#loginForm')).toBeVisible();
|
||||
});
|
||||
|
||||
test('bad password shows error', async ({ page }) => {
|
||||
await page.goto('/website/#/login');
|
||||
await page.locator('#username').fill('testadmin');
|
||||
await page.locator('#password').fill('wrong-password');
|
||||
await page.locator('#loginForm button[type="submit"]').click();
|
||||
await expect(page.locator('#loginError')).toBeVisible();
|
||||
});
|
||||
|
||||
test('protected route redirects to login', async ({ page }) => {
|
||||
await page.goto('/website/#/users');
|
||||
await expect(page.locator('#loginForm')).toBeVisible();
|
||||
});
|
||||
});
|
||||
64
e2e/website/routes.spec.ts
Normal file
64
e2e/website/routes.spec.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { loginAs, logout } from './helpers';
|
||||
|
||||
test.describe('Website – routes (admin)', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAs(page, 'testadmin');
|
||||
});
|
||||
|
||||
test('dashboard lists questionnaires', async ({ page }) => {
|
||||
await expect(page.locator('h1')).toHaveText('Questionnaires');
|
||||
await expect(page.locator('.q-card, .empty-state')).toBeVisible();
|
||||
});
|
||||
|
||||
test('export page', async ({ page }) => {
|
||||
await page.goto('/website/#/export');
|
||||
await expect(page.locator('h1')).toContainText('Export');
|
||||
});
|
||||
|
||||
test('users page for admin', async ({ page }) => {
|
||||
await page.goto('/website/#/users');
|
||||
await expect(page.locator('h1')).toContainText('Users');
|
||||
});
|
||||
|
||||
test('clients page', async ({ page }) => {
|
||||
await page.goto('/website/#/clients');
|
||||
await expect(page.locator('h1')).toContainText('Clients');
|
||||
});
|
||||
|
||||
test('assignments page', async ({ page }) => {
|
||||
await page.goto('/website/#/assignments');
|
||||
await expect(page.locator('h1')).toContainText('Assign');
|
||||
});
|
||||
|
||||
test('editor for seeded questionnaire', async ({ page }) => {
|
||||
await page.goto('/website/#/questionnaire/questionnaire_test_demo');
|
||||
await expect(page.locator('#editorTitle')).not.toHaveText('Loading...');
|
||||
await expect(page.locator('#editorContent')).toContainText('Questions');
|
||||
});
|
||||
|
||||
test('results page', async ({ page }) => {
|
||||
await page.goto('/website/#/questionnaire/questionnaire_test_demo/results');
|
||||
await expect(page.locator('h1')).toContainText('Results');
|
||||
});
|
||||
|
||||
test('logout clears session', async ({ page }) => {
|
||||
await logout(page);
|
||||
await page.goto('/website/#/');
|
||||
await expect(page.locator('#loginForm')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Website – role nav', () => {
|
||||
test('coach cannot see admin nav items', async ({ page }) => {
|
||||
await loginAs(page, 'testcoach1');
|
||||
const usersNav = page.locator('[data-nav-roles] a[href="#/users"]');
|
||||
await expect(usersNav).toBeHidden();
|
||||
});
|
||||
|
||||
test('supervisor sees users nav', async ({ page }) => {
|
||||
await loginAs(page, 'testsuper1');
|
||||
const usersNav = page.locator('[data-nav-roles] a[href="#/users"]');
|
||||
await expect(usersNav).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user