test(phase-02): add Nyquist validation tests — fill SEC-05, AUTH-08, SEC-03 and frontend gaps
8 test files, 60 new tests (14 backend + 46 frontend). All green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6c79f92d70
commit
d98e3ab7a1
@@ -0,0 +1,117 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import PasswordStrengthBar from '../PasswordStrengthBar.vue'
|
||||
|
||||
/**
|
||||
* AUTH-01 (frontend): PasswordStrengthBar strength scoring.
|
||||
*
|
||||
* Score algorithm (0–4):
|
||||
* +1 if length >= 12
|
||||
* +1 if /[A-Z]/
|
||||
* +1 if /[0-9]/
|
||||
* +1 if /[^A-Za-z0-9]/ (special character)
|
||||
*/
|
||||
|
||||
describe('PasswordStrengthBar — score and visibility', () => {
|
||||
it('renders nothing when password is empty string', () => {
|
||||
const w = mount(PasswordStrengthBar, { props: { password: '' } })
|
||||
// v-if="password" — empty string is falsy, component is hidden
|
||||
const bar = w.find('.mt-2')
|
||||
expect(bar.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders nothing when password prop is absent (default empty)', () => {
|
||||
const w = mount(PasswordStrengthBar)
|
||||
const bar = w.find('.mt-2')
|
||||
expect(bar.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows strength bar when password is non-empty', () => {
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'a' } })
|
||||
expect(w.find('.mt-2').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PasswordStrengthBar — score 1 (weak)', () => {
|
||||
it('score 1: long-enough uppercase-only password', () => {
|
||||
// Only length >= 12 satisfied: "AAAAAAAAAAAA" — uppercase yes, but no digit, no special
|
||||
// Actually uppercase satisfies /[A-Z]/ → score 2. Use lowercase only >= 12
|
||||
// "aaaaaaaaaaaa" — only length passes → score 1
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'aaaaaaaaaaaa' } })
|
||||
expect(w.text()).toContain('Too weak')
|
||||
})
|
||||
|
||||
it('score 1: short uppercase password with no digit or special', () => {
|
||||
// "ABC" — only uppercase passes, no length, no digit, no special → score 1
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'ABC' } })
|
||||
expect(w.text()).toContain('Too weak')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PasswordStrengthBar — score 2 (weak)', () => {
|
||||
it('score 2: length + uppercase only', () => {
|
||||
// "AAAAAAAAAAAAa" — length>=12 + uppercase: score 2 (no digit, no special)
|
||||
// Wait: "AAAAAAAAAAAA" — length>=12 AND [A-Z] → score 2
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'AAAAAAAAAAAA' } })
|
||||
expect(w.text()).toContain('Weak')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PasswordStrengthBar — score 3 (fair)', () => {
|
||||
it('score 3: length + uppercase + digit', () => {
|
||||
// "AAAAAAAAAAA1" (12 chars) — length>=12, [A-Z], [0-9] → score 3 (no special)
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'AAAAAAAAAAA1' } })
|
||||
expect(w.text()).toContain('Fair')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PasswordStrengthBar — score 4 (strong)', () => {
|
||||
it('score 4: length + uppercase + digit + special', () => {
|
||||
// "Passw0rd123!" — all four criteria met
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'Passw0rd123!' } })
|
||||
expect(w.text()).toContain('Strong')
|
||||
})
|
||||
|
||||
it('score 4: matches backend AUTH-01 strong password example', () => {
|
||||
// StrongPass12! — exactly the password used in backend tests
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'StrongPass12!' } })
|
||||
expect(w.text()).toContain('Strong')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PasswordStrengthBar — score boundary: short password', () => {
|
||||
it('11-char password with all other criteria still misses +1 for length', () => {
|
||||
// "Passw0rd12!" — 11 chars: no length bonus, but uppercase+digit+special → score 3
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'Passw0rd12!' } })
|
||||
// score should be 3 (Fair), not 4 (Strong)
|
||||
expect(w.text()).toContain('Fair')
|
||||
expect(w.text()).not.toContain('Strong')
|
||||
})
|
||||
|
||||
it('12-char password with all criteria → score 4', () => {
|
||||
// "Passw0rd123!" — exactly 12 chars with all criteria
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'Passw0rd123!' } })
|
||||
expect(w.text()).toContain('Strong')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PasswordStrengthBar — visual segments', () => {
|
||||
it('renders 4 bar segments', () => {
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'something' } })
|
||||
// 4 segment divs from v-for="i in 4"
|
||||
const segments = w.findAll('.h-1.flex-1.rounded')
|
||||
expect(segments.length).toBe(4)
|
||||
})
|
||||
|
||||
it('score-1 label is red', () => {
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'ABC' } })
|
||||
const label = w.find('span.text-xs')
|
||||
expect(label.classes()).toContain('text-red-500')
|
||||
})
|
||||
|
||||
it('score-4 label is green', () => {
|
||||
const w = mount(PasswordStrengthBar, { props: { password: 'StrongPass12!' } })
|
||||
const label = w.find('span.text-xs')
|
||||
expect(label.classes()).toContain('text-green-500')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user