Initial commit: DashCaddy v1.0

Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
2026-03-05 02:26:12 -08:00
commit f61e85d9a7
337 changed files with 75282 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
// credential-manager depends on keychain-manager and crypto-utils (both singletons).
// crypto-utils is already initialized via jest.setup.js env var.
// keychain-manager may not have OS keychain available in test env.
const fs = require('fs');
const os = require('os');
const path = require('path');
const credentialManager = require('../credential-manager');
// Use a temp file for credentials in tests
const TEMP_CREDS_FILE = path.join(os.tmpdir(), 'dashcaddy-test-creds.json');
beforeEach(() => {
// Reset singleton state
credentialManager.cache.clear();
// Clean up temp file
if (fs.existsSync(TEMP_CREDS_FILE)) {
fs.unlinkSync(TEMP_CREDS_FILE);
}
});
afterAll(() => {
if (fs.existsSync(TEMP_CREDS_FILE)) {
fs.unlinkSync(TEMP_CREDS_FILE);
}
});
describe('store', () => {
test('rejects invalid key (null)', async () => {
const result = await credentialManager.store(null, 'value');
expect(result).toBe(false);
});
test('rejects invalid key (non-string)', async () => {
const result = await credentialManager.store(123, 'value');
expect(result).toBe(false);
});
test('rejects invalid value (null)', async () => {
const result = await credentialManager.store('key', null);
expect(result).toBe(false);
});
test('rejects invalid value (non-string)', async () => {
const result = await credentialManager.store('key', 123);
expect(result).toBe(false);
});
test('stores credential and caches it', async () => {
const result = await credentialManager.store('test.key', 'secret123');
expect(result).toBe(true);
expect(credentialManager.cache.get('test.key')).toBe('secret123');
});
});
describe('retrieve', () => {
test('returns cached value when available', async () => {
credentialManager.cache.set('cached.key', 'cached-value');
const result = await credentialManager.retrieve('cached.key');
expect(result).toBe('cached-value');
});
test('returns null for non-existent key', async () => {
const result = await credentialManager.retrieve('nonexistent');
expect(result).toBeNull();
});
});
describe('store + retrieve round-trip', () => {
test('retrieves what was stored', async () => {
await credentialManager.store('roundtrip.key', 'my-secret');
// Clear cache to force file read
credentialManager.cache.clear();
const result = await credentialManager.retrieve('roundtrip.key');
expect(result).toBe('my-secret');
});
});
describe('delete', () => {
test('removes from cache', async () => {
await credentialManager.store('delete.key', 'value');
expect(credentialManager.cache.has('delete.key')).toBe(true);
await credentialManager.delete('delete.key');
expect(credentialManager.cache.has('delete.key')).toBe(false);
});
test('deleted credential cannot be retrieved', async () => {
await credentialManager.store('delete2.key', 'value');
await credentialManager.delete('delete2.key');
credentialManager.cache.clear();
const result = await credentialManager.retrieve('delete2.key');
expect(result).toBeNull();
});
});
describe('list', () => {
test('returns array of credential keys', async () => {
await credentialManager.store('list.a', 'val1');
await credentialManager.store('list.b', 'val2');
const keys = await credentialManager.list();
expect(keys).toContain('list.a');
expect(keys).toContain('list.b');
});
test('returns empty array when no credentials', async () => {
const keys = await credentialManager.list();
expect(Array.isArray(keys)).toBe(true);
});
});
describe('getMetadata', () => {
test('returns metadata for existing key', async () => {
await credentialManager.store('meta.key', 'val', { description: 'Test credential' });
const meta = await credentialManager.getMetadata('meta.key');
expect(meta).toEqual({ description: 'Test credential' });
});
test('returns null for non-existent key', async () => {
const meta = await credentialManager.getMetadata('nonexistent');
expect(meta).toBeNull();
});
});
describe('exportBackup / importBackup', () => {
test('export returns encrypted string', async () => {
await credentialManager.store('backup.key', 'backup-value');
const backup = await credentialManager.exportBackup();
expect(typeof backup).toBe('string');
expect(backup.split(':').length).toBe(3); // iv:authTag:ciphertext
});
test('import restores credentials from backup', async () => {
await credentialManager.store('backup.key', 'backup-value');
const backup = await credentialManager.exportBackup();
// Clear everything
await credentialManager.delete('backup.key');
credentialManager.cache.clear();
// Import backup
const result = await credentialManager.importBackup(backup);
expect(result).toBe(true);
// Verify restored
const keys = await credentialManager.list();
expect(keys).toContain('backup.key');
});
test('importBackup rejects unsupported version', async () => {
const cryptoUtils = require('../crypto-utils');
const badBackup = cryptoUtils.encrypt(JSON.stringify({ version: '99.0', credentials: {} }));
const result = await credentialManager.importBackup(badBackup);
expect(result).toBe(false);
});
});
describe('migrateToEncrypted', () => {
test('returns migration count', async () => {
const result = await credentialManager.migrateToEncrypted();
expect(result).toHaveProperty('migrated');
expect(result).toHaveProperty('skipped');
expect(result).toHaveProperty('total');
});
});