Files
lunarfront-app/packages/backend/api-tests/suites/vault.ts
Ryan Moon 9400828f62 Rename Forte to LunarFront, generalize for any small business
Rebrand from Forte (music-store-specific) to LunarFront (any small business):
- Package namespace @forte/* → @lunarfront/*
- Database forte/forte_test → lunarfront/lunarfront_test
- Docker containers, volumes, connection strings
- UI branding, localStorage keys, test emails
- All documentation and planning docs

Generalize music-specific terminology:
- instrumentDescription → itemDescription
- instrumentCount → itemCount
- instrumentType → itemCategory (on service templates)
- New migration 0027_generalize_terminology for column renames
- Seed data updated with generic examples
- RBAC descriptions updated
2026-03-30 08:51:54 -05:00

233 lines
8.4 KiB
TypeScript

import { suite } from '../lib/context.js'
const MASTER_PASSWORD = 'test-vault-master-2024!'
suite('Vault', { tags: ['vault'] }, (t) => {
// --- Initialization & Unlock ---
t.test('initializes vault with master password', { tags: ['init'] }, async () => {
const res = await t.api.post('/v1/vault/initialize', { masterPassword: MASTER_PASSWORD })
t.assert.status(res, 201)
t.assert.contains(res.data.message, 'initialized')
})
t.test('rejects re-initialization', { tags: ['init'] }, async () => {
const res = await t.api.post('/v1/vault/initialize', { masterPassword: 'another-password' })
t.assert.status(res, 400)
})
t.test('reports vault status', { tags: ['status'] }, async () => {
const res = await t.api.get('/v1/vault/status')
t.assert.status(res, 200)
t.assert.equal(res.data.initialized, true)
t.assert.equal(res.data.unlocked, true)
})
t.test('locks vault', { tags: ['lock'] }, async () => {
const res = await t.api.post('/v1/vault/lock')
t.assert.status(res, 200)
const status = await t.api.get('/v1/vault/status')
t.assert.equal(status.data.unlocked, false)
})
t.test('rejects operations when locked', { tags: ['lock'] }, async () => {
const res = await t.api.get('/v1/vault/categories')
t.assert.status(res, 403)
t.assert.contains(res.data.error.message, 'locked')
})
t.test('unlocks vault with correct password', { tags: ['lock'] }, async () => {
const res = await t.api.post('/v1/vault/unlock', { masterPassword: MASTER_PASSWORD })
t.assert.status(res, 200)
const status = await t.api.get('/v1/vault/status')
t.assert.equal(status.data.unlocked, true)
})
t.test('rejects unlock with wrong password', { tags: ['lock'] }, async () => {
await t.api.post('/v1/vault/lock')
const res = await t.api.post('/v1/vault/unlock', { masterPassword: 'wrong-password' })
t.assert.status(res, 401)
// Re-unlock for remaining tests
await t.api.post('/v1/vault/unlock', { masterPassword: MASTER_PASSWORD })
})
// --- Categories ---
t.test('creates a category', { tags: ['category'] }, async () => {
const res = await t.api.post('/v1/vault/categories', { name: 'WiFi Passwords', description: 'Store WiFi credentials' })
t.assert.status(res, 201)
t.assert.equal(res.data.name, 'WiFi Passwords')
t.assert.ok(res.data.id)
})
t.test('lists accessible categories', { tags: ['category'] }, async () => {
const res = await t.api.get('/v1/vault/categories')
t.assert.status(res, 200)
t.assert.greaterThan(res.data.data.length, 0)
})
t.test('gets category detail with accessLevel', { tags: ['category'] }, async () => {
const list = await t.api.get('/v1/vault/categories')
const catId = list.data.data[0].id
const res = await t.api.get(`/v1/vault/categories/${catId}`)
t.assert.status(res, 200)
t.assert.equal(res.data.accessLevel, 'admin')
})
t.test('updates a category', { tags: ['category'] }, async () => {
const list = await t.api.get('/v1/vault/categories')
const catId = list.data.data[0].id
const res = await t.api.patch(`/v1/vault/categories/${catId}`, { name: 'WiFi & Network' })
t.assert.status(res, 200)
t.assert.equal(res.data.name, 'WiFi & Network')
})
// --- Entries ---
t.test('creates an entry with a secret', { tags: ['entry'] }, async () => {
const list = await t.api.get('/v1/vault/categories')
const catId = list.data.data[0].id
const res = await t.api.post(`/v1/vault/categories/${catId}/entries`, {
name: 'Store WiFi',
username: 'DemoUser',
url: 'http://192.168.1.1',
notes: 'Router admin panel',
secret: 'supersecretpassword123',
})
t.assert.status(res, 201)
t.assert.equal(res.data.name, 'Store WiFi')
t.assert.equal(res.data.username, 'DemoUser')
t.assert.equal(res.data.hasSecret, true)
// Secret value should NOT be in the response
t.assert.falsy(res.data.encryptedValue)
t.assert.falsy(res.data.iv)
t.assert.falsy(res.data.secret)
})
t.test('lists entries without revealing secrets', { tags: ['entry'] }, async () => {
const cats = await t.api.get('/v1/vault/categories')
const catId = cats.data.data[0].id
const res = await t.api.get(`/v1/vault/categories/${catId}/entries`)
t.assert.status(res, 200)
t.assert.greaterThan(res.data.data.length, 0)
t.assert.equal(res.data.data[0].hasSecret, true)
t.assert.falsy(res.data.data[0].encryptedValue)
})
t.test('reveals a secret', { tags: ['entry', 'reveal'] }, async () => {
const cats = await t.api.get('/v1/vault/categories')
const catId = cats.data.data[0].id
const entries = await t.api.get(`/v1/vault/categories/${catId}/entries`)
const entryId = entries.data.data[0].id
const res = await t.api.post(`/v1/vault/entries/${entryId}/reveal`)
t.assert.status(res, 200)
t.assert.equal(res.data.value, 'supersecretpassword123')
})
t.test('updates an entry with new secret', { tags: ['entry'] }, async () => {
const cats = await t.api.get('/v1/vault/categories')
const catId = cats.data.data[0].id
const entries = await t.api.get(`/v1/vault/categories/${catId}/entries`)
const entryId = entries.data.data[0].id
const res = await t.api.patch(`/v1/vault/entries/${entryId}`, {
name: 'Store WiFi (updated)',
secret: 'newsecret456',
})
t.assert.status(res, 200)
t.assert.equal(res.data.name, 'Store WiFi (updated)')
// Verify new secret
const reveal = await t.api.post(`/v1/vault/entries/${entryId}/reveal`)
t.assert.equal(reveal.data.value, 'newsecret456')
})
t.test('creates entry without secret', { tags: ['entry'] }, async () => {
const cats = await t.api.get('/v1/vault/categories')
const catId = cats.data.data[0].id
const res = await t.api.post(`/v1/vault/categories/${catId}/entries`, {
name: 'Vendor Contact',
notes: 'Call 555-1234',
})
t.assert.status(res, 201)
t.assert.equal(res.data.hasSecret, false)
})
t.test('deletes an entry', { tags: ['entry'] }, async () => {
const cats = await t.api.get('/v1/vault/categories')
const catId = cats.data.data[0].id
// Create one to delete
const created = await t.api.post(`/v1/vault/categories/${catId}/entries`, {
name: 'To Delete',
secret: 'deleteme',
})
const res = await t.api.del(`/v1/vault/entries/${created.data.id}`)
t.assert.status(res, 200)
const check = await t.api.get(`/v1/vault/entries/${created.data.id}`)
t.assert.status(check, 404)
})
// --- Master Password Change ---
t.test('changes master password and secrets still work', { tags: ['master'] }, async () => {
const newMaster = 'new-master-password-2024!'
const res = await t.api.post('/v1/vault/change-master-password', {
currentPassword: MASTER_PASSWORD,
newPassword: newMaster,
})
t.assert.status(res, 200)
// Verify existing secrets still decrypt
const cats = await t.api.get('/v1/vault/categories')
const catId = cats.data.data[0].id
const entries = await t.api.get(`/v1/vault/categories/${catId}/entries`)
const secretEntry = entries.data.data.find((e: any) => e.hasSecret)
if (secretEntry) {
const reveal = await t.api.post(`/v1/vault/entries/${secretEntry.id}/reveal`)
t.assert.status(reveal, 200)
t.assert.ok(reveal.data.value)
}
// Lock and re-unlock with new password
await t.api.post('/v1/vault/lock')
const unlock = await t.api.post('/v1/vault/unlock', { masterPassword: newMaster })
t.assert.status(unlock, 200)
// Old password should fail
await t.api.post('/v1/vault/lock')
const oldUnlock = await t.api.post('/v1/vault/unlock', { masterPassword: MASTER_PASSWORD })
t.assert.status(oldUnlock, 401)
// Re-unlock with new password for cleanup
await t.api.post('/v1/vault/unlock', { masterPassword: newMaster })
})
// --- Delete category ---
t.test('deletes a category and cascades entries', { tags: ['category'] }, async () => {
// Create a fresh category with an entry
const cat = await t.api.post('/v1/vault/categories', { name: 'To Delete Cat' })
await t.api.post(`/v1/vault/categories/${cat.data.id}/entries`, { name: 'Temp Entry', secret: 'temp' })
const res = await t.api.del(`/v1/vault/categories/${cat.data.id}`)
t.assert.status(res, 200)
const check = await t.api.get(`/v1/vault/categories/${cat.data.id}`)
t.assert.status(check, 404)
})
})