-
-
Notifications
You must be signed in to change notification settings - Fork 1k
feat: add testing infrastructure for backend and frontend #532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| [pytest] | ||
| testpaths = tests | ||
| asyncio_mode = auto | ||
| asyncio_default_fixture_loop_scope = function | ||
| python_files = test_*.py | ||
| python_classes = Test* | ||
| python_functions = test_* | ||
| addopts = -v --tb=short | ||
| filterwarnings = | ||
| ignore::DeprecationWarning | ||
| ignore::PendingDeprecationWarning | ||
| markers = | ||
| unit: Unit tests (fast, isolated) | ||
| integration: Integration tests (may require external services) | ||
| slow: Slow running tests | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Test package for SurfSense Backend |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| Shared test fixtures and configuration for SurfSense Backend tests. | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import uuid | ||||||||||||||||||||||||
| from unittest.mock import AsyncMock, MagicMock | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||
| from sqlalchemy.ext.asyncio import AsyncSession | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @pytest.fixture | ||||||||||||||||||||||||
| def mock_session() -> AsyncMock: | ||||||||||||||||||||||||
| """Create a mock async database session.""" | ||||||||||||||||||||||||
| session = AsyncMock(spec=AsyncSession) | ||||||||||||||||||||||||
| session.execute = AsyncMock() | ||||||||||||||||||||||||
| session.commit = AsyncMock() | ||||||||||||||||||||||||
| session.rollback = AsyncMock() | ||||||||||||||||||||||||
| session.refresh = AsyncMock() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| session.refresh = AsyncMock() | |
| session.refresh = AsyncMock() | |
| # Note: session.add() is synchronous even on AsyncSession, so we use MagicMock() here. |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using more specific type hints. Instead of list[dict], use list[dict[str, str]] to better specify the structure of chat messages with role and content fields.
| def sample_messages() -> list[dict]: | |
| def sample_messages() -> list[dict[str, str]]: |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sample_messages fixture returns a mutable list that could be modified by tests, potentially affecting subsequent tests. Consider using @pytest.fixture(scope="function") explicitly or returning a new copy of the list each time to ensure test isolation. Alternatively, document that tests should not mutate this fixture data.
| return [ | |
| {"role": "user", "content": "Hello, how are you?"}, | |
| {"role": "assistant", "content": "I'm doing well, thank you!"}, | |
| {"role": "user", "content": "What is the weather today?"}, | |
| ] | |
| messages = [ | |
| {"role": "user", "content": "Hello, how are you?"}, | |
| {"role": "assistant", "content": "I'm doing well, thank you!"}, | |
| {"role": "user", "content": "What is the weather today?"}, | |
| ] | |
| return messages.copy() |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a more specific type hint like dict[str, Any] or creating a TypedDict for the chat creation data structure to provide better type safety and documentation.
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to sample_messages, the sample_chat_create_data fixture returns a mutable dictionary. Consider returning a copy (return {...}) or documenting that tests should not mutate the returned data to prevent potential test interference.
| } | |
| }.copy() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,10 @@ | |
| "db:migrate": "drizzle-kit migrate", | ||
| "db:push": "drizzle-kit push", | ||
| "db:studio": "drizzle-kit studio", | ||
| "format:fix": "npx @biomejs/biome check --fix" | ||
| "format:fix": "npx @biomejs/biome check --fix", | ||
| "test": "vitest run", | ||
| "test:watch": "vitest", | ||
| "test:coverage": "vitest run --coverage" | ||
| }, | ||
| "dependencies": { | ||
| "@ai-sdk/react": "^1.2.12", | ||
|
|
@@ -103,17 +106,23 @@ | |
| "@eslint/eslintrc": "^3.3.1", | ||
| "@tailwindcss/postcss": "^4.1.11", | ||
| "@tailwindcss/typography": "^0.5.16", | ||
| "@testing-library/jest-dom": "^6.6.3", | ||
| "@testing-library/react": "^16.2.0", | ||
|
Comment on lines
+109
to
+110
|
||
| "@types/canvas-confetti": "^1.9.0", | ||
| "@types/node": "^20.19.9", | ||
| "@types/pg": "^8.15.5", | ||
| "@types/react": "^19.1.8", | ||
| "@types/react-dom": "^19.1.6", | ||
| "@vitejs/plugin-react": "^4.3.4", | ||
| "@vitest/coverage-v8": "^2.1.8", | ||
| "cross-env": "^7.0.3", | ||
| "drizzle-kit": "^0.31.5", | ||
| "eslint": "^9.32.0", | ||
| "eslint-config-next": "15.2.0", | ||
| "jsdom": "^25.0.1", | ||
| "tailwindcss": "^4.1.11", | ||
| "tsx": "^4.20.6", | ||
| "typescript": "^5.8.3" | ||
| "typescript": "^5.8.3", | ||
| "vitest": "^2.1.8" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,130 @@ | ||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Vitest test setup file. | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * This file runs before all tests and sets up the testing environment. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import "@testing-library/jest-dom/vitest"; | ||||||||||||||||||||
| import { vi } from "vitest"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Mock localStorage for auth-utils tests | ||||||||||||||||||||
| const localStorageMock = { | ||||||||||||||||||||
| store: {} as Record<string, string>, | ||||||||||||||||||||
| getItem: vi.fn((key: string) => localStorageMock.store[key] ?? null), | ||||||||||||||||||||
| setItem: vi.fn((key: string, value: string) => { | ||||||||||||||||||||
| localStorageMock.store[key] = value; | ||||||||||||||||||||
| }), | ||||||||||||||||||||
| removeItem: vi.fn((key: string) => { | ||||||||||||||||||||
| delete localStorageMock.store[key]; | ||||||||||||||||||||
| }), | ||||||||||||||||||||
| clear: vi.fn(() => { | ||||||||||||||||||||
| localStorageMock.store = {}; | ||||||||||||||||||||
| }), | ||||||||||||||||||||
|
||||||||||||||||||||
| }), | |
| }), | |
| get length() { | |
| return Object.keys(localStorageMock.store).length; | |
| }, | |
| key: vi.fn((index: number) => { | |
| const keys = Object.keys(localStorageMock.store); | |
| return keys[index] ?? null; | |
| }), |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The localStorage mock is missing the writable property in the Object.defineProperty configuration. Consider adding writable: true to allow tests to potentially reassign the mock if needed, maintaining consistency with the window.location mock pattern (line 37).
| value: localStorageMock, | |
| value: localStorageMock, | |
| writable: true, |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Next.js router mock returns static mock functions, but in the afterEach cleanup (line 124), only vi.clearAllMocks() is called without resetting the router state. Consider documenting that tests requiring specific router behavior should use vi.mocked() to access and configure these mocks, or add explicit router mock reset logic if tests need isolated router state.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||
| import { defineConfig } from "vitest/config"; | ||||||||||
| import react from "@vitejs/plugin-react"; | ||||||||||
| import path from "node:path"; | ||||||||||
|
|
||||||||||
| export default defineConfig({ | ||||||||||
| plugins: [react()], | ||||||||||
| test: { | ||||||||||
| environment: "jsdom", | ||||||||||
| globals: true, | ||||||||||
| setupFiles: ["./tests/setup.tsx"], | ||||||||||
| include: ["./tests/**/*.{test,spec}.{ts,tsx}"], | ||||||||||
| exclude: ["node_modules", ".next", "out"], | ||||||||||
| coverage: { | ||||||||||
| provider: "v8", | ||||||||||
| reporter: ["text", "json", "html", "lcov"], | ||||||||||
| include: ["lib/**/*.{ts,tsx}", "hooks/**/*.{ts,tsx}"], | ||||||||||
|
||||||||||
| include: ["lib/**/*.{ts,tsx}", "hooks/**/*.{ts,tsx}"], | |
| include: ["lib/**/*.{ts,tsx}", "hooks/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"], |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Setting PostCSS to an empty object effectively disables CSS processing in tests. While this can speed up tests, it may hide CSS-related issues. Consider documenting this decision or evaluating if CSS processing should be enabled for certain integration tests.
| // Disable PostCSS for tests | |
| // PostCSS is intentionally disabled for tests to speed up execution and avoid flakiness. | |
| // Note: This may hide CSS-related issues (e.g., missing PostCSS transformations). | |
| // If you have integration or end-to-end tests that rely on CSS, consider enabling PostCSS for those. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
asyncio_default_fixture_loop_scopeconfiguration option is deprecated in pytest-asyncio 0.23.0+. With pytest-asyncio>=0.24.0 specified in dependencies, consider usingasyncio_fixture_scopeinstead, or rely on the defaultfunctionscope which is already the standard behavior.