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:
Max
2026-01-26 12:28:43 +07:00
commit 3fbbb1a93b
812 changed files with 150531 additions and 0 deletions

View 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();
});
});

View 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();
});
});
});