import { describe, it, expect } from 'vitest' import { generateId, createEntry, updateEntry, createGroup, validateEntry, validateGroup, createTrashGroup, isTrashGroup, TRASH_GROUP_ID, TRASH_GROUP_NAME, TRASH_GROUP_COLOR, } from '../../../src/lib/models/schema.js' describe('generateId', () => { it('should generate unique IDs', () => { const ids = new Set() for (let i = 0; i < 100; i++) { ids.add(generateId()) } expect(ids.size).toBe(100) }) it('should produce sortable IDs (timestamp-based)', () => { const id1 = generateId() // Small delay to ensure different timestamp // IDs are base36 timestamps + random, so they should be lexicographically ordered expect(id1).toMatch(/^[a-z0-9]+_[0-9a-f]{8}$/) }) it('should contain underscore separator', () => { const id = generateId() const parts = id.split('_') expect(parts).toHaveLength(2) expect(parts[1]).toHaveLength(8) }) }) describe('createEntry', () => { it('should create an entry with required fields', () => { const entry = createEntry({ title: 'GitHub', encryptedPassword: 'encrypted-blob', }) expect(entry.id).toBeDefined() expect(entry.title).toBe('GitHub') expect(entry.encryptedPassword).toBe('encrypted-blob') expect(entry.createdAt).toBeDefined() expect(entry.updatedAt).toBe(entry.createdAt) }) it('should trim title and optional fields', () => { const entry = createEntry({ title: ' GitHub ', username: ' user@test.com ', url: ' https://github.com ', notes: ' some notes ', encryptedPassword: 'encrypted-blob', }) expect(entry.title).toBe('GitHub') expect(entry.username).toBe('user@test.com') expect(entry.url).toBe('https://github.com') expect(entry.notes).toBe('some notes') }) it('should set defaults for optional fields', () => { const entry = createEntry({ title: 'Test', encryptedPassword: 'encrypted-blob', }) expect(entry.username).toBe('') expect(entry.url).toBe('') expect(entry.notes).toBe('') expect(entry.groupId).toBe('') expect(entry.tags).toEqual([]) }) it('should accept groupId and tags', () => { const entry = createEntry({ title: 'Test', encryptedPassword: 'encrypted-blob', groupId: 'group-123', tags: ['work', 'important'], }) expect(entry.groupId).toBe('group-123') expect(entry.tags).toEqual(['work', 'important']) }) }) describe('updateEntry', () => { it('should preserve id and createdAt', () => { const existing = createEntry({ title: 'GitHub', encryptedPassword: 'old-encrypted', }) const updated = updateEntry(existing, { title: 'GitHub Pro' }) expect(updated.id).toBe(existing.id) expect(updated.createdAt).toBe(existing.createdAt) // updatedAt should be >= createdAt (ISO strings are lexicographically comparable) expect(updated.updatedAt >= existing.updatedAt).toBe(true) }) it('should update only specified fields', () => { const existing = createEntry({ title: 'GitHub', username: 'user', encryptedPassword: 'old-encrypted', url: 'https://github.com', notes: 'old notes', }) const updated = updateEntry(existing, { title: 'GitHub Pro' }) expect(updated.title).toBe('GitHub Pro') expect(updated.username).toBe('user') expect(updated.encryptedPassword).toBe('old-encrypted') expect(updated.url).toBe('https://github.com') expect(updated.notes).toBe('old notes') }) it('should trim updated string fields', () => { const existing = createEntry({ title: 'GitHub', encryptedPassword: 'encrypted', }) const updated = updateEntry(existing, { title: ' GitHub Pro ', notes: ' new notes ' }) expect(updated.title).toBe('GitHub Pro') expect(updated.notes).toBe('new notes') }) it('should handle undefined fields (no-op)', () => { const existing = createEntry({ title: 'GitHub', encryptedPassword: 'encrypted', }) const updated = updateEntry(existing, { title: undefined }) expect(updated.title).toBe('GitHub') }) }) describe('createGroup', () => { it('should create a group with name and color', () => { const group = createGroup('Work', '#ff0000') expect(group.id).toBeDefined() expect(group.name).toBe('Work') expect(group.color).toBe('#ff0000') expect(group.createdAt).toBeDefined() }) it('should assign a random color when none provided', () => { const group = createGroup('Personal') expect(group.color).toMatch(/^#[0-9a-f]{6}$/) }) it('should trim group name', () => { const group = createGroup(' Work ') expect(group.name).toBe('Work') }) }) describe('validateEntry', () => { it('should pass with valid data', () => { const result = validateEntry({ title: 'GitHub', encryptedPassword: 'encrypted' }) expect(result.valid).toBe(true) expect(result.errors).toEqual([]) }) it('should fail with empty title', () => { const result = validateEntry({ title: '', encryptedPassword: 'encrypted' }) expect(result.valid).toBe(false) expect(result.errors).toContain('Title is required') }) it('should fail with whitespace-only title', () => { const result = validateEntry({ title: ' ', encryptedPassword: 'encrypted' }) expect(result.valid).toBe(false) expect(result.errors).toContain('Title is required') }) it('should fail with missing encryptedPassword', () => { const result = validateEntry({ title: 'GitHub' }) expect(result.valid).toBe(false) expect(result.errors).toContain('Password is required') }) it('should report multiple errors', () => { const result = validateEntry({ title: '' }) expect(result.valid).toBe(false) expect(result.errors.length).toBe(2) }) }) describe('validateGroup', () => { it('should pass with valid name', () => { const result = validateGroup('Work') expect(result.valid).toBe(true) expect(result.errors).toEqual([]) }) it('should fail with empty name', () => { const result = validateGroup('') expect(result.valid).toBe(false) expect(result.errors).toContain('Group name is required') }) it('should fail with whitespace-only name', () => { const result = validateGroup(' ') expect(result.valid).toBe(false) expect(result.errors).toContain('Group name is required') }) it('should fail with name over 50 characters', () => { const result = validateGroup('a'.repeat(51)) expect(result.valid).toBe(false) expect(result.errors).toContain('Group name must be 50 characters or less') }) it('should pass with exactly 50 characters', () => { const result = validateGroup('a'.repeat(50)) expect(result.valid).toBe(true) }) }) describe('createTrashGroup', () => { it('should return a group with fixed trash ID', () => { const group = createTrashGroup() expect(group.id).toBe(TRASH_GROUP_ID) expect(group.name).toBe(TRASH_GROUP_NAME) expect(group.color).toBe(TRASH_GROUP_COLOR) expect(group.createdAt).toBeDefined() }) }) describe('isTrashGroup', () => { it('should return true for trash group ID', () => { expect(isTrashGroup(TRASH_GROUP_ID)).toBe(true) }) it('should return false for other IDs', () => { expect(isTrashGroup('group-123')).toBe(false) expect(isTrashGroup('')).toBe(false) expect(isTrashGroup('__trash')).toBe(false) expect(isTrashGroup('__trash__extra')).toBe(false) }) })