@dpesch/mantisbt-mcp-server 1.10.3 → 1.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.json +1 -1
  3. package/server.json +2 -2
  4. package/.gitea/PULL_REQUEST_TEMPLATE.md +0 -17
  5. package/.gitea/workflows/ci.yml +0 -95
  6. package/.github/workflows/ci.yml +0 -32
  7. package/scripts/hooks/pre-push.mjs +0 -220
  8. package/scripts/init.mjs +0 -95
  9. package/scripts/record-fixtures.ts +0 -160
  10. package/tests/cache.test.ts +0 -265
  11. package/tests/client.test.ts +0 -372
  12. package/tests/config.test.ts +0 -263
  13. package/tests/fixtures/get_current_user.json +0 -40
  14. package/tests/fixtures/get_issue.json +0 -133
  15. package/tests/fixtures/get_issue_enums.json +0 -67
  16. package/tests/fixtures/get_issue_fields_sample.json +0 -76
  17. package/tests/fixtures/get_project_categories.json +0 -157
  18. package/tests/fixtures/get_project_versions.json +0 -3
  19. package/tests/fixtures/get_project_versions_with_data.json +0 -52
  20. package/tests/fixtures/list_issues.json +0 -95
  21. package/tests/fixtures/list_projects.json +0 -65
  22. package/tests/helpers/mock-server.ts +0 -166
  23. package/tests/helpers/search-mocks.ts +0 -84
  24. package/tests/prompts/prompts.test.ts +0 -242
  25. package/tests/resources/resources.test.ts +0 -309
  26. package/tests/search/embedder.test.ts +0 -81
  27. package/tests/search/highlight.test.ts +0 -129
  28. package/tests/search/store.test.ts +0 -193
  29. package/tests/search/sync.test.ts +0 -249
  30. package/tests/search/tools.test.ts +0 -661
  31. package/tests/tools/config.test.ts +0 -212
  32. package/tests/tools/files.test.ts +0 -343
  33. package/tests/tools/issues.test.ts +0 -1180
  34. package/tests/tools/metadata.test.ts +0 -509
  35. package/tests/tools/monitors.test.ts +0 -101
  36. package/tests/tools/projects.test.ts +0 -338
  37. package/tests/tools/relationships.test.ts +0 -177
  38. package/tests/tools/string-coercion.test.ts +0 -317
  39. package/tests/tools/users.test.ts +0 -62
  40. package/tests/utils/date-filter.test.ts +0 -169
  41. package/tests/version-hint.test.ts +0 -230
  42. package/tsconfig.build.json +0 -8
  43. package/vitest.config.ts +0 -8
