Files
nofx/web/src/components/auth/RegisterPage.test.tsx
tinkle-community 110bf52908 feat: cream terminal redesign, English-only UI, autopilot launch fixes
- Redesign dashboard into a cream-paper + vermilion IBM Plex Mono terminal
  (live L2 order book, cost/liq map, WS K-line, signal matrix, orchestration
  topology, risk radar, execution log, current positions, equity curve)
- Convert all user-facing UI and backend strings/prompts from Chinese to
  English (multi-language retained, default English)
- Add /api/statistics/full endpoint + full-stats frontend wiring
- Fix Autopilot launch: reuse the existing trader instead of creating
  duplicates (eliminates repeat ~35s create cost and stale-trader 404s);
  launch sends 5m scan interval
- Fix unreadable toasts: cream theme with high-contrast text + per-type accent
- Silence background dashboard polls (getTraderConfig) to stop error-toast spam
2026-06-30 16:03:52 +08:00

378 lines
13 KiB
TypeScript

import { describe, it, expect } from 'vitest'
/**
* PR #XXX test: fix inconsistent password validation
*
* Problem: RegisterPage had two password validation paths:
* 1. Visual validation provided by the PasswordChecklist component
* 2. A custom isStrongPassword function
* This could cause the validation rules to diverge.
*
* Fix: remove the duplicate isStrongPassword function and rely solely on the PasswordChecklist result.
*
* This test focuses on verifying the consistency of the password validation logic, ensuring:
* 1. The duplicate isStrongPassword function is removed
* 2. A single PasswordChecklist validation is used
* 3. The special character rule stays consistent between normal display and error messages
*/
describe('RegisterPage - Password Validation Consistency (Logic Tests)', () => {
/**
* Test the password validation rule logic
* These tests verify the core validation logic, consistent with the PasswordChecklist component rules
*/
describe('password validation rules', () => {
it('should validate minimum 8 characters', () => {
const password = 'Short1!'
const isValid = password.length >= 8
expect(isValid).toBe(false)
const validPassword = 'LongPass1!'
const isValidPassword = validPassword.length >= 8
expect(isValidPassword).toBe(true)
})
it('should require uppercase letter', () => {
const hasUppercase = (pwd: string) => /[A-Z]/.test(pwd)
expect(hasUppercase('lowercase123!')).toBe(false)
expect(hasUppercase('Uppercase123!')).toBe(true)
expect(hasUppercase('ALLCAPS123!')).toBe(true)
})
it('should require lowercase letter', () => {
const hasLowercase = (pwd: string) => /[a-z]/.test(pwd)
expect(hasLowercase('UPPERCASE123!')).toBe(false)
expect(hasLowercase('Lowercase123!')).toBe(true)
expect(hasLowercase('alllower123!')).toBe(true)
})
it('should require number', () => {
const hasNumber = (pwd: string) => /\d/.test(pwd)
expect(hasNumber('NoNumber!')).toBe(false)
expect(hasNumber('HasNumber1!')).toBe(true)
expect(hasNumber('Multiple123!')).toBe(true)
})
it('should require special character from allowed set', () => {
// Per the RegisterPage.tsx config, the special character regex is /[@#$%!&*?]/
const hasSpecialChar = (pwd: string) => /[@#$%!&*?]/.test(pwd)
expect(hasSpecialChar('NoSpecial123')).toBe(false)
expect(hasSpecialChar('HasAt123@')).toBe(true)
expect(hasSpecialChar('HasHash123#')).toBe(true)
expect(hasSpecialChar('HasDollar123$')).toBe(true)
expect(hasSpecialChar('HasPercent123%')).toBe(true)
expect(hasSpecialChar('HasExclaim123!')).toBe(true)
expect(hasSpecialChar('HasAmpersand123&')).toBe(true)
expect(hasSpecialChar('HasStar123*')).toBe(true)
expect(hasSpecialChar('HasQuestion123?')).toBe(true)
// Special characters not in the allowed list should fail
expect(hasSpecialChar('HasCaret123^')).toBe(false)
expect(hasSpecialChar('HasTilde123~')).toBe(false)
})
it('should validate passwords match', () => {
const password = 'StrongPass123!'
const confirmPassword1 = 'StrongPass123!'
const confirmPassword2 = 'DifferentPass123!'
expect(password === confirmPassword1).toBe(true)
expect(password === confirmPassword2).toBe(false)
})
})
/**
* Test the complete password strength validation
* Simulates the full PasswordChecklist validation logic
*/
describe('complete password strength validation', () => {
const validatePassword = (
pwd: string,
confirmPwd: string
): {
minLength: boolean
hasUppercase: boolean
hasLowercase: boolean
hasNumber: boolean
hasSpecialChar: boolean
match: boolean
isValid: boolean
} => {
const minLength = pwd.length >= 8
const hasUppercase = /[A-Z]/.test(pwd)
const hasLowercase = /[a-z]/.test(pwd)
const hasNumber = /\d/.test(pwd)
const hasSpecialChar = /[@#$%!&*?]/.test(pwd)
const match = pwd === confirmPwd
return {
minLength,
hasUppercase,
hasLowercase,
hasNumber,
hasSpecialChar,
match,
isValid:
minLength &&
hasUppercase &&
hasLowercase &&
hasNumber &&
hasSpecialChar &&
match,
}
}
it('should reject password with only lowercase', () => {
const result = validatePassword('lowercase123!', 'lowercase123!')
expect(result.hasLowercase).toBe(true)
expect(result.hasUppercase).toBe(false)
expect(result.isValid).toBe(false)
})
it('should reject password with only uppercase', () => {
const result = validatePassword('UPPERCASE123!', 'UPPERCASE123!')
expect(result.hasUppercase).toBe(true)
expect(result.hasLowercase).toBe(false)
expect(result.isValid).toBe(false)
})
it('should reject password without numbers', () => {
const result = validatePassword('NoNumber!', 'NoNumber!')
expect(result.hasNumber).toBe(false)
expect(result.isValid).toBe(false)
})
it('should reject password without special characters', () => {
const result = validatePassword('NoSpecial123', 'NoSpecial123')
expect(result.hasSpecialChar).toBe(false)
expect(result.isValid).toBe(false)
})
it('should reject password less than 8 characters', () => {
const result = validatePassword('Short1!', 'Short1!')
expect(result.minLength).toBe(false)
expect(result.isValid).toBe(false)
})
it('should reject when passwords do not match', () => {
const result = validatePassword('StrongPass123!', 'DifferentPass123!')
expect(result.match).toBe(false)
expect(result.isValid).toBe(false)
})
it('should accept strong password meeting all requirements', () => {
const result = validatePassword('StrongPass123!', 'StrongPass123!')
expect(result.minLength).toBe(true)
expect(result.hasUppercase).toBe(true)
expect(result.hasLowercase).toBe(true)
expect(result.hasNumber).toBe(true)
expect(result.hasSpecialChar).toBe(true)
expect(result.match).toBe(true)
expect(result.isValid).toBe(true)
})
it('should accept password with exactly 8 characters', () => {
const result = validatePassword('Pass123!', 'Pass123!')
expect(result.isValid).toBe(true)
})
it('should accept password with multiple special characters', () => {
const result = validatePassword('Pass123!@#', 'Pass123!@#')
expect(result.isValid).toBe(true)
})
it('should accept very long password', () => {
const longPassword = 'VeryLongStrongPassword123!@#$%'
const result = validatePassword(longPassword, longPassword)
expect(result.isValid).toBe(true)
})
})
/**
* Test special character consistency
* Ensures the normal display (lines 229-251) and error messages (lines 300-323) in RegisterPage
* use the same special character regex /[@#$%!&*?]/
*/
describe('special character consistency', () => {
it('should use consistent special character regex across all validations', () => {
// Both PasswordChecklist instances in RegisterPage should use the same specialCharsRegex
const specialCharsRegex = /[@#$%!&*?]/
// Test the allowed special characters
const validSpecialChars = ['@', '#', '$', '%', '!', '&', '*', '?']
validSpecialChars.forEach((char) => {
expect(specialCharsRegex.test(char)).toBe(true)
})
// Test the disallowed special characters
const invalidSpecialChars = ['^', '~', '`', '(', ')', '-', '_', '=', '+']
invalidSpecialChars.forEach((char) => {
expect(specialCharsRegex.test(char)).toBe(false)
})
})
it('should validate all allowed special characters in passwords', () => {
const hasSpecialChar = (pwd: string) => /[@#$%!&*?]/.test(pwd)
const validPasswords = [
'Password123@',
'Password123#',
'Password123$',
'Password123%',
'Password123!',
'Password123&',
'Password123*',
'Password123?',
]
validPasswords.forEach((pwd) => {
expect(hasSpecialChar(pwd)).toBe(true)
})
})
it('should reject passwords with non-allowed special characters', () => {
const hasSpecialChar = (pwd: string) => /[@#$%!&*?]/.test(pwd)
const invalidPasswords = [
'Password123^',
'Password123~',
'Password123`',
'Password123(',
'Password123)',
'Password123-',
'Password123_',
'Password123=',
'Password123+',
]
invalidPasswords.forEach((pwd) => {
expect(hasSpecialChar(pwd)).toBe(false)
})
})
})
/**
* Test edge cases
*/
describe('edge cases', () => {
const validatePassword = (pwd: string, confirmPwd: string): boolean => {
const minLength = pwd.length >= 8
const hasUppercase = /[A-Z]/.test(pwd)
const hasLowercase = /[a-z]/.test(pwd)
const hasNumber = /\d/.test(pwd)
const hasSpecialChar = /[@#$%!&*?]/.test(pwd)
const match = pwd === confirmPwd
return (
minLength &&
hasUppercase &&
hasLowercase &&
hasNumber &&
hasSpecialChar &&
match
)
}
it('should handle exactly 8 character password', () => {
expect(validatePassword('Pass123!', 'Pass123!')).toBe(true)
})
it('should handle very long password', () => {
const longPassword = 'VeryLongStrongPassword123!@#$%^&*()_+'
expect(validatePassword(longPassword, longPassword)).toBe(true)
})
it('should handle password with all allowed special characters', () => {
const password = 'Pass123@#$%!&*?'
expect(validatePassword(password, password)).toBe(true)
})
it('should handle password with consecutive numbers', () => {
const password = 'Password123456789!'
expect(validatePassword(password, password)).toBe(true)
})
it('should handle password with consecutive special characters', () => {
const password = 'Pass123!@#$%'
expect(validatePassword(password, password)).toBe(true)
})
it('should be case sensitive for matching', () => {
expect(validatePassword('Password123!', 'password123!')).toBe(false)
expect(validatePassword('password123!', 'Password123!')).toBe(false)
})
it('should not accept whitespace as special character', () => {
const hasSpecialChar = /[@#$%!&*?]/.test('Password123 ')
expect(hasSpecialChar).toBe(false)
})
})
/**
* Test consistency after refactoring
* Ensures that after removing the isStrongPassword function, all validation goes through PasswordChecklist
*/
describe('refactoring consistency verification', () => {
it('should have removed duplicate isStrongPassword function', () => {
// This test verifies the intent of the refactor:
// Before the refactor there was an isStrongPassword function
// After the refactor it should be removed, using only PasswordChecklist validation
// We verify consistency by simulating the PasswordChecklist logic
const passwordChecklistValidation = (pwd: string, confirm: string) => {
return {
minLength: pwd.length >= 8,
capital: /[A-Z]/.test(pwd),
lowercase: /[a-z]/.test(pwd),
number: /\d/.test(pwd),
specialChar: /[@#$%!&*?]/.test(pwd),
match: pwd === confirm,
}
}
// Test a few passwords
const testCases = [
{ pwd: 'Weak', confirm: 'Weak', shouldPass: false },
{ pwd: 'StrongPass123!', confirm: 'StrongPass123!', shouldPass: true },
{ pwd: 'NoNumber!', confirm: 'NoNumber!', shouldPass: false },
{ pwd: 'Pass123!', confirm: 'Pass123!', shouldPass: true },
]
testCases.forEach((testCase) => {
const result = passwordChecklistValidation(
testCase.pwd,
testCase.confirm
)
const isValid = Object.values(result).every((v) => v === true)
expect(isValid).toBe(testCase.shouldPass)
})
})
it('should use consistent validation logic across the component', () => {
// Verify the consistency of the validation logic
const validation1 = {
minLength: 8,
requireCapital: true,
requireLowercase: true,
requireNumber: true,
requireSpecialChar: true,
specialCharsRegex: /[@#$%!&*?]/,
}
// The normal display and error messages in RegisterPage should use the same config
const validation2 = {
minLength: 8,
requireCapital: true,
requireLowercase: true,
requireNumber: true,
requireSpecialChar: true,
specialCharsRegex: /[@#$%!&*?]/,
}
expect(validation1).toEqual(validation2)
})
})
})