@dpesch/mantisbt-mcp-server 1.10.3 → 1.11.0

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 (54) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.de.md +11 -8
  3. package/README.md +11 -8
  4. package/dist/config.js +13 -0
  5. package/dist/index.js +12 -12
  6. package/dist/tools/files.js +11 -2
  7. package/dist/tools/issues.js +47 -15
  8. package/dist/tools/notes.js +15 -10
  9. package/docs/cookbook.de.md +67 -0
  10. package/docs/cookbook.md +67 -0
  11. package/docs/examples.de.md +8 -0
  12. package/docs/examples.md +8 -0
  13. package/package.json +1 -1
  14. package/server.json +2 -2
  15. package/.gitea/PULL_REQUEST_TEMPLATE.md +0 -17
  16. package/.gitea/workflows/ci.yml +0 -95
  17. package/.github/workflows/ci.yml +0 -32
  18. package/scripts/hooks/pre-push.mjs +0 -220
  19. package/scripts/init.mjs +0 -95
  20. package/scripts/record-fixtures.ts +0 -160
  21. package/tests/cache.test.ts +0 -265
  22. package/tests/client.test.ts +0 -372
  23. package/tests/config.test.ts +0 -263
  24. package/tests/fixtures/get_current_user.json +0 -40
  25. package/tests/fixtures/get_issue.json +0 -133
  26. package/tests/fixtures/get_issue_enums.json +0 -67
  27. package/tests/fixtures/get_issue_fields_sample.json +0 -76
  28. package/tests/fixtures/get_project_categories.json +0 -157
  29. package/tests/fixtures/get_project_versions.json +0 -3
  30. package/tests/fixtures/get_project_versions_with_data.json +0 -52
  31. package/tests/fixtures/list_issues.json +0 -95
  32. package/tests/fixtures/list_projects.json +0 -65
  33. package/tests/helpers/mock-server.ts +0 -166
  34. package/tests/helpers/search-mocks.ts +0 -84
  35. package/tests/prompts/prompts.test.ts +0 -242
  36. package/tests/resources/resources.test.ts +0 -309
  37. package/tests/search/embedder.test.ts +0 -81
  38. package/tests/search/highlight.test.ts +0 -129
  39. package/tests/search/store.test.ts +0 -193
  40. package/tests/search/sync.test.ts +0 -249
  41. package/tests/search/tools.test.ts +0 -661
  42. package/tests/tools/config.test.ts +0 -212
  43. package/tests/tools/files.test.ts +0 -343
  44. package/tests/tools/issues.test.ts +0 -1180
  45. package/tests/tools/metadata.test.ts +0 -509
  46. package/tests/tools/monitors.test.ts +0 -101
  47. package/tests/tools/projects.test.ts +0 -338
  48. package/tests/tools/relationships.test.ts +0 -177
  49. package/tests/tools/string-coercion.test.ts +0 -317
  50. package/tests/tools/users.test.ts +0 -62
  51. package/tests/utils/date-filter.test.ts +0 -169
  52. package/tests/version-hint.test.ts +0 -230
  53. package/tsconfig.build.json +0 -8
  54. package/vitest.config.ts +0 -8