@@ -1,242 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { registerPrompts } from '../../src/prompts/index.js';
3
- import { MockMcpServer } from '../helpers/mock-server.js';
4
-
5
- let mockServer: MockMcpServer;
6
-
7
- beforeEach(() => {
8
- mockServer = new MockMcpServer();
9
- registerPrompts(mockServer as never);
10
- });
11
-
12
- // ---------------------------------------------------------------------------
13
- // Registration
14
- // ---------------------------------------------------------------------------
15
-
16
- describe('prompt registration', () => {
17
- it('registers create-bug-report', () => {
18
- expect(mockServer.hasPromptRegistered('create-bug-report')).toBe(true);
19
- });
20
-
21
- it('registers create-feature-request', () => {
22
- expect(mockServer.hasPromptRegistered('create-feature-request')).toBe(true);
23
- });
24
-
25
- it('registers summarize-issue', () => {
26
- expect(mockServer.hasPromptRegistered('summarize-issue')).toBe(true);
27
- });
28
-
29
- it('registers project-status', () => {
30
- expect(mockServer.hasPromptRegistered('project-status')).toBe(true);
31
- });
32
- });
33
-
34
- // ---------------------------------------------------------------------------
35
- // create-bug-report
36
- // ---------------------------------------------------------------------------
37
-
38
- describe('create-bug-report', () => {
39
- it('returns a single user message', () => {
40
- const result = mockServer.callPrompt('create-bug-report', {
41
- project_id: 1,
42
- category: 'General',
43
- summary: 'Login fails',
44
- description: 'Cannot log in after password reset',
45
- });
46
-
47
- expect(result.messages).toHaveLength(1);
48
- expect(result.messages[0]!.role).toBe('user');
49
- expect(result.messages[0]!.content.type).toBe('text');
50
- });
51
-
52
- it('includes project_id, category, summary, and description in the text', () => {
53
- const result = mockServer.callPrompt('create-bug-report', {
54
- project_id: 7,
55
- category: 'Authentication',
56
- summary: 'Session expires too early',
57
- description: 'Session is invalidated after 5 minutes of inactivity',
58
- });
59
-
60
- const text = result.messages[0]!.content.text;
61
- expect(text).toContain('project 7');
62
- expect(text).toContain('Authentication');
63
- expect(text).toContain('Session expires too early');
64
- expect(text).toContain('Session is invalidated after 5 minutes');
65
- });
66
-
67
- it('includes optional steps_to_reproduce when provided', () => {
68
- const result = mockServer.callPrompt('create-bug-report', {
69
- project_id: 1,
70
- category: 'General',
71
- summary: 'Bug',
72
- description: 'Desc',
73
- steps_to_reproduce: '1. Open app\n2. Click login',
74
- });
75
-
76
- expect(result.messages[0]!.content.text).toContain('Steps to reproduce');
77
- expect(result.messages[0]!.content.text).toContain('1. Open app');
78
- });
79
-
80
- it('omits optional sections when not provided', () => {
81
- const result = mockServer.callPrompt('create-bug-report', {
82
- project_id: 1,
83
- category: 'General',
84
- summary: 'Bug',
85
- description: 'Desc',
86
- });
87
-
88
- const text = result.messages[0]!.content.text;
89
- expect(text).not.toContain('Steps to reproduce');
90
- expect(text).not.toContain('Expected behavior');
91
- expect(text).not.toContain('Actual behavior');
92
- expect(text).not.toContain('Environment');
93
- });
94
-
95
- it('includes all optional fields when provided', () => {
96
- const result = mockServer.callPrompt('create-bug-report', {
97
- project_id: 1,
98
- category: 'UI',
99
- summary: 'Button broken',
100
- description: 'Save button does nothing',
101
- steps_to_reproduce: 'Click Save',
102
- expected: 'Form is saved',
103
- actual: 'Nothing happens',
104
- environment: 'Chrome 120, Windows 11',
105
- });
106
-
107
- const text = result.messages[0]!.content.text;
108
- expect(text).toContain('Steps to reproduce');
109
- expect(text).toContain('Expected behavior');
110
- expect(text).toContain('Actual behavior');
111
- expect(text).toContain('Environment');
112
- expect(text).toContain('Chrome 120');
113
- });
114
-
115
- it('mentions create_issue tool', () => {
116
- const result = mockServer.callPrompt('create-bug-report', {
117
- project_id: 1,
118
- category: 'General',
119
- summary: 'Bug',
120
- description: 'Desc',
121
- });
122
-
123
- expect(result.messages[0]!.content.text).toContain('create_issue');
124
- });
125
- });
126
-
127
- // ---------------------------------------------------------------------------
128
- // create-feature-request
129
- // ---------------------------------------------------------------------------
130
-
131
- describe('create-feature-request', () => {
132
- it('returns a single user message', () => {
133
- const result = mockServer.callPrompt('create-feature-request', {
134
- project_id: 2,
135
- category: 'General',
136
- summary: 'Dark mode',
137
- description: 'Add a dark mode toggle to the UI',
138
- });
139
-
140
- expect(result.messages).toHaveLength(1);
141
- expect(result.messages[0]!.role).toBe('user');
142
- });
143
-
144
- it('includes project_id, category, summary, and description', () => {
145
- const result = mockServer.callPrompt('create-feature-request', {
146
- project_id: 5,
147
- category: 'UX',
148
- summary: 'Export to CSV',
149
- description: 'Allow exporting issue lists as CSV',
150
- });
151
-
152
- const text = result.messages[0]!.content.text;
153
- expect(text).toContain('project 5');
154
- expect(text).toContain('UX');
155
- expect(text).toContain('Export to CSV');
156
- expect(text).toContain('Allow exporting issue lists');
157
- });
158
-
159
- it('includes use_case when provided', () => {
160
- const result = mockServer.callPrompt('create-feature-request', {
161
- project_id: 1,
162
- category: 'General',
163
- summary: 'Feature',
164
- description: 'Desc',
165
- use_case: 'Needed for monthly reporting',
166
- });
167
-
168
- expect(result.messages[0]!.content.text).toContain('Needed for monthly reporting');
169
- });
170
-
171
- it('omits use_case section when not provided', () => {
172
- const result = mockServer.callPrompt('create-feature-request', {
173
- project_id: 1,
174
- category: 'General',
175
- summary: 'Feature',
176
- description: 'Desc',
177
- });
178
-
179
- expect(result.messages[0]!.content.text).not.toContain('Use case');
180
- });
181
-
182
- it('mentions create_issue tool', () => {
183
- const result = mockServer.callPrompt('create-feature-request', {
184
- project_id: 1,
185
- category: 'General',
186
- summary: 'Feature',
187
- description: 'Desc',
188
- });
189
-
190
- expect(result.messages[0]!.content.text).toContain('create_issue');
191
- });
192
- });
193
-
194
- // ---------------------------------------------------------------------------
195
- // summarize-issue
196
- // ---------------------------------------------------------------------------
197
-
198
- describe('summarize-issue', () => {
199
- it('returns a single user message', () => {
200
- const result = mockServer.callPrompt('summarize-issue', { issue_id: 42 });
201
-
202
- expect(result.messages).toHaveLength(1);
203
- expect(result.messages[0]!.role).toBe('user');
204
- });
205
-
206
- it('includes the issue ID in the text', () => {
207
- const result = mockServer.callPrompt('summarize-issue', { issue_id: 1234 });
208
-
209
- expect(result.messages[0]!.content.text).toContain('1234');
210
- });
211
-
212
- it('mentions get_issue tool', () => {
213
- const result = mockServer.callPrompt('summarize-issue', { issue_id: 1 });
214
-
215
- expect(result.messages[0]!.content.text).toContain('get_issue');
216
- });
217
- });
218
-
219
- // ---------------------------------------------------------------------------
220
- // project-status
221
- // ---------------------------------------------------------------------------
222
-
223
- describe('project-status', () => {
224
- it('returns a single user message', () => {
225
- const result = mockServer.callPrompt('project-status', { project_id: 3 });
226
-
227
- expect(result.messages).toHaveLength(1);
228
- expect(result.messages[0]!.role).toBe('user');
229
- });
230
-
231
- it('includes the project ID in the text', () => {
232
- const result = mockServer.callPrompt('project-status', { project_id: 99 });
233
-
234
- expect(result.messages[0]!.content.text).toContain('99');
235
- });
236
-
237
- it('mentions list_issues tool', () => {
238
- const result = mockServer.callPrompt('project-status', { project_id: 1 });
239
-
240
- expect(result.messages[0]!.content.text).toContain('list_issues');
241
- });
242
- });
@@ -1,309 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { MantisClient } from '../../src/client.js';
3
- import { MetadataCache } from '../../src/cache.js';
4
- import { registerResources } from '../../src/resources/index.js';
5
- import { MockMcpServer, makeResponse } from '../helpers/mock-server.js';
6
-
7
- // ---------------------------------------------------------------------------
8
- // Inline fixtures
9
- // ---------------------------------------------------------------------------
10
-
11
- const USER_FIXTURE = { id: 1, name: 'jsmith', real_name: 'John Smith', email: 'jsmith@example.com' };
12
-
13
- const PROJECTS_FIXTURE = [
14
- { id: 10, name: 'Alpha', enabled: true },
15
- { id: 11, name: 'Beta', enabled: true },
16
- ];
17
-
18
- // Simulates a raw MantisBT API response with extra fields that must be stripped
19
- const PROJECTS_RAW_FIXTURE = [
20
- {
21
- id: 10,
22
- name: 'Alpha',
23
- enabled: true,
24
- status: { id: 10, name: 'development', label: 'Entwicklung' },
25
- view_state: { id: 10, name: 'public', label: 'Öffentlich' },
26
- custom_fields: [
27
- { id: 1, name: 'Reklamieren', type: 'checkbox', default_value: '', possible_values: 'Ja' },
28
- ],
29
- },
30
- ];
31
-
32
- const ENUM_FIXTURE = {
33
- configs: [
34
- { option: 'severity_enum_string', value: '10:feature,50:minor,80:block' },
35
- { option: 'status_enum_string', value: '10:new,80:resolved,90:closed' },
36
- { option: 'priority_enum_string', value: '10:none,30:normal,60:immediate' },
37
- { option: 'resolution_enum_string', value: '10:open,20:fixed' },
38
- { option: 'reproducibility_enum_string', value: '10:always,70:have not tried' },
39
- ],
40
- };
41
-
42
- // ---------------------------------------------------------------------------
43
- // Setup
44
- // ---------------------------------------------------------------------------
45
-
46
- let mockServer: MockMcpServer;
47
- let client: MantisClient;
48
- let cache: MetadataCache;
49
-
50
- beforeEach(() => {
51
- mockServer = new MockMcpServer();
52
- client = new MantisClient('https://mantis.example.com', 'test-token');
53
- cache = new MetadataCache('/tmp/cache-resources-test', 86400);
54
- registerResources(mockServer as never, client, cache);
55
- vi.stubGlobal('fetch', vi.fn());
56
- });
57
-
58
- afterEach(async () => {
59
- vi.unstubAllGlobals();
60
- await cache.invalidate();
61
- });
62
-
63
- // ---------------------------------------------------------------------------
64
- // Registration
65
- // ---------------------------------------------------------------------------
66
-
67
- describe('resource registration', () => {
68
- it('registers mantis://me', () => {
69
- expect(mockServer.hasResourceRegistered('mantis://me')).toBe(true);
70
- });
71
-
72
- it('registers mantis://projects', () => {
73
- expect(mockServer.hasResourceRegistered('mantis://projects')).toBe(true);
74
- });
75
-
76
- it('registers mantis://enums', () => {
77
- expect(mockServer.hasResourceRegistered('mantis://enums')).toBe(true);
78
- });
79
- });
80
-
81
- // ---------------------------------------------------------------------------
82
- // mantis://me
83
- // ---------------------------------------------------------------------------
84
-
85
- describe('mantis://me', () => {
86
- it('returns a single content item with application/json', async () => {
87
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(USER_FIXTURE)));
88
-
89
- const result = await mockServer.callResource('mantis://me');
90
-
91
- expect(result.contents).toHaveLength(1);
92
- expect(result.contents[0]!.mimeType).toBe('application/json');
93
- });
94
-
95
- it('sets uri to mantis://me', async () => {
96
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(USER_FIXTURE)));
97
-
98
- const result = await mockServer.callResource('mantis://me');
99
-
100
- expect(result.contents[0]!.uri).toBe('mantis://me');
101
- });
102
-
103
- it('returns valid JSON with id and name', async () => {
104
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(USER_FIXTURE)));
105
-
106
- const result = await mockServer.callResource('mantis://me');
107
-
108
- const parsed = JSON.parse(result.contents[0]!.text) as { id: number; name: string };
109
- expect(parsed.id).toBe(USER_FIXTURE.id);
110
- expect(parsed.name).toBe(USER_FIXTURE.name);
111
- });
112
- });
113
-
114
- // ---------------------------------------------------------------------------
115
- // mantis://projects
116
- // ---------------------------------------------------------------------------
117
-
118
- describe('mantis://projects', () => {
119
- it('returns a single content item with application/json', async () => {
120
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: PROJECTS_FIXTURE })));
121
-
122
- const result = await mockServer.callResource('mantis://projects');
123
-
124
- expect(result.contents).toHaveLength(1);
125
- expect(result.contents[0]!.mimeType).toBe('application/json');
126
- });
127
-
128
- it('sets uri to mantis://projects', async () => {
129
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: PROJECTS_FIXTURE })));
130
-
131
- const result = await mockServer.callResource('mantis://projects');
132
-
133
- expect(result.contents[0]!.uri).toBe('mantis://projects');
134
- });
135
-
136
- it('returns a JSON array of projects from live API when cache is empty', async () => {
137
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: PROJECTS_FIXTURE })));
138
-
139
- const result = await mockServer.callResource('mantis://projects');
140
-
141
- const parsed = JSON.parse(result.contents[0]!.text) as unknown[];
142
- expect(Array.isArray(parsed)).toBe(true);
143
- expect(parsed).toHaveLength(2);
144
- });
145
-
146
- it('returns minified JSON (no indentation)', async () => {
147
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: PROJECTS_FIXTURE })));
148
-
149
- const result = await mockServer.callResource('mantis://projects');
150
-
151
- expect(result.contents[0]!.text).not.toContain('\n');
152
- });
153
-
154
- it('strips custom_fields from live API response', async () => {
155
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: PROJECTS_RAW_FIXTURE })));
156
-
157
- const result = await mockServer.callResource('mantis://projects');
158
-
159
- const parsed = JSON.parse(result.contents[0]!.text) as Array<Record<string, unknown>>;
160
- expect(parsed[0]).not.toHaveProperty('custom_fields');
161
- });
162
-
163
- it('preserves label on status and view_state from live API response', async () => {
164
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: PROJECTS_RAW_FIXTURE })));
165
-
166
- const result = await mockServer.callResource('mantis://projects');
167
-
168
- const parsed = JSON.parse(result.contents[0]!.text) as Array<Record<string, unknown>>;
169
- expect((parsed[0]!['status'] as Record<string, unknown>)['label']).toBe('Entwicklung');
170
- expect((parsed[0]!['view_state'] as Record<string, unknown>)['label']).toBe('Öffentlich');
171
- });
172
-
173
- it('serves from cache without calling the API when cache is valid', async () => {
174
- await cache.save({
175
- timestamp: Date.now(),
176
- projects: PROJECTS_FIXTURE,
177
- byProject: {},
178
- tags: [],
179
- });
180
-
181
- const result = await mockServer.callResource('mantis://projects');
182
-
183
- expect(vi.mocked(fetch)).not.toHaveBeenCalled();
184
- const parsed = JSON.parse(result.contents[0]!.text) as unknown[];
185
- expect(parsed).toHaveLength(2);
186
- });
187
- });
188
-
189
- // ---------------------------------------------------------------------------
190
- // mantis://projects/{id}
191
- // ---------------------------------------------------------------------------
192
-
193
- describe('mantis://projects/{id}', () => {
194
- it('registers the template resource', () => {
195
- expect(mockServer.hasResourceRegistered('mantis://projects/{id}')).toBe(true);
196
- });
197
-
198
- it('returns combined project view from cache (users, versions, categories)', async () => {
199
- const projectsFixture = [{ id: 10, name: 'Alpha', enabled: true }];
200
- await cache.save({
201
- timestamp: Date.now(),
202
- projects: projectsFixture,
203
- byProject: {
204
- 10: {
205
- users: [{ id: 1, name: 'jsmith' }],
206
- versions: [{ id: 5, name: '1.0.0', released: true }],
207
- categories: [{ id: 3, name: 'Bugs' }],
208
- },
209
- },
210
- tags: [],
211
- });
212
-
213
- const result = await mockServer.callResource('mantis://projects/10');
214
-
215
- const parsed = JSON.parse(result.contents[0]!.text) as Record<string, unknown>;
216
- expect(parsed['id']).toBe(10);
217
- expect(parsed['name']).toBe('Alpha');
218
- expect(parsed['users']).toHaveLength(1);
219
- expect(parsed['versions']).toHaveLength(1);
220
- expect(parsed['categories']).toHaveLength(1);
221
- expect(vi.mocked(fetch)).not.toHaveBeenCalled();
222
- });
223
-
224
- it('fetches live data in parallel when cache is cold', async () => {
225
- vi.mocked(fetch)
226
- .mockResolvedValueOnce(makeResponse(200, JSON.stringify({ projects: [{ id: 10, name: 'Alpha', categories: [{ id: 3, name: 'Bugs' }] }] })))
227
- .mockResolvedValueOnce(makeResponse(200, JSON.stringify({ users: [{ id: 1, name: 'jsmith' }] })))
228
- .mockResolvedValueOnce(makeResponse(200, JSON.stringify({ versions: [{ id: 5, name: '1.0.0' }] })));
229
-
230
- const result = await mockServer.callResource('mantis://projects/10');
231
-
232
- const parsed = JSON.parse(result.contents[0]!.text) as Record<string, unknown>;
233
- expect(parsed['id']).toBe(10);
234
- expect((parsed['users'] as unknown[]).length).toBeGreaterThan(0);
235
- expect((parsed['versions'] as unknown[]).length).toBeGreaterThan(0);
236
- expect((parsed['categories'] as unknown[]).length).toBeGreaterThan(0);
237
- });
238
-
239
- it('strips [All Projects] prefix from category names on live fetch', async () => {
240
- vi.mocked(fetch)
241
- .mockResolvedValueOnce(makeResponse(200, JSON.stringify({ projects: [{ id: 10, name: 'Alpha', categories: [{ id: 1, name: '[All Projects] General' }] }] })))
242
- .mockResolvedValueOnce(makeResponse(200, JSON.stringify({ users: [] })))
243
- .mockResolvedValueOnce(makeResponse(200, JSON.stringify({ versions: [] })));
244
-
245
- const result = await mockServer.callResource('mantis://projects/10');
246
-
247
- const parsed = JSON.parse(result.contents[0]!.text) as { categories: Array<{ name: string }> };
248
- expect(parsed.categories[0]!.name).toBe('General');
249
- });
250
-
251
- it('returns minified JSON', async () => {
252
- await cache.save({
253
- timestamp: Date.now(),
254
- projects: [{ id: 10, name: 'Alpha' }],
255
- byProject: { 10: { users: [], versions: [], categories: [] } },
256
- tags: [],
257
- });
258
-
259
- const result = await mockServer.callResource('mantis://projects/10');
260
-
261
- expect(result.contents[0]!.text).not.toContain('\n');
262
- });
263
- });
264
-
265
- // ---------------------------------------------------------------------------
266
- // mantis://enums
267
- // ---------------------------------------------------------------------------
268
-
269
- describe('mantis://enums', () => {
270
- it('returns a single content item with application/json', async () => {
271
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(ENUM_FIXTURE)));
272
-
273
- const result = await mockServer.callResource('mantis://enums');
274
-
275
- expect(result.contents).toHaveLength(1);
276
- expect(result.contents[0]!.mimeType).toBe('application/json');
277
- });
278
-
279
- it('sets uri to mantis://enums', async () => {
280
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(ENUM_FIXTURE)));
281
-
282
- const result = await mockServer.callResource('mantis://enums');
283
-
284
- expect(result.contents[0]!.uri).toBe('mantis://enums');
285
- });
286
-
287
- it('returns parsed enum groups with id and name entries', async () => {
288
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(ENUM_FIXTURE)));
289
-
290
- const result = await mockServer.callResource('mantis://enums');
291
-
292
- const parsed = JSON.parse(result.contents[0]!.text) as Record<string, Array<{ id: number; name: string }>>;
293
- for (const key of ['severity', 'priority', 'status', 'resolution', 'reproducibility']) {
294
- expect(Array.isArray(parsed[key])).toBe(true);
295
- expect(parsed[key]!.length).toBeGreaterThan(0);
296
- expect(parsed[key]![0]).toMatchObject({ id: expect.any(Number), name: expect.any(String) });
297
- }
298
- });
299
-
300
- it('parses severity values correctly from fixture', async () => {
301
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(ENUM_FIXTURE)));
302
-
303
- const result = await mockServer.callResource('mantis://enums');
304
-
305
- const parsed = JSON.parse(result.contents[0]!.text) as Record<string, Array<{ id: number; name: string }>>;
306
- expect(parsed['severity']).toContainEqual({ id: 10, name: 'feature' });
307
- expect(parsed['severity']).toContainEqual({ id: 50, name: 'minor' });
308
- });
309
- });
@@ -1,81 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { Embedder } from '../../src/search/embedder.js';
3
-
4
- // ---------------------------------------------------------------------------
5
- // Mock @huggingface/transformers (dynamic import)
6
- // ---------------------------------------------------------------------------
7
-
8
- const mockPipelineFn = vi.fn(async (texts: string | string[]) => {
9
- if (Array.isArray(texts)) {
10
- return texts.map(() => ({ data: new Float32Array(4).fill(0.1), dims: [1, 4] }));
11
- }
12
- return { data: new Float32Array(4).fill(0.1), dims: [4] };
13
- });
14
-
15
- const mockPipelineFactory = vi.fn(async (_task: string, _model: string, _opts?: unknown) => mockPipelineFn);
16
-
17
- vi.mock('@huggingface/transformers', () => ({
18
- pipeline: mockPipelineFactory,
19
- }));
20
-
21
- beforeEach(() => {
22
- vi.clearAllMocks();
23
- });
24
-
25
- // ---------------------------------------------------------------------------
26
- // Thread configuration
27
- // ---------------------------------------------------------------------------
28
-
29
- describe('Embedder – thread configuration', () => {
30
- it('passes intra_op_num_threads=1 by default', async () => {
31
- const embedder = new Embedder('test-model');
32
- await embedder.embed('hello');
33
-
34
- expect(mockPipelineFactory).toHaveBeenCalledWith(
35
- 'feature-extraction',
36
- 'test-model',
37
- expect.objectContaining({
38
- session_options: { intra_op_num_threads: 1, inter_op_num_threads: 1 },
39
- }),
40
- );
41
- });
42
-
43
- it('passes configured numThreads to intra_op_num_threads; inter stays 1', async () => {
44
- const embedder = new Embedder('test-model', 4);
45
- await embedder.embed('hello');
46
-
47
- expect(mockPipelineFactory).toHaveBeenCalledWith(
48
- 'feature-extraction',
49
- 'test-model',
50
- expect.objectContaining({
51
- session_options: { intra_op_num_threads: 4, inter_op_num_threads: 1 },
52
- }),
53
- );
54
- });
55
-
56
- it('loads the pipeline only once (lazy singleton)', async () => {
57
- const embedder = new Embedder('test-model', 1);
58
- await embedder.embed('first');
59
- await embedder.embed('second');
60
-
61
- expect(mockPipelineFactory).toHaveBeenCalledTimes(1);
62
- });
63
- });
64
-
65
- // ---------------------------------------------------------------------------
66
- // Embedder default — numThreads omitted
67
- // ---------------------------------------------------------------------------
68
-
69
- describe('Embedder – numThreads default', () => {
70
- it('uses intra_op_num_threads=1 when numThreads is not passed', async () => {
71
- const embedder = new Embedder('m');
72
- await embedder.embed('x');
73
- expect(mockPipelineFactory).toHaveBeenCalledWith(
74
- 'feature-extraction',
75
- 'm',
76
- expect.objectContaining({
77
- session_options: expect.objectContaining({ intra_op_num_threads: 1 }),
78
- }),
79
- );
80
- });
81
- });