password_manager/tests/lib/stores/security.test.js

156 lines
4.0 KiB
JavaScript

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { startAutoLock, stopAutoLock } from '../../../src/lib/stores/security.svelte.js'
import { app } from '../../../src/lib/stores/app.svelte.js'
import { settings } from '../../../src/lib/stores/settings.svelte.js'
// Reset singleton state before each test
function resetState() {
app.isUnlocked = false
app.encryptionKey = null
app.salt = null
settings.autoLockMinutes = 5
settings.lockOnTabSwitch = true
}
beforeEach(() => {
resetState()
stopAutoLock()
})
afterEach(() => {
stopAutoLock()
resetState()
})
describe('startAutoLock / stopAutoLock', () => {
it('should register and clean up activity listeners', () => {
// startAutoLock registers event listeners
startAutoLock()
// stopAutoLock should remove them
stopAutoLock()
// No error means cleanup worked
expect(true).toBe(true)
})
it('should register and clean up visibility listener', () => {
startAutoLock()
stopAutoLock()
expect(true).toBe(true)
})
it('should register and clean up beforeunload listener', () => {
startAutoLock()
stopAutoLock()
expect(true).toBe(true)
})
it('should not throw when called multiple times', () => {
startAutoLock()
startAutoLock()
stopAutoLock()
stopAutoLock()
expect(true).toBe(true)
})
})
describe('auto-lock behavior', () => {
it('should lock vault after inactivity timeout', async () => {
vi.useFakeTimers()
settings.autoLockMinutes = 1
app.isUnlocked = true
app.encryptionKey = { mock: 'key' }
startAutoLock()
// Wait for the timer (1 minute = 60000ms)
vi.advanceTimersByTime(60000)
expect(app.isUnlocked).toBe(false)
expect(app.encryptionKey).toBe(null)
vi.useRealTimers()
})
it('should lock on tab switch when lockOnTabSwitch is true', async () => {
app.isUnlocked = true
app.encryptionKey = { mock: 'key' }
settings.lockOnTabSwitch = true
startAutoLock()
// Simulate visibility change
Object.defineProperty(document, 'hidden', { value: true, writable: true, configurable: true })
document.dispatchEvent(new Event('visibilitychange'))
expect(app.isUnlocked).toBe(false)
expect(app.encryptionKey).toBe(null)
})
it('should not lock on tab switch when lockOnTabSwitch is false', async () => {
app.isUnlocked = true
app.encryptionKey = { mock: 'key' }
settings.lockOnTabSwitch = false
startAutoLock()
Object.defineProperty(document, 'hidden', { value: true, writable: true, configurable: true })
document.dispatchEvent(new Event('visibilitychange'))
expect(app.isUnlocked).toBe(true)
expect(app.encryptionKey).toEqual({ mock: 'key' })
})
it('should clear key on beforeunload', async () => {
app.isUnlocked = true
app.encryptionKey = { mock: 'key' }
startAutoLock()
window.dispatchEvent(new Event('beforeunload'))
expect(app.encryptionKey).toBe(null)
})
it('should reset timer on user activity', async () => {
vi.useFakeTimers()
settings.autoLockMinutes = 1
app.isUnlocked = true
app.encryptionKey = { mock: 'key' }
startAutoLock()
// Tick 45 seconds — not enough to trigger
vi.advanceTimersByTime(45000)
expect(app.isUnlocked).toBe(true)
// Simulate user activity (keydown resets timer)
window.dispatchEvent(new Event('keydown'))
// Tick another 45 seconds — should still be unlocked (timer was reset)
vi.advanceTimersByTime(45000)
expect(app.isUnlocked).toBe(true)
// Tick the remaining 15+ seconds
vi.advanceTimersByTime(20000)
expect(app.isUnlocked).toBe(false)
vi.useRealTimers()
})
it('should not lock when already locked', async () => {
app.isUnlocked = false
settings.lockOnTabSwitch = true
startAutoLock()
Object.defineProperty(document, 'hidden', { value: true, writable: true, configurable: true })
document.dispatchEvent(new Event('visibilitychange'))
expect(app.isUnlocked).toBe(false)
})
})