@@ -1,317 +0,0 @@
1
- /**
2
- * Regression tests for string-to-number coercion in MCP tool schemas.
3
- *
4
- * The MCP protocol allows clients to pass numeric IDs as strings (e.g. "1940"
5
- * instead of 1940). Without z.coerce.number(), the Zod schema would reject
6
- * these inputs with error -32602 (Invalid params).
7
- *
8
- * These tests use { validate: true } to run args through the Zod schema —
9
- * exactly as the real MCP server does — before passing them to the handler.
10
- */
11
-
12
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
13
- import { MantisClient } from '../../src/client.js';
14
- import { registerIssueTools } from '../../src/tools/issues.js';
15
- import { MetadataCache } from '../../src/cache.js';
16
- import { registerNoteTools } from '../../src/tools/notes.js';
17
- import { registerFileTools } from '../../src/tools/files.js';
18
- import { registerMonitorTools } from '../../src/tools/monitors.js';
19
- import { registerRelationshipTools } from '../../src/tools/relationships.js';
20
- import { registerTagTools } from '../../src/tools/tags.js';
21
- import { registerProjectTools } from '../../src/tools/projects.js';
22
- import { registerVersionTools } from '../../src/tools/version.js';
23
- import { VersionHintService } from '../../src/version-hint.js';
24
- import { MockMcpServer, makeResponse } from '../helpers/mock-server.js';
25
-
26
- // ---------------------------------------------------------------------------
27
- // Setup
28
- // ---------------------------------------------------------------------------
29
-
30
- let mockServer: MockMcpServer;
31
- let client: MantisClient;
32
-
33
- beforeEach(() => {
34
- mockServer = new MockMcpServer();
35
- client = new MantisClient('https://mantis.example.com', 'test-token');
36
- const stubCache = { loadIfValid: vi.fn(async () => null) } as unknown as MetadataCache;
37
- registerIssueTools(mockServer as never, client, stubCache);
38
- registerNoteTools(mockServer as never, client);
39
- registerFileTools(mockServer as never, client);
40
- registerMonitorTools(mockServer as never, client);
41
- registerRelationshipTools(mockServer as never, client);
42
- registerTagTools(mockServer as never, client);
43
- registerProjectTools(mockServer as never, client);
44
- registerVersionTools(mockServer as never, client, new VersionHintService(), '0.0.0-test');
45
- vi.stubGlobal('fetch', vi.fn());
46
- });
47
-
48
- afterEach(() => {
49
- vi.unstubAllGlobals();
50
- vi.clearAllMocks();
51
- });
52
-
53
- // ---------------------------------------------------------------------------
54
- // Issues
55
- // ---------------------------------------------------------------------------
56
-
57
- describe('string-coercion – get_issue', () => {
58
- it('accepts issue_id as string "1940"', async () => {
59
- vi.mocked(fetch).mockResolvedValue(
60
- makeResponse(200, JSON.stringify({ issues: [{ id: 1940, summary: 'Test' }] }))
61
- );
62
-
63
- const result = await mockServer.callTool('get_issue', { id: '1940' }, { validate: true });
64
-
65
- expect(result.isError).toBeUndefined();
66
- const calledUrl = vi.mocked(fetch).mock.calls[0]![0] as string;
67
- expect(calledUrl).toContain('1940');
68
- });
69
-
70
- it('rejects a non-numeric string', async () => {
71
- const result = await mockServer.callTool('get_issue', { id: 'abc' }, { validate: true });
72
- expect(result.isError).toBe(true);
73
- });
74
- });
75
-
76
- describe('string-coercion – list_issues', () => {
77
- it('accepts project_id as string', async () => {
78
- vi.mocked(fetch).mockResolvedValue(
79
- makeResponse(200, JSON.stringify({ issues: [], total_count: 0 }))
80
- );
81
-
82
- const result = await mockServer.callTool(
83
- 'list_issues',
84
- { project_id: '5', page: '1', page_size: '10' },
85
- { validate: true },
86
- );
87
-
88
- expect(result.isError).toBeUndefined();
89
- });
90
- });
91
-
92
- describe('string-coercion – update_issue', () => {
93
- it('accepts id as string', async () => {
94
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ issue: { id: 99, summary: 'Updated' } })));
95
-
96
- const result = await mockServer.callTool(
97
- 'update_issue',
98
- { id: '99', fields: { summary: 'Updated' } },
99
- { validate: true },
100
- );
101
-
102
- expect(result.isError).toBeUndefined();
103
- });
104
- });
105
-
106
- describe('string-coercion – delete_issue', () => {
107
- it('accepts id as string', async () => {
108
- vi.mocked(fetch).mockResolvedValue(makeResponse(204, ''));
109
-
110
- const result = await mockServer.callTool('delete_issue', { id: '42' }, { validate: true });
111
-
112
- expect(result.isError).toBeUndefined();
113
- });
114
- });
115
-
116
- // ---------------------------------------------------------------------------
117
- // Notes
118
- // ---------------------------------------------------------------------------
119
-
120
- describe('string-coercion – list_notes', () => {
121
- it('accepts issue_id as string', async () => {
122
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ issues: [{ notes: [] }] })));
123
-
124
- const result = await mockServer.callTool('list_notes', { issue_id: '1940' }, { validate: true });
125
-
126
- expect(result.isError).toBeUndefined();
127
- });
128
- });
129
-
130
- describe('string-coercion – delete_note', () => {
131
- it('accepts issue_id and note_id as strings', async () => {
132
- vi.mocked(fetch).mockResolvedValue(makeResponse(204, ''));
133
-
134
- const result = await mockServer.callTool(
135
- 'delete_note',
136
- { issue_id: '1940', note_id: '77' },
137
- { validate: true },
138
- );
139
-
140
- expect(result.isError).toBeUndefined();
141
- });
142
- });
143
-
144
- // ---------------------------------------------------------------------------
145
- // Files
146
- // ---------------------------------------------------------------------------
147
-
148
- describe('string-coercion – list_issue_files', () => {
149
- it('accepts issue_id as string', async () => {
150
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ issues: [{ attachments: [] }] })));
151
-
152
- const result = await mockServer.callTool('list_issue_files', { issue_id: '1940' }, { validate: true });
153
-
154
- expect(result.isError).toBeUndefined();
155
- });
156
- });
157
-
158
- // ---------------------------------------------------------------------------
159
- // Monitors
160
- // ---------------------------------------------------------------------------
161
-
162
- describe('string-coercion – add_monitor', () => {
163
- it('accepts issue_id as string', async () => {
164
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, ''));
165
-
166
- const result = await mockServer.callTool(
167
- 'add_monitor',
168
- { issue_id: '1940', username: 'jdoe' },
169
- { validate: true },
170
- );
171
-
172
- expect(result.isError).toBeUndefined();
173
- });
174
- });
175
-
176
- describe('string-coercion – remove_monitor', () => {
177
- it('accepts issue_id as string', async () => {
178
- vi.mocked(fetch).mockResolvedValue(makeResponse(204, ''));
179
-
180
- const result = await mockServer.callTool(
181
- 'remove_monitor',
182
- { issue_id: '1940', username: 'jdoe' },
183
- { validate: true },
184
- );
185
-
186
- expect(result.isError).toBeUndefined();
187
- });
188
- });
189
-
190
- // ---------------------------------------------------------------------------
191
- // Relationships
192
- // ---------------------------------------------------------------------------
193
-
194
- describe('string-coercion – add_relationship', () => {
195
- it('accepts issue_id, target_id and type_id as strings', async () => {
196
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ id: 1 })));
197
-
198
- const result = await mockServer.callTool(
199
- 'add_relationship',
200
- { issue_id: '1940', target_id: '1941', type_id: '1' },
201
- { validate: true },
202
- );
203
-
204
- expect(result.isError).toBeUndefined();
205
- });
206
- });
207
-
208
- describe('string-coercion – remove_relationship', () => {
209
- it('accepts issue_id and relationship_id as strings', async () => {
210
- vi.mocked(fetch).mockResolvedValue(makeResponse(204, ''));
211
-
212
- const result = await mockServer.callTool(
213
- 'remove_relationship',
214
- { issue_id: '1940', relationship_id: '55' },
215
- { validate: true },
216
- );
217
-
218
- expect(result.isError).toBeUndefined();
219
- });
220
- });
221
-
222
- // ---------------------------------------------------------------------------
223
- // Tags
224
- // ---------------------------------------------------------------------------
225
-
226
- describe('string-coercion – detach_tag', () => {
227
- it('accepts issue_id and tag_id as strings', async () => {
228
- vi.mocked(fetch).mockResolvedValue(makeResponse(204, ''));
229
-
230
- const result = await mockServer.callTool(
231
- 'detach_tag',
232
- { issue_id: '1940', tag_id: '7' },
233
- { validate: true },
234
- );
235
-
236
- expect(result.isError).toBeUndefined();
237
- });
238
- });
239
-
240
- // ---------------------------------------------------------------------------
241
- // Projects
242
- // ---------------------------------------------------------------------------
243
-
244
- describe('string-coercion – get_project_versions', () => {
245
- it('accepts project_id as string', async () => {
246
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: [{ versions: [] }] })));
247
-
248
- const result = await mockServer.callTool(
249
- 'get_project_versions',
250
- { project_id: '3' },
251
- { validate: true },
252
- );
253
-
254
- expect(result.isError).toBeUndefined();
255
- });
256
- });
257
-
258
- // ---------------------------------------------------------------------------
259
- // Boolean-Parameter als String
260
- // ---------------------------------------------------------------------------
261
-
262
- describe('string-coercion – update_issue fields as JSON string', () => {
263
- it('accepts fields as JSON string', async () => {
264
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ issue: { id: 99, summary: 'Updated' } })));
265
- const result = await mockServer.callTool(
266
- 'update_issue',
267
- { id: 99, fields: '{"summary":"Updated"}' },
268
- { validate: true },
269
- );
270
- expect(result.isError).toBeUndefined();
271
- });
272
-
273
- it('rejects invalid JSON in fields with a validation error (not an uncaught exception)', async () => {
274
- const result = await mockServer.callTool(
275
- 'update_issue',
276
- { id: 99, fields: '{invalid json' },
277
- { validate: true },
278
- );
279
- expect(result.isError).toBe(true);
280
- });
281
- });
282
-
283
- describe('string-coercion – update_issue dry_run as string', () => {
284
- it('accepts dry_run "true" as boolean true', async () => {
285
- const result = await mockServer.callTool(
286
- 'update_issue',
287
- { id: 99, fields: { summary: 'x' }, dry_run: 'true' },
288
- { validate: true },
289
- );
290
- expect(result.isError).toBeUndefined();
291
- expect(result.content[0]?.text).toContain('"dry_run": true');
292
- });
293
- });
294
-
295
- describe('string-coercion – get_project_versions boolean flags', () => {
296
- it('accepts obsolete "true" and inherit "false" as booleans', async () => {
297
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ projects: [{ versions: [] }] })));
298
- const result = await mockServer.callTool(
299
- 'get_project_versions',
300
- { project_id: 3, obsolete: 'true', inherit: 'false' },
301
- { validate: true },
302
- );
303
- expect(result.isError).toBeUndefined();
304
- });
305
- });
306
-
307
- describe('string-coercion – get_mantis_version check_latest as string', () => {
308
- it('accepts check_latest "false" as boolean false', async () => {
309
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify({ version: '2.26.0' })));
310
- const result = await mockServer.callTool(
311
- 'get_mantis_version',
312
- { check_latest: 'false' },
313
- { validate: true },
314
- );
315
- expect(result.isError).toBeUndefined();
316
- });
317
- });
@@ -1,62 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { readFileSync, existsSync } from 'node:fs';
3
- import { join, dirname } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { MantisClient } from '../../src/client.js';
6
- import { registerUserTools } from '../../src/tools/users.js';
7
- import { MockMcpServer, makeResponse } from '../helpers/mock-server.js';
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = dirname(__filename);
11
- const fixturesDir = join(__dirname, '..', 'fixtures');
12
-
13
- // ---------------------------------------------------------------------------
14
- // Fixtures laden (mit Inline-Fallback)
15
- // ---------------------------------------------------------------------------
16
-
17
- const getCurrentUserFixturePath = join(fixturesDir, 'get_current_user.json');
18
-
19
- const getCurrentUserFixture = existsSync(getCurrentUserFixturePath)
20
- ? (JSON.parse(readFileSync(getCurrentUserFixturePath, 'utf-8')) as { id: number; name: string; real_name?: string; email?: string })
21
- : { id: 5, name: 'testuser', real_name: 'Test User', email: 'test@example.com' };
22
-
23
- // ---------------------------------------------------------------------------
24
- // Setup
25
- // ---------------------------------------------------------------------------
26
-
27
- let mockServer: MockMcpServer;
28
- let client: MantisClient;
29
-
30
- beforeEach(() => {
31
- mockServer = new MockMcpServer();
32
- client = new MantisClient('https://mantis.example.com', 'test-token');
33
- registerUserTools(mockServer as never, client);
34
- vi.stubGlobal('fetch', vi.fn());
35
- });
36
-
37
- afterEach(() => {
38
- vi.unstubAllGlobals();
39
- });
40
-
41
- // ---------------------------------------------------------------------------
42
- // get_current_user
43
- // ---------------------------------------------------------------------------
44
-
45
- describe('get_current_user', () => {
46
- it('ist registriert', () => {
47
- expect(mockServer.hasToolRegistered('get_current_user')).toBe(true);
48
- });
49
-
50
- it('gibt User-Daten zurück mit id und name', async () => {
51
- vi.mocked(fetch).mockResolvedValue(makeResponse(200, JSON.stringify(getCurrentUserFixture)));
52
-
53
- const result = await mockServer.callTool('get_current_user', {});
54
-
55
- expect(result.isError).toBeUndefined();
56
- const parsed = JSON.parse(result.content[0]!.text) as { id: number; name: string };
57
- expect(typeof parsed.id).toBe('number');
58
- expect(typeof parsed.name).toBe('string');
59
- expect(parsed.id).toBe(getCurrentUserFixture.id);
60
- expect(parsed.name).toBe(getCurrentUserFixture.name);
61
- });
62
- });
@@ -1,169 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { matchesDateFilter } from '../../src/date-filter.js';
3
-
4
- describe('matchesDateFilter', () => {
5
-
6
- // ---------------------------------------------------------------------------
7
- // No filter → always pass
8
- // ---------------------------------------------------------------------------
9
-
10
- it('returns true when no filter is set', () => {
11
- expect(matchesDateFilter(
12
- { updated_at: '2026-03-20T10:00:00Z', created_at: '2026-03-01T00:00:00Z' },
13
- {}
14
- )).toBe(true);
15
- });
16
-
17
- it('returns true for an item with no dates when no filter is set', () => {
18
- expect(matchesDateFilter({}, {})).toBe(true);
19
- });
20
-
21
- // ---------------------------------------------------------------------------
22
- // updated_after
23
- // ---------------------------------------------------------------------------
24
-
25
- it('updated_after: passes when updated_at is after the threshold', () => {
26
- expect(matchesDateFilter(
27
- { updated_at: '2026-03-25T12:00:00Z' },
28
- { updated_after: '2026-03-24T00:00:00Z' }
29
- )).toBe(true);
30
- });
31
-
32
- it('updated_after: fails when updated_at is before the threshold', () => {
33
- expect(matchesDateFilter(
34
- { updated_at: '2026-03-23T12:00:00Z' },
35
- { updated_after: '2026-03-24T00:00:00Z' }
36
- )).toBe(false);
37
- });
38
-
39
- it('updated_after: fails when updated_at equals the threshold (exclusive)', () => {
40
- expect(matchesDateFilter(
41
- { updated_at: '2026-03-24T00:00:00Z' },
42
- { updated_after: '2026-03-24T00:00:00Z' }
43
- )).toBe(false);
44
- });
45
-
46
- it('updated_after: fails when updated_at is missing', () => {
47
- expect(matchesDateFilter(
48
- { created_at: '2026-03-25T00:00:00Z' },
49
- { updated_after: '2026-03-24T00:00:00Z' }
50
- )).toBe(false);
51
- });
52
-
53
- // ---------------------------------------------------------------------------
54
- // updated_before
55
- // ---------------------------------------------------------------------------
56
-
57
- it('updated_before: passes when updated_at is before the threshold', () => {
58
- expect(matchesDateFilter(
59
- { updated_at: '2026-03-20T00:00:00Z' },
60
- { updated_before: '2026-03-25T00:00:00Z' }
61
- )).toBe(true);
62
- });
63
-
64
- it('updated_before: fails when updated_at is after the threshold', () => {
65
- expect(matchesDateFilter(
66
- { updated_at: '2026-03-26T00:00:00Z' },
67
- { updated_before: '2026-03-25T00:00:00Z' }
68
- )).toBe(false);
69
- });
70
-
71
- it('updated_before: fails when updated_at equals the threshold (exclusive)', () => {
72
- expect(matchesDateFilter(
73
- { updated_at: '2026-03-25T00:00:00Z' },
74
- { updated_before: '2026-03-25T00:00:00Z' }
75
- )).toBe(false);
76
- });
77
-
78
- it('updated_before: fails when updated_at is missing', () => {
79
- expect(matchesDateFilter(
80
- {},
81
- { updated_before: '2026-03-25T00:00:00Z' }
82
- )).toBe(false);
83
- });
84
-
85
- // ---------------------------------------------------------------------------
86
- // created_after
87
- // ---------------------------------------------------------------------------
88
-
89
- it('created_after: passes when created_at is after the threshold', () => {
90
- expect(matchesDateFilter(
91
- { created_at: '2026-03-25T12:00:00Z' },
92
- { created_after: '2026-03-24T00:00:00Z' }
93
- )).toBe(true);
94
- });
95
-
96
- it('created_after: fails when created_at is before the threshold', () => {
97
- expect(matchesDateFilter(
98
- { created_at: '2026-03-23T00:00:00Z' },
99
- { created_after: '2026-03-24T00:00:00Z' }
100
- )).toBe(false);
101
- });
102
-
103
- it('created_after: fails when created_at is missing', () => {
104
- expect(matchesDateFilter(
105
- { updated_at: '2026-03-25T00:00:00Z' },
106
- { created_after: '2026-03-24T00:00:00Z' }
107
- )).toBe(false);
108
- });
109
-
110
- // ---------------------------------------------------------------------------
111
- // created_before
112
- // ---------------------------------------------------------------------------
113
-
114
- it('created_before: passes when created_at is before the threshold', () => {
115
- expect(matchesDateFilter(
116
- { created_at: '2026-03-20T00:00:00Z' },
117
- { created_before: '2026-03-25T00:00:00Z' }
118
- )).toBe(true);
119
- });
120
-
121
- it('created_before: fails when created_at is after the threshold', () => {
122
- expect(matchesDateFilter(
123
- { created_at: '2026-03-26T00:00:00Z' },
124
- { created_before: '2026-03-25T00:00:00Z' }
125
- )).toBe(false);
126
- });
127
-
128
- // ---------------------------------------------------------------------------
129
- // Combined filters (time window)
130
- // ---------------------------------------------------------------------------
131
-
132
- it('updated_after + updated_before: passes when updated_at is within window', () => {
133
- expect(matchesDateFilter(
134
- { updated_at: '2026-03-22T00:00:00Z' },
135
- { updated_after: '2026-03-20T00:00:00Z', updated_before: '2026-03-25T00:00:00Z' }
136
- )).toBe(true);
137
- });
138
-
139
- it('updated_after + updated_before: fails when updated_at is outside window', () => {
140
- expect(matchesDateFilter(
141
- { updated_at: '2026-03-26T00:00:00Z' },
142
- { updated_after: '2026-03-20T00:00:00Z', updated_before: '2026-03-25T00:00:00Z' }
143
- )).toBe(false);
144
- });
145
-
146
- it('all four filters: passes only when both dates are within their windows', () => {
147
- expect(matchesDateFilter(
148
- { updated_at: '2026-03-22T00:00:00Z', created_at: '2026-03-10T00:00:00Z' },
149
- {
150
- updated_after: '2026-03-20T00:00:00Z',
151
- updated_before: '2026-03-25T00:00:00Z',
152
- created_after: '2026-03-05T00:00:00Z',
153
- created_before: '2026-03-15T00:00:00Z',
154
- }
155
- )).toBe(true);
156
- });
157
-
158
- it('all four filters: fails when one date is out of range', () => {
159
- expect(matchesDateFilter(
160
- { updated_at: '2026-03-22T00:00:00Z', created_at: '2026-03-16T00:00:00Z' }, // created_at too late
161
- {
162
- updated_after: '2026-03-20T00:00:00Z',
163
- updated_before: '2026-03-25T00:00:00Z',
164
- created_after: '2026-03-05T00:00:00Z',
165
- created_before: '2026-03-15T00:00:00Z',
166
- }
167
- )).toBe(false);
168
- });
169
- });