Merge pull request #32 from cloud-neutral-toolkit/feature/refine-registration-and-cors
Feature/refine registration and cors
This commit is contained in:
commit
73005db01c
BIN
frontend.log
Normal file
BIN
frontend.log
Normal file
Binary file not shown.
@ -62,6 +62,20 @@ const nextConfig = {
|
||||
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/api/:path*",
|
||||
headers: [
|
||||
{ key: "Access-Control-Allow-Credentials", value: "true" },
|
||||
{ key: "Access-Control-Allow-Origin", value: process.env.CORS_ALLOWED_ORIGINS || "https://console.svc.plus,http://localhost:3000" },
|
||||
{ key: "Access-Control-Allow-Methods", value: "GET,POST,PUT,PATCH,DELETE,OPTIONS" },
|
||||
{ key: "Access-Control-Allow-Headers", value: "Content-Type, Authorization, X-Requested-With, X-Account-Session" },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
reactStrictMode: true,
|
||||
typedRoutes: false,
|
||||
turbopack: {
|
||||
|
||||
10
run-api-test.sh
Executable file
10
run-api-test.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# run-api-test.sh
|
||||
# Placeholder for API tests - as requested by user pattern.
|
||||
# Currently assumes API tests might be located similarly or future expansion.
|
||||
# If no API tests defined yet, this is a stub.
|
||||
|
||||
echo "Running API tests..."
|
||||
# Example: npm run test:api
|
||||
# or vitest specific path
|
||||
echo "No specific API test command configured yet. Please update this script."
|
||||
8
run-ui-test.sh
Executable file
8
run-ui-test.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# run-ui-test.sh
|
||||
# Usage: ./run-ui-test.sh [test-file]
|
||||
|
||||
TEST_FILE=${1:-"tests/e2e/ui/registration/register.spec.ts"}
|
||||
|
||||
echo "Running UI tests: $TEST_FILE"
|
||||
npx playwright test "$TEST_FILE"
|
||||
File diff suppressed because it is too large
Load Diff
@ -222,6 +222,8 @@ type AuthRegisterTranslation = {
|
||||
form: {
|
||||
title: string
|
||||
subtitle: string
|
||||
name: string
|
||||
namePlaceholder: string
|
||||
email: string
|
||||
emailPlaceholder: string
|
||||
password: string
|
||||
@ -723,6 +725,8 @@ export const translations: Record<'en' | 'zh', Translation> = {
|
||||
form: {
|
||||
title: 'Create your account',
|
||||
subtitle: 'Submit your email and password, request the code, and enter it to activate your account.',
|
||||
name: 'Username',
|
||||
namePlaceholder: '4-16 chars, starts with letter',
|
||||
email: 'Work email',
|
||||
emailPlaceholder: 'name@example.com',
|
||||
password: 'Password',
|
||||
@ -1384,6 +1388,8 @@ export const translations: Record<'en' | 'zh', Translation> = {
|
||||
form: {
|
||||
title: '创建账号',
|
||||
subtitle: '先提交邮箱和密码获取验证码,再输入邮箱收到的验证码完成注册。',
|
||||
name: '用户名',
|
||||
namePlaceholder: '4-16位字母或数字,字母开头',
|
||||
email: '邮箱',
|
||||
emailPlaceholder: 'name@example.com',
|
||||
password: '密码',
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
const DEFAULT_ALLOWED_ORIGINS = 'https://console.svc.plus,http://localhost:3000'
|
||||
|
||||
function parseAllowedOrigins() {
|
||||
const raw = process.env.CORS_ALLOWED_ORIGINS ?? DEFAULT_ALLOWED_ORIGINS
|
||||
return raw
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function resolveAllowedOrigin(origin: string | null, allowed: string[]) {
|
||||
if (!origin) return null
|
||||
if (allowed.includes('*')) return '*'
|
||||
return allowed.includes(origin) ? origin : null
|
||||
}
|
||||
|
||||
function applyCorsHeaders(response: NextResponse, origin: string | null) {
|
||||
if (!origin) return
|
||||
response.headers.set('Access-Control-Allow-Origin', origin)
|
||||
response.headers.set('Access-Control-Allow-Credentials', 'true')
|
||||
response.headers.set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS')
|
||||
response.headers.set(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Content-Type, Authorization, X-Requested-With, X-Account-Session',
|
||||
)
|
||||
response.headers.set('Vary', 'Origin')
|
||||
}
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const allowedOrigins = parseAllowedOrigins()
|
||||
const originHeader = request.headers.get('origin')
|
||||
const allowedOrigin = resolveAllowedOrigin(originHeader, allowedOrigins)
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
const response = new NextResponse(null, { status: 204 })
|
||||
applyCorsHeaders(response, allowedOrigin)
|
||||
return response
|
||||
}
|
||||
|
||||
const response = NextResponse.next()
|
||||
applyCorsHeaders(response, allowedOrigin)
|
||||
return response
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/api/:path*'],
|
||||
}
|
||||
93
tests/e2e/ui/registration/register.spec.ts
Normal file
93
tests/e2e/ui/registration/register.spec.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Registration Flow', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Mock the send email API to always succeed
|
||||
await page.route('/api/auth/register/send', async (route) => {
|
||||
await route.fulfill({ status: 200, body: JSON.stringify({ success: true }) });
|
||||
});
|
||||
|
||||
// Mock the verification/register API to always succeed
|
||||
await page.route('/api/auth/register', async (route) => {
|
||||
await route.fulfill({ status: 200, body: JSON.stringify({ success: true, redirectTo: '/' }) });
|
||||
});
|
||||
|
||||
// Mock the login API that is called after registration
|
||||
await page.route('/api/auth/login', async (route) => {
|
||||
await route.fulfill({ status: 200, body: JSON.stringify({ success: true, redirectTo: '/' }) });
|
||||
});
|
||||
|
||||
await page.goto('/register');
|
||||
});
|
||||
|
||||
test('should validate username rules', async ({ page }) => {
|
||||
const usernameInput = page.getByLabel(/Username|用户名/);
|
||||
const emailInput = page.getByLabel(/Email|邮箱/);
|
||||
const passwordInput = page.getByLabel(/^Password|密码$/);
|
||||
const confirmPasswordInput = page.getByLabel(/Confirm Password|确认密码/);
|
||||
const nextButton = page.getByRole('button', { name: /Next|下一步|获取验证码/ });
|
||||
|
||||
// Helper to fill form and check for error
|
||||
const attemptRegistration = async (username: string) => {
|
||||
await usernameInput.fill(username);
|
||||
await nextButton.click();
|
||||
};
|
||||
|
||||
// 1. Starts with digit
|
||||
await attemptRegistration('1abc');
|
||||
await expect(page.getByText(/Invalid Name|用户名格式不正确/)).toBeVisible();
|
||||
|
||||
// 2. Too short
|
||||
await attemptRegistration('abc');
|
||||
await expect(page.getByText(/Invalid Name|用户名格式不正确/)).toBeVisible();
|
||||
|
||||
// 3. Special characters
|
||||
await attemptRegistration('user@name');
|
||||
await expect(page.getByText(/Invalid Name|用户名格式不正确/)).toBeVisible();
|
||||
|
||||
// 4. Valid username - should NOT show error (but will fail on other fields if empty, so we just check user field validation logic primarily)
|
||||
// To strictly check if it proceeds, we need to fill other fields
|
||||
await usernameInput.fill('ValidUser1');
|
||||
await emailInput.fill('test@example.com');
|
||||
await passwordInput.fill('Password123!');
|
||||
await confirmPasswordInput.fill('Password123!');
|
||||
await page.getByRole('checkbox').check();
|
||||
|
||||
// Clear any previous errors
|
||||
// Click Next
|
||||
await nextButton.click();
|
||||
|
||||
// Expect transition to Step 2 (Verification code input should appear)
|
||||
await expect(page.locator('input[inputmode="numeric"]')).toHaveCount(6);
|
||||
});
|
||||
|
||||
test('should auto-submit after entering 6-digit code', async ({ page }) => {
|
||||
// Fill Step 1
|
||||
await page.getByLabel(/Username|用户名/).fill('AutoSubmitUser');
|
||||
await page.getByLabel(/Email|邮箱/).fill('test@example.com');
|
||||
await page.getByLabel(/^Password|密码$/).fill('Password123!');
|
||||
await page.getByLabel(/Confirm Password|确认密码/).fill('Password123!');
|
||||
await page.getByRole('checkbox').check();
|
||||
await page.getByRole('button', { name: /Next|下一步|获取验证码/ }).click();
|
||||
|
||||
// Wait for Step 2
|
||||
const codeInputs = page.locator('input[inputmode="numeric"]');
|
||||
await expect(codeInputs).toHaveCount(6);
|
||||
|
||||
// Enter code digits 1-6 slowly
|
||||
const code = '123456';
|
||||
for (let i = 0; i < code.length; i++) {
|
||||
await codeInputs.nth(i).type(code[i], { delay: 100 });
|
||||
}
|
||||
|
||||
// Verify auto-submit triggered
|
||||
// The button should show loading state or we should be redirected
|
||||
// Since we mocked success, we expect redirect or success message
|
||||
// Updating checking logic based on UI behavior
|
||||
await expect(page.getByRole('button', { name: /Completing|完成中/ })).toBeVisible();
|
||||
|
||||
// Eventually should redirect or show success
|
||||
// If redirection happens within the test context, page URL should change or success alert appears
|
||||
// await expect(page).toHaveURL('/'); // Uncomment if verification of redirect is strictly needed and works with the mock
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user