Skip to content

Commit b46dac9

Browse files
authored
Merge branch 'main' into claude/add-dashboard-analytics-01KKhDzpEF4cVjRvXB5U6kXB
2 parents 79dccc5 + b0d71f3 commit b46dac9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5306
-54
lines changed

api/src/__test__/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Search API Tests
2+
3+
This directory contains tests for the search functionality.
4+
5+
## Running Tests
6+
7+
To run all tests:
8+
```bash
9+
cd api
10+
npm test
11+
```
12+
13+
To run only search tests:
14+
```bash
15+
cd api
16+
npm test search.test.ts
17+
```
18+
19+
To run tests with coverage:
20+
```bash
21+
cd api
22+
npm test -- --coverage
23+
```
24+
25+
## Test Structure
26+
27+
### search.test.ts
28+
Tests for the SearchService layer:
29+
- **searchAll**: Tests searching across all entity types (boards, lists, cards)
30+
- **searchBoards**: Tests board-specific search
31+
- **searchLists**: Tests list-specific search with optional board filtering
32+
- **searchCards**: Tests card-specific search with optional board filtering
33+
- **Edge cases**: Empty results, missing parameters, limit handling
34+
35+
## Test Coverage
36+
37+
The tests mock the SearchRepository layer to isolate and test the SearchService business logic:
38+
- Query parameter handling
39+
- Type-based filtering (all, board, list, card)
40+
- Result aggregation and total calculation
41+
- Board-scoped filtering
42+
- Limit parameter passing
43+
44+
## Dependencies
45+
46+
Tests require:
47+
- `jest`: Test framework
48+
- `ts-jest`: TypeScript support for Jest
49+
- `@types/jest`: TypeScript definitions for Jest
50+
51+
These are already configured in `package.json` and `jest.config.ts`.
52+
53+
## CI/CD
54+
55+
These tests run automatically in the GitHub Actions workflow on:
56+
- Pull requests
57+
- Pushes to main branch
58+
- Manual workflow dispatch
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { SearchController } from "../modules/search/search.controller";
2+
import { SearchService } from "../modules/search/search.service";
3+
import { FastifyRequest, FastifyReply } from "fastify";
4+
import { SearchQuery } from "../modules/search/search.schema";
5+
6+
jest.mock("../modules/search/search.service");
7+
8+
const MockedSearchService = SearchService as jest.Mock<SearchService>;
9+
10+
describe("SearchController", () => {
11+
let searchController: SearchController;
12+
let searchService: jest.Mocked<SearchService>;
13+
let mockRequest: Partial<FastifyRequest>;
14+
let mockReply: Partial<FastifyReply>;
15+
16+
beforeEach(() => {
17+
searchService = new MockedSearchService() as jest.Mocked<SearchService>;
18+
searchController = new SearchController();
19+
// @ts-ignore
20+
searchController["searchService"] = searchService;
21+
22+
// Mock Fastify request and reply
23+
mockRequest = {
24+
query: {},
25+
};
26+
27+
mockReply = {
28+
status: jest.fn().mockReturnThis(),
29+
send: jest.fn().mockReturnThis(),
30+
};
31+
});
32+
33+
describe("searchController", () => {
34+
it("should return 200 with search results", async () => {
35+
const queryParams: SearchQuery = {
36+
query: "test",
37+
organization_id: "org-1",
38+
type: "all",
39+
};
40+
41+
const mockResults = {
42+
query: "test",
43+
total: 3,
44+
results: {
45+
boards: [
46+
{
47+
id: "board-1",
48+
title: "Test Board",
49+
description: "Description",
50+
organization_id: "org-1",
51+
created_at: new Date().toISOString(),
52+
updated_at: new Date().toISOString(),
53+
result_type: "board" as const,
54+
},
55+
],
56+
lists: [],
57+
cards: [],
58+
},
59+
};
60+
61+
mockRequest.query = queryParams;
62+
searchService.search.mockResolvedValue(mockResults);
63+
64+
await searchController.searchController(
65+
mockRequest as FastifyRequest<{ Querystring: SearchQuery }>,
66+
mockReply as FastifyReply,
67+
);
68+
69+
expect(searchService.search).toHaveBeenCalledWith(queryParams);
70+
expect(mockReply.status).toHaveBeenCalledWith(200);
71+
expect(mockReply.send).toHaveBeenCalledWith(mockResults);
72+
});
73+
74+
it("should return 500 on service error", async () => {
75+
const queryParams: SearchQuery = {
76+
query: "test",
77+
organization_id: "org-1",
78+
};
79+
80+
const error = new Error("Database connection failed");
81+
mockRequest.query = queryParams;
82+
searchService.search.mockRejectedValue(error);
83+
84+
await searchController.searchController(
85+
mockRequest as FastifyRequest<{ Querystring: SearchQuery }>,
86+
mockReply as FastifyReply,
87+
);
88+
89+
expect(mockReply.status).toHaveBeenCalledWith(500);
90+
expect(mockReply.send).toHaveBeenCalledWith({
91+
error: "Internal server error",
92+
message: "Database connection failed",
93+
});
94+
});
95+
96+
it("should handle non-Error exceptions", async () => {
97+
const queryParams: SearchQuery = {
98+
query: "test",
99+
organization_id: "org-1",
100+
};
101+
102+
mockRequest.query = queryParams;
103+
searchService.search.mockRejectedValue("Unknown error");
104+
105+
await searchController.searchController(
106+
mockRequest as FastifyRequest<{ Querystring: SearchQuery }>,
107+
mockReply as FastifyReply,
108+
);
109+
110+
expect(mockReply.status).toHaveBeenCalledWith(500);
111+
expect(mockReply.send).toHaveBeenCalledWith({
112+
error: "Internal server error",
113+
message: "Search failed",
114+
});
115+
});
116+
117+
it("should handle empty search results", async () => {
118+
const queryParams: SearchQuery = {
119+
query: "nonexistent",
120+
organization_id: "org-1",
121+
};
122+
123+
const mockResults = {
124+
query: "nonexistent",
125+
total: 0,
126+
results: {
127+
boards: [],
128+
lists: [],
129+
cards: [],
130+
},
131+
};
132+
133+
mockRequest.query = queryParams;
134+
searchService.search.mockResolvedValue(mockResults);
135+
136+
await searchController.searchController(
137+
mockRequest as FastifyRequest<{ Querystring: SearchQuery }>,
138+
mockReply as FastifyReply,
139+
);
140+
141+
expect(mockReply.status).toHaveBeenCalledWith(200);
142+
expect(mockReply.send).toHaveBeenCalledWith(mockResults);
143+
});
144+
145+
it("should handle search with all optional parameters", async () => {
146+
const queryParams: SearchQuery = {
147+
query: "test",
148+
organization_id: "org-1",
149+
board_id: "board-1",
150+
type: "card",
151+
limit: 10,
152+
};
153+
154+
const mockResults = {
155+
query: "test",
156+
total: 1,
157+
results: {
158+
boards: [],
159+
lists: [],
160+
cards: [
161+
{
162+
id: "card-1",
163+
title: "Test Card",
164+
description: "Description",
165+
status: "active",
166+
list_id: "list-1",
167+
list_title: "List",
168+
board_id: "board-1",
169+
board_title: "Board",
170+
order: 1,
171+
created_at: new Date().toISOString(),
172+
updated_at: new Date().toISOString(),
173+
result_type: "card" as const,
174+
},
175+
],
176+
},
177+
};
178+
179+
mockRequest.query = queryParams;
180+
searchService.search.mockResolvedValue(mockResults);
181+
182+
await searchController.searchController(
183+
mockRequest as FastifyRequest<{ Querystring: SearchQuery }>,
184+
mockReply as FastifyReply,
185+
);
186+
187+
expect(searchService.search).toHaveBeenCalledWith(queryParams);
188+
expect(mockReply.status).toHaveBeenCalledWith(200);
189+
expect(mockReply.send).toHaveBeenCalledWith(mockResults);
190+
});
191+
});
192+
});

0 commit comments

Comments
 (0)