From d66cf97422b7f73f9870b9b7752a5b3e7c86ef82 Mon Sep 17 00:00:00 2001 From: ForestMars Date: Tue, 18 Nov 2025 21:13:08 -0500 Subject: [PATCH 1/6] tests --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index fbfd720b..e3641aba 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@types/node": "^24.6.2", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", + "@types/supertest": "^6.0.3", "@vitejs/plugin-react-swc": "^4.1.0", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", @@ -96,6 +97,7 @@ "jsdom": "^27.0.0", "lovable-tagger": "^1.1.10", "postcss": "^8.5.6", + "supertest": "^7.1.4", "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7", "ts-node": "^10.9.2", From ddd719c8d4d1b1fd4e03984dfa5131ecb1bdc3f7 Mon Sep 17 00:00:00 2001 From: ForestMars Date: Tue, 18 Nov 2025 21:13:24 -0500 Subject: [PATCH 2/6] file header --- src/server/chatSyncApi.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/server/chatSyncApi.js b/src/server/chatSyncApi.js index 32f3afb1..75bed7ef 100644 --- a/src/server/chatSyncApi.js +++ b/src/server/chatSyncApi.js @@ -4,6 +4,8 @@ import http from 'http'; import { getAllChats, addOrUpdateChats, deleteChat, deleteChats } from './chatStore.js'; +const PORT = process.env.CHAT_SYNC_PORT || 4001; + // Utility: parse JSON body function parseBody(req) { return new Promise((resolve, reject) => { @@ -102,8 +104,12 @@ const server = http.createServer(async (req, res) => { } }); -// Start server -const PORT = process.env.CHAT_SYNC_PORT || 4001; -server.listen(PORT, () => { - console.log(`Chat sync API running on http://localhost:${PORT}`); -}); \ No newline at end of file +// Only start the server if this file is run directly +if (import.meta.url === `file://${process.argv[1]}`) { + server.listen(PORT, () => { + console.log(`Chat sync API running on http://localhost:${PORT}`); + }); +} + +// Export for testing +export { server }; \ No newline at end of file From ba4f0ae488f7580f98e6f7710332260ce82f95f1 Mon Sep 17 00:00:00 2001 From: ForestMars Date: Tue, 18 Nov 2025 21:13:43 -0500 Subject: [PATCH 3/6] vitest config --- vitest.config.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vitest.config.ts b/vitest.config.ts index 04565e6a..1bca0a2f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,3 +1,4 @@ +// vitest.config.ts import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react-swc' import path from 'path' @@ -6,19 +7,20 @@ export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', - setupFiles: ['./src/test/setup.ts'], + setupFiles: ['./src/tests/setup.ts'], globals: true, css: true, coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ + 'src/tests/', + 'src/__tests__/**/*', 'node_modules/', - 'src/test/', '**/*.d.ts', '**/*.config.*', - 'dist/', - 'coverage/' + 'coverage/', + 'dist/' ] } }, @@ -27,4 +29,5 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, -}) \ No newline at end of file +}) + From ba68de07c2508babe57d9a772f8ffbd1282eb412 Mon Sep 17 00:00:00 2001 From: ForestMars Date: Tue, 18 Nov 2025 21:14:08 -0500 Subject: [PATCH 4/6] lock --- package-lock.json | 171 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/package-lock.json b/package-lock.json index 50067bca..402a4338 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "@types/node": "^24.6.2", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", + "@types/supertest": "^6.0.3", "@vitejs/plugin-react-swc": "^4.1.0", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", @@ -84,6 +85,7 @@ "jsdom": "^27.0.0", "lovable-tagger": "^1.1.10", "postcss": "^8.5.6", + "supertest": "^7.1.4", "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7", "ts-node": "^10.9.2", @@ -1832,6 +1834,19 @@ "zod": "^3.24.1" } }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1926,6 +1941,16 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4065,6 +4090,13 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -4149,6 +4181,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.6.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz", @@ -4178,6 +4217,30 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -4807,6 +4870,13 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -5380,6 +5450,16 @@ "node": ">= 6" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5430,6 +5510,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -5804,6 +5891,17 @@ "react": ">=16" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -6448,6 +6546,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -6666,6 +6771,24 @@ "node": ">=12.20.0" } }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9876,6 +9999,54 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/superagent": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.4", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.3" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", From fa3e553755badccc3aa2ed0db3bcac16e1c949ab Mon Sep 17 00:00:00 2001 From: ForestMars Date: Tue, 18 Nov 2025 21:14:19 -0500 Subject: [PATCH 5/6] tests --- src/tests/mocks/api.ts | 25 +++++ src/tests/mocks/providers.ts | 72 ++++++++++++ src/tests/server/chatSync.unit.test.ts | 145 +++++++++++++++++++++++++ src/tests/setup.ts | 41 +++++++ src/tests/utils.tsx | 34 ++++++ 5 files changed, 317 insertions(+) create mode 100644 src/tests/mocks/api.ts create mode 100644 src/tests/mocks/providers.ts create mode 100644 src/tests/server/chatSync.unit.test.ts create mode 100644 src/tests/setup.ts create mode 100644 src/tests/utils.tsx diff --git a/src/tests/mocks/api.ts b/src/tests/mocks/api.ts new file mode 100644 index 00000000..881be3c6 --- /dev/null +++ b/src/tests/mocks/api.ts @@ -0,0 +1,25 @@ +import { vi } from 'vitest' + +export const mockApiCall = vi.fn().mockResolvedValue({ + content: 'Mock AI response', + timestamp: new Date(), + provider: 'openai' +}) + +export const mockApiCallError = vi.fn().mockRejectedValue(new Error('API Error')) + +export const mockApiCallTimeout = vi.fn().mockImplementation(() => + new Promise((resolve) => setTimeout(() => resolve({ + content: 'Delayed response', + timestamp: new Date(), + provider: 'openai' + }), 1000)) +) + +export const mockApiCallWithProvider = vi.fn().mockImplementation((provider: string) => + Promise.resolve({ + content: `Mock response from ${provider}`, + timestamp: new Date(), + provider + }) +) \ No newline at end of file diff --git a/src/tests/mocks/providers.ts b/src/tests/mocks/providers.ts new file mode 100644 index 00000000..677c24b2 --- /dev/null +++ b/src/tests/mocks/providers.ts @@ -0,0 +1,72 @@ +import type { Provider } from '@/components/ChatInterface' + +export const mockProviders: Provider[] = [ + { + id: 'openai', + name: 'OpenAI', + apiKeys: [ + { id: '1', name: 'Personal Key', key: 'sk-test-1234567890abcdef' }, + { id: '2', name: 'Work Key', key: 'sk-test-0987654321fedcba' } + ], + models: ['gpt-4.1-2025-04-14', 'gpt-4o', 'gpt-4o-mini'], + defaultModel: 'gpt-4.1-2025-04-14' + }, + { + id: 'anthropic', + name: 'Anthropic', + apiKeys: [ + { id: '3', name: 'Claude Key', key: 'sk-ant-test-1234567890abcdef' } + ], + models: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514', 'claude-3-5-haiku-20241022'], + defaultModel: 'claude-sonnet-4-20250514' + }, + { + id: 'google', + name: 'Google', + apiKeys: [], + models: ['gemini-pro', 'gemini-pro-vision'], + defaultModel: 'gemini-pro' + }, + { + id: 'ollama', + name: 'Ollama (Local)', + apiKeys: [], + models: ['llama3.2', 'llama3.2:3b', 'llama3.2:8b', 'llama3.2:70b', 'mistral', 'codellama', 'phi3'], + defaultModel: 'llama3.2', + isLocal: true, + baseUrl: 'http://localhost:11434' + } +] + +export const mockEmptyProviders: Provider[] = [ + { + id: 'openai', + name: 'OpenAI', + apiKeys: [], + models: ['gpt-4.1-2025-04-14', 'gpt-4o', 'gpt-4o-mini'], + defaultModel: 'gpt-4.1-2025-04-14' + }, + { + id: 'anthropic', + name: 'Anthropic', + apiKeys: [], + models: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514', 'claude-3-5-haiku-20241022'], + defaultModel: 'claude-sonnet-4-20250514' + }, + { + id: 'google', + name: 'Google', + apiKeys: [], + models: ['gemini-pro', 'gemini-pro-vision'], + defaultModel: 'gemini-pro' + }, + { + id: 'ollama', + name: 'Ollama (Local)', + apiKeys: [], + models: ['llama3.2', 'llama3.2:3b', 'llama3.2:8b', 'llama3.2:70b', 'mistral', 'codellama', 'phi3'], + defaultModel: 'llama3.2', + isLocal: true, + baseUrl: 'http://localhost:11434' + } +] \ No newline at end of file diff --git a/src/tests/server/chatSync.unit.test.ts b/src/tests/server/chatSync.unit.test.ts new file mode 100644 index 00000000..34f67039 --- /dev/null +++ b/src/tests/server/chatSync.unit.test.ts @@ -0,0 +1,145 @@ +// src/tests/services/chatSync.unit.test.ts +import { describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; + +// Import the server instance directly (not starting it on a port) +// We need to export the server from chatSyncApi.js first +import { server } from '../../server/chatSyncApi.js'; + +// Create supertest agent - NO network ports used +const agent = request(server); + +describe('Chat Sync API - Unit Tests', () => { + let createdChatId: string; + + // Clean up: delete any test chats before each test + beforeEach(async () => { + // Get all chats + const response = await agent.get('/fetchChats'); + const chats = response.body; + + // Delete any existing test chats + const testChatIds = chats + .filter((chat: any) => chat.id?.startsWith('test_')) + .map((chat: any) => chat.id); + + if (testChatIds.length > 0) { + await agent + .post('/deleteChats') + .send({ chatIds: testChatIds }); + } + }); + + it('should create, delete, and verify removal of a chat', async () => { + // --- 1. CREATE (Push a new chat to the server) --- + const newChat = { + id: `test_conv_${Date.now()}`, + title: 'Test Chat', + messages: [], + createdAt: Date.now(), + updatedAt: Date.now() + }; + + const createResponse = await agent + .post('/pushChats') + .send({ chats: [newChat] }); + + expect(createResponse.statusCode).toBe(200); + expect(createResponse.body).toHaveProperty('ok', true); + createdChatId = newChat.id; + + // Verify it was created + const fetchAfterCreate = await agent.get('/fetchChats'); + expect(fetchAfterCreate.statusCode).toBe(200); + const chatExists = fetchAfterCreate.body.some((chat: any) => chat.id === createdChatId); + expect(chatExists).toBe(true); + + // --- 2. DELETE --- + const deleteResponse = await agent + .delete(`/deleteChat/${createdChatId}`); + + expect(deleteResponse.statusCode).toBe(200); + expect(deleteResponse.body).toHaveProperty('ok', true); + expect(deleteResponse.body).toHaveProperty('deleted', true); + + // --- 3. CONFIRM REMOVAL --- + const fetchAfterDelete = await agent.get('/fetchChats'); + expect(fetchAfterDelete.statusCode).toBe(200); + + const isChatStillPresent = fetchAfterDelete.body.some( + (chat: any) => chat.id === createdChatId + ); + expect(isChatStillPresent).toBe(false); + }); + + it('should handle deleting non-existent chat', async () => { + const response = await agent + .delete('/deleteChat/nonexistent_id_12345'); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('ok', true); + expect(response.body).toHaveProperty('deleted', false); + }); + + it('should handle missing chat ID in delete request', async () => { + const response = await agent + .delete('/deleteChat/'); + + expect(response.statusCode).toBe(400); + }); + + it('should delete multiple chats', async () => { + // Create test chats + const testChats = [ + { + id: `test_1_${Date.now()}`, + title: 'Test 1', + messages: [], + createdAt: Date.now(), + updatedAt: Date.now() + }, + { + id: `test_2_${Date.now()}`, + title: 'Test 2', + messages: [], + createdAt: Date.now(), + updatedAt: Date.now() + } + ]; + + await agent + .post('/pushChats') + .send({ chats: testChats }); + + // Delete them + const response = await agent + .post('/deleteChats') + .send({ chatIds: testChats.map(c => c.id) }); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('ok', true); + expect(response.body).toHaveProperty('deletedCount', 2); + + // Verify deletion + const fetchResponse = await agent.get('/fetchChats'); + const remainingIds = fetchResponse.body.map((chat: any) => chat.id); + + testChats.forEach(chat => { + expect(remainingIds).not.toContain(chat.id); + }); + }); + + it('should fetch all chats', async () => { + const response = await agent.get('/fetchChats'); + + expect(response.statusCode).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + it('should handle CORS preflight', async () => { + const response = await agent.options('/fetchChats'); + + expect(response.statusCode).toBe(204); + expect(response.headers['access-control-allow-origin']).toBe('*'); + }); +}); \ No newline at end of file diff --git a/src/tests/setup.ts b/src/tests/setup.ts new file mode 100644 index 00000000..7c942b03 --- /dev/null +++ b/src/tests/setup.ts @@ -0,0 +1,41 @@ +import '@testing-library/jest-dom' +import { vi } from 'vitest' + +// Mock window.matchMedia for use-mobile hook +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}) + +// Mock ResizeObserver +global.ResizeObserver = vi.fn().mockImplementation(() => ({ + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), +})) + +// Mock IntersectionObserver +global.IntersectionObserver = vi.fn().mockImplementation(() => ({ + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), +})) + +// Mock scrollIntoView +Element.prototype.scrollIntoView = vi.fn() + +// Mock getComputedStyle +Object.defineProperty(window, 'getComputedStyle', { + value: () => ({ + getPropertyValue: () => '', + }), +}) \ No newline at end of file diff --git a/src/tests/utils.tsx b/src/tests/utils.tsx new file mode 100644 index 00000000..32015575 --- /dev/null +++ b/src/tests/utils.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { render, RenderOptions } from '@testing-library/react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { BrowserRouter } from 'react-router-dom' + +// Create a custom render function that includes providers +const AllTheProviders = ({ children }: { children: React.ReactNode }) => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + return ( + + + {children} + + + ) +} + +const customRender = ( + ui: React.ReactElement, + options?: Omit +) => render(ui, { wrapper: AllTheProviders, ...options }) + +// Re-export everything +export * from '@testing-library/react' + +// Override render method +export { customRender as render } \ No newline at end of file From 21e7f64a3ad103cbb8ce12bf4017c16d9c215cc9 Mon Sep 17 00:00:00 2001 From: ForestMars Date: Tue, 18 Nov 2025 21:16:08 -0500 Subject: [PATCH 6/6] deleted --- .gitignore | 1 - .../results.json | 1 - src/test/mocks/api.ts | 25 ------- src/test/mocks/providers.ts | 72 ------------------- src/test/setup.ts | 41 ----------- src/test/utils.tsx | 34 --------- 6 files changed, 174 deletions(-) delete mode 100644 node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json delete mode 100644 src/test/mocks/api.ts delete mode 100644 src/test/mocks/providers.ts delete mode 100644 src/test/setup.ts delete mode 100644 src/test/utils.tsx diff --git a/.gitignore b/.gitignore index 09ec4783..f5a35665 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ **/.DS_Store .notion -node_modules/.vite/deps **/.auth #.env diff --git a/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json b/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json deleted file mode 100644 index 79ea6c14..00000000 --- a/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"3.2.4","results":[[":src/__tests__/hooks/use-toast.test.tsx",{"duration":91.43616600000007,"failed":true}],[":src/__tests__/hooks/use-mobile.test.tsx",{"duration":60.4452500000001,"failed":true}],[":src/__tests__/utils/utils.test.ts",{"duration":22.88345800000002,"failed":true}],[":src/services/__tests__/stateManagement.test.ts",{"duration":77.12820899999997,"failed":true}],[":src/services/__tests__/storage.test.ts",{"duration":50.88487500000008,"failed":true}],[":src/components/__tests__/ConversationSidebar.test.tsx",{"duration":1353.1905420000003,"failed":true}],[":src/__tests__/components/ConversationSidebar.test.tsx",{"duration":151.526292,"failed":true}],[":src/__tests__/components/ChatInterface.test.tsx",{"duration":113.44941599999993,"failed":true}],[":src/__tests__/components/SettingsPanel.test.tsx",{"duration":144.17041700000004,"failed":true}]]} \ No newline at end of file diff --git a/src/test/mocks/api.ts b/src/test/mocks/api.ts deleted file mode 100644 index 881be3c6..00000000 --- a/src/test/mocks/api.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { vi } from 'vitest' - -export const mockApiCall = vi.fn().mockResolvedValue({ - content: 'Mock AI response', - timestamp: new Date(), - provider: 'openai' -}) - -export const mockApiCallError = vi.fn().mockRejectedValue(new Error('API Error')) - -export const mockApiCallTimeout = vi.fn().mockImplementation(() => - new Promise((resolve) => setTimeout(() => resolve({ - content: 'Delayed response', - timestamp: new Date(), - provider: 'openai' - }), 1000)) -) - -export const mockApiCallWithProvider = vi.fn().mockImplementation((provider: string) => - Promise.resolve({ - content: `Mock response from ${provider}`, - timestamp: new Date(), - provider - }) -) \ No newline at end of file diff --git a/src/test/mocks/providers.ts b/src/test/mocks/providers.ts deleted file mode 100644 index 677c24b2..00000000 --- a/src/test/mocks/providers.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Provider } from '@/components/ChatInterface' - -export const mockProviders: Provider[] = [ - { - id: 'openai', - name: 'OpenAI', - apiKeys: [ - { id: '1', name: 'Personal Key', key: 'sk-test-1234567890abcdef' }, - { id: '2', name: 'Work Key', key: 'sk-test-0987654321fedcba' } - ], - models: ['gpt-4.1-2025-04-14', 'gpt-4o', 'gpt-4o-mini'], - defaultModel: 'gpt-4.1-2025-04-14' - }, - { - id: 'anthropic', - name: 'Anthropic', - apiKeys: [ - { id: '3', name: 'Claude Key', key: 'sk-ant-test-1234567890abcdef' } - ], - models: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514', 'claude-3-5-haiku-20241022'], - defaultModel: 'claude-sonnet-4-20250514' - }, - { - id: 'google', - name: 'Google', - apiKeys: [], - models: ['gemini-pro', 'gemini-pro-vision'], - defaultModel: 'gemini-pro' - }, - { - id: 'ollama', - name: 'Ollama (Local)', - apiKeys: [], - models: ['llama3.2', 'llama3.2:3b', 'llama3.2:8b', 'llama3.2:70b', 'mistral', 'codellama', 'phi3'], - defaultModel: 'llama3.2', - isLocal: true, - baseUrl: 'http://localhost:11434' - } -] - -export const mockEmptyProviders: Provider[] = [ - { - id: 'openai', - name: 'OpenAI', - apiKeys: [], - models: ['gpt-4.1-2025-04-14', 'gpt-4o', 'gpt-4o-mini'], - defaultModel: 'gpt-4.1-2025-04-14' - }, - { - id: 'anthropic', - name: 'Anthropic', - apiKeys: [], - models: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514', 'claude-3-5-haiku-20241022'], - defaultModel: 'claude-sonnet-4-20250514' - }, - { - id: 'google', - name: 'Google', - apiKeys: [], - models: ['gemini-pro', 'gemini-pro-vision'], - defaultModel: 'gemini-pro' - }, - { - id: 'ollama', - name: 'Ollama (Local)', - apiKeys: [], - models: ['llama3.2', 'llama3.2:3b', 'llama3.2:8b', 'llama3.2:70b', 'mistral', 'codellama', 'phi3'], - defaultModel: 'llama3.2', - isLocal: true, - baseUrl: 'http://localhost:11434' - } -] \ No newline at end of file diff --git a/src/test/setup.ts b/src/test/setup.ts deleted file mode 100644 index 7c942b03..00000000 --- a/src/test/setup.ts +++ /dev/null @@ -1,41 +0,0 @@ -import '@testing-library/jest-dom' -import { vi } from 'vitest' - -// Mock window.matchMedia for use-mobile hook -Object.defineProperty(window, 'matchMedia', { - writable: true, - value: vi.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addListener: vi.fn(), // deprecated - removeListener: vi.fn(), // deprecated - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })), -}) - -// Mock ResizeObserver -global.ResizeObserver = vi.fn().mockImplementation(() => ({ - observe: vi.fn(), - unobserve: vi.fn(), - disconnect: vi.fn(), -})) - -// Mock IntersectionObserver -global.IntersectionObserver = vi.fn().mockImplementation(() => ({ - observe: vi.fn(), - unobserve: vi.fn(), - disconnect: vi.fn(), -})) - -// Mock scrollIntoView -Element.prototype.scrollIntoView = vi.fn() - -// Mock getComputedStyle -Object.defineProperty(window, 'getComputedStyle', { - value: () => ({ - getPropertyValue: () => '', - }), -}) \ No newline at end of file diff --git a/src/test/utils.tsx b/src/test/utils.tsx deleted file mode 100644 index 32015575..00000000 --- a/src/test/utils.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import { render, RenderOptions } from '@testing-library/react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { BrowserRouter } from 'react-router-dom' - -// Create a custom render function that includes providers -const AllTheProviders = ({ children }: { children: React.ReactNode }) => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }) - - return ( - - - {children} - - - ) -} - -const customRender = ( - ui: React.ReactElement, - options?: Omit -) => render(ui, { wrapper: AllTheProviders, ...options }) - -// Re-export everything -export * from '@testing-library/react' - -// Override render method -export { customRender as render } \ No newline at end of file