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: 'ForteMusic', 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, 'ForteMusic') 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) }) })