Skip to content

Commit 649ed24

Browse files
committed
test: add search integration tests
1 parent 5ca04f2 commit 649ed24

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

tests/integration/search.test.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* tests/integration/search.test.js
3+
*
4+
* Tests search routes:
5+
* GET /search/:q (global search across all channels)
6+
* GET /search/:channel/:q (search within a specific channel)
7+
*/
8+
9+
import { describe, it, expect, beforeEach } from 'vitest'
10+
import request from 'supertest'
11+
import { setup, resetDb, getApp, insertItem } from '../helpers/setup.js'
12+
13+
setup()
14+
15+
beforeEach(async () => {
16+
await resetDb()
17+
})
18+
19+
// ─────────────────────────────────────────────────────────────────────────────
20+
// GET /search/:q (global)
21+
// ─────────────────────────────────────────────────────────────────────────────
22+
23+
describe('GET /search/:q (global)', () => {
24+
it('returns an empty array when nothing matches', async () => {
25+
const res = await request(getApp()).get('/search/zzznomatch')
26+
27+
expect(res.status).toBe(200)
28+
expect(res.body).toEqual([])
29+
})
30+
31+
it('finds an item by content', async () => {
32+
await insertItem({ content: 'hello world', channel: 'general' })
33+
34+
const res = await request(getApp()).get('/search/hello')
35+
36+
expect(res.status).toBe(200)
37+
expect(res.body).toHaveLength(1)
38+
expect(res.body[0].content).toBe('hello world')
39+
})
40+
41+
it('is case insensitive', async () => {
42+
await insertItem({ content: 'Hello World', channel: 'general' })
43+
44+
const res = await request(getApp()).get('/search/hello')
45+
expect(res.body).toHaveLength(1)
46+
})
47+
48+
it('matches partial words', async () => {
49+
await insertItem({ content: 'deployment pipeline', channel: 'general' })
50+
51+
const res = await request(getApp()).get('/search/deploy')
52+
expect(res.body).toHaveLength(1)
53+
})
54+
55+
it('searches across all channels', async () => {
56+
await insertItem({ content: 'result one', channel: 'general' })
57+
await insertItem({ content: 'result two', channel: 'projects' })
58+
await insertItem({ content: 'result three', channel: 'assets' })
59+
60+
const res = await request(getApp()).get('/search/result')
61+
expect(res.body).toHaveLength(3)
62+
})
63+
64+
it('does not return items that do not match', async () => {
65+
await insertItem({ content: 'matching item', channel: 'general' })
66+
await insertItem({ content: 'unrelated item', channel: 'general' })
67+
68+
const res = await request(getApp()).get('/search/matching')
69+
expect(res.body).toHaveLength(1)
70+
expect(res.body[0].content).toBe('matching item')
71+
})
72+
73+
it('returns results ordered by channel then pinned then newest', async () => {
74+
await insertItem({ content: 'alpha result', channel: 'projects', created_at: 1000 })
75+
await insertItem({ content: 'beta result', channel: 'general', created_at: 2000 })
76+
await insertItem({ content: 'gamma result', channel: 'general', created_at: 3000, pinned: 1 })
77+
78+
const res = await request(getApp()).get('/search/result')
79+
const items = res.body
80+
81+
// general comes before projects alphabetically
82+
// within general: pinned first, then newest
83+
expect(items[0].content).toBe('gamma result') // general, pinned
84+
expect(items[1].content).toBe('beta result') // general, unpinned
85+
expect(items[2].content).toBe('alpha result') // projects
86+
})
87+
88+
it('finds items by filename', async () => {
89+
await insertItem({
90+
type: 'file',
91+
filename: 'report-2024.pdf',
92+
channel: 'assets'
93+
})
94+
95+
const res = await request(getApp()).get('/search/report')
96+
expect(res.body).toHaveLength(1)
97+
expect(res.body[0].filename).toBe('report-2024.pdf')
98+
})
99+
100+
it('returns empty array when db has no items', async () => {
101+
const res = await request(getApp()).get('/search/anything')
102+
expect(res.body).toEqual([])
103+
})
104+
})
105+
106+
// ─────────────────────────────────────────────────────────────────────────────
107+
// GET /search/:channel/:q (channel-scoped)
108+
// ─────────────────────────────────────────────────────────────────────────────
109+
110+
describe('GET /search/:channel/:q (channel-scoped)', () => {
111+
it('returns an empty array when nothing matches', async () => {
112+
const res = await request(getApp()).get('/search/general/zzznomatch')
113+
114+
expect(res.status).toBe(200)
115+
expect(res.body).toEqual([])
116+
})
117+
118+
it('finds an item by content within the channel', async () => {
119+
await insertItem({ content: 'scoped result', channel: 'general' })
120+
121+
const res = await request(getApp()).get('/search/general/scoped')
122+
expect(res.body).toHaveLength(1)
123+
expect(res.body[0].content).toBe('scoped result')
124+
})
125+
126+
it('does not return items from other channels', async () => {
127+
await insertItem({ content: 'shared keyword', channel: 'general' })
128+
await insertItem({ content: 'shared keyword', channel: 'projects' })
129+
130+
const res = await request(getApp()).get('/search/general/shared')
131+
expect(res.body).toHaveLength(1)
132+
expect(res.body[0].channel).toBe('general')
133+
})
134+
135+
it('finds items by filename within the channel', async () => {
136+
await insertItem({
137+
type: 'file',
138+
filename: 'budget.xlsx',
139+
channel: 'projects'
140+
})
141+
await insertItem({
142+
type: 'file',
143+
filename: 'budget.xlsx',
144+
channel: 'general'
145+
})
146+
147+
const res = await request(getApp()).get('/search/projects/budget')
148+
expect(res.body).toHaveLength(1)
149+
expect(res.body[0].channel).toBe('projects')
150+
})
151+
152+
it('returns pinned items before unpinned items', async () => {
153+
await insertItem({ content: 'keyword unpinned', channel: 'general', pinned: 0, created_at: 9000 })
154+
await insertItem({ content: 'keyword pinned', channel: 'general', pinned: 1, created_at: 1000 })
155+
156+
const res = await request(getApp()).get('/search/general/keyword')
157+
expect(res.body[0].content).toBe('keyword pinned')
158+
})
159+
160+
it('is case insensitive', async () => {
161+
await insertItem({ content: 'Deploy Script', channel: 'general' })
162+
163+
const res = await request(getApp()).get('/search/general/deploy')
164+
expect(res.body).toHaveLength(1)
165+
})
166+
167+
it('returns empty array for a channel with no items', async () => {
168+
const res = await request(getApp()).get('/search/assets/anything')
169+
expect(res.body).toEqual([])
170+
})
171+
})

0 commit comments

Comments
 (0)