Initial commit: Brachnha Insight project setup
- Next.js 14+ with App Router and TypeScript - Tailwind CSS and ShadCN UI styling - Zustand state management - Dexie.js for IndexedDB (local-first data) - Auth.js v5 for authentication - BMAD framework integration Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
52
tests/unit/services/secure-storage.test.ts
Normal file
52
tests/unit/services/secure-storage.test.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { SecureStorage } from '../../../src/services/secure-storage'; // Implementation doesn't exist yet
|
||||
|
||||
describe('SecureStorage Service', () => {
|
||||
const TEST_KEY = 'test_api_key_123';
|
||||
const STORAGE_KEY = 'llm_provider_config';
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('should save API key with basic encoding (not plain text)', () => {
|
||||
SecureStorage.saveKey(TEST_KEY);
|
||||
|
||||
// Direct access to localStorage to verify obfuscation
|
||||
const storedValue = localStorage.getItem(STORAGE_KEY);
|
||||
expect(storedValue).toBeDefined();
|
||||
expect(storedValue).not.toBe(TEST_KEY); // Should NOT be plain text
|
||||
expect(storedValue).not.toContain(TEST_KEY); // Should not contain the key
|
||||
});
|
||||
|
||||
it('should retrieve the original API key correctly', () => {
|
||||
SecureStorage.saveKey(TEST_KEY);
|
||||
|
||||
const retrievedKey = SecureStorage.getKey();
|
||||
expect(retrievedKey).toBe(TEST_KEY);
|
||||
});
|
||||
|
||||
it('should return null if no key is stored', () => {
|
||||
const retrievedKey = SecureStorage.getKey();
|
||||
expect(retrievedKey).toBeNull();
|
||||
});
|
||||
|
||||
it('should overwrite existing key when saving new one', () => {
|
||||
SecureStorage.saveKey('old_key');
|
||||
SecureStorage.saveKey('new_key');
|
||||
|
||||
expect(SecureStorage.getKey()).toBe('new_key');
|
||||
});
|
||||
|
||||
it('should clear key when requested', () => {
|
||||
SecureStorage.saveKey(TEST_KEY);
|
||||
SecureStorage.clearKey();
|
||||
|
||||
expect(SecureStorage.getKey()).toBeNull();
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBeNull();
|
||||
});
|
||||
});
|
||||
111
tests/unit/services/sync-manager.test.ts
Normal file
111
tests/unit/services/sync-manager.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { SyncManager } from '../../../src/services/sync-manager';
|
||||
|
||||
// Define hoisted mocks to be accessible inside vi.mock
|
||||
const { mockAdd, mockWhere, mockUpdate, mockDelete, mockSortBy } = vi.hoisted(() => {
|
||||
const mockSortBy = vi.fn();
|
||||
const mockEquals = vi.fn(() => ({ sortBy: mockSortBy }));
|
||||
const mockWhere = vi.fn(() => ({ equals: mockEquals }));
|
||||
return {
|
||||
mockAdd: vi.fn(),
|
||||
mockWhere,
|
||||
mockUpdate: vi.fn(),
|
||||
mockDelete: vi.fn(),
|
||||
mockSortBy
|
||||
};
|
||||
});
|
||||
|
||||
// Mock Dexie
|
||||
vi.mock('../../../src/lib/db', () => ({
|
||||
db: {
|
||||
syncQueue: {
|
||||
add: mockAdd,
|
||||
where: mockWhere,
|
||||
update: mockUpdate,
|
||||
delete: mockDelete,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('SyncManager Service', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Default return for sortBy (mockPendingItems)
|
||||
mockSortBy.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('queueAction', () => {
|
||||
it('should add action to syncQueue with pending status', async () => {
|
||||
// GIVEN: A draft payload
|
||||
const payload = { draftData: { title: 'Test', content: 'Content' } as any };
|
||||
mockAdd.mockResolvedValue(1);
|
||||
|
||||
// WHEN: queueAction is called
|
||||
const id = await SyncManager.queueAction('saveDraft', payload);
|
||||
|
||||
// THEN: It returns the new ID
|
||||
expect(id).toBe(1);
|
||||
|
||||
// AND: It calls db.syncQueue.add with correct data
|
||||
expect(mockAdd).toHaveBeenCalledWith({
|
||||
action: 'saveDraft',
|
||||
payload,
|
||||
status: 'pending',
|
||||
createdAt: expect.any(Number),
|
||||
retries: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processQueue', () => {
|
||||
it('should process pending items in order', async () => {
|
||||
// GIVEN: Pending items in queue
|
||||
const items = [
|
||||
{ id: 1, action: 'saveDraft', payload: {}, status: 'pending', retries: 0 },
|
||||
{ id: 2, action: 'deleteDraft', payload: { draftId: 123 }, status: 'pending', retries: 0 },
|
||||
];
|
||||
|
||||
mockSortBy.mockResolvedValue(items);
|
||||
|
||||
// WHEN: processQueue is called
|
||||
await SyncManager.processQueue();
|
||||
|
||||
// THEN: Items are processed
|
||||
// Verify update to 'processing'
|
||||
expect(mockUpdate).toHaveBeenCalledWith(1, { status: 'processing' });
|
||||
expect(mockUpdate).toHaveBeenCalledWith(2, { status: 'processing' });
|
||||
|
||||
// Verify deletion after success (assuming success path)
|
||||
expect(mockDelete).toHaveBeenCalledWith(1);
|
||||
expect(mockDelete).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('should handle execution errors and increment retries', async () => {
|
||||
// GIVEN: An item that will fail execution (invalid action)
|
||||
const item = {
|
||||
id: 1,
|
||||
action: 'invalidAction', // Will throw error in executeAction
|
||||
payload: {},
|
||||
status: 'pending',
|
||||
retries: 0
|
||||
};
|
||||
|
||||
mockSortBy.mockResolvedValue([item]);
|
||||
|
||||
// WHEN: processQueue is called
|
||||
await SyncManager.processQueue();
|
||||
|
||||
// THEN: Item should be updated to processing
|
||||
expect(mockUpdate).toHaveBeenCalledWith(1, { status: 'processing' });
|
||||
|
||||
// AND: Should be updated with incremented retry count (not deleted)
|
||||
expect(mockUpdate).toHaveBeenCalledWith(1, {
|
||||
status: 'pending',
|
||||
retries: 1
|
||||
});
|
||||
expect(mockDelete).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user