@gwdn/nexus-mcp 0.8.7 → 0.8.8
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.
- package/dist/server.js +0 -0
- package/package.json +8 -2
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/release.yml +0 -57
- package/src/__tests__/auth.test.ts +0 -162
- package/src/__tests__/decision-comments.test.ts +0 -173
- package/src/__tests__/e2e-integration.test.ts +0 -443
- package/src/__tests__/helpers.ts +0 -58
- package/src/__tests__/layer1-knowledge.test.ts +0 -302
- package/src/__tests__/layer2-coordination.test.ts +0 -918
- package/src/__tests__/layer3-governance.test.ts +0 -205
- package/src/__tests__/machine-id.test.ts +0 -136
- package/src/__tests__/nexus-api.test.ts +0 -127
- package/src/__tests__/project-list-and-skill-assign.test.ts +0 -282
- package/src/__tests__/reviews.test.ts +0 -420
- package/src/__tests__/server.test.ts +0 -251
- package/src/__tests__/setup.ts +0 -15
- package/src/__tests__/task-list-and-doc-list.test.ts +0 -226
- package/src/auth.ts +0 -81
- package/src/machine-id.ts +0 -60
- package/src/nexus-api.ts +0 -149
- package/src/server.ts +0 -629
- package/src/tools/add-task-note.ts +0 -50
- package/src/tools/append-session-entry.ts +0 -113
- package/src/tools/classify-document.ts +0 -55
- package/src/tools/create-task.ts +0 -66
- package/src/tools/decision-comments.ts +0 -103
- package/src/tools/delete-document.ts +0 -43
- package/src/tools/directives.ts +0 -303
- package/src/tools/get-document.ts +0 -80
- package/src/tools/get-project-memory.ts +0 -65
- package/src/tools/get-related-entities.ts +0 -73
- package/src/tools/governance.ts +0 -165
- package/src/tools/ingest-document.ts +0 -65
- package/src/tools/letter-inbox.ts +0 -157
- package/src/tools/letters.ts +0 -144
- package/src/tools/list-documents.ts +0 -58
- package/src/tools/list-tasks.ts +0 -57
- package/src/tools/project-list.ts +0 -52
- package/src/tools/reviews.ts +0 -281
- package/src/tools/search-knowledge.ts +0 -68
- package/src/tools/sessions.ts +0 -157
- package/src/tools/skill-assign.ts +0 -142
- package/src/tools/skills.ts +0 -255
- package/src/tools/update-document.ts +0 -66
- package/src/tools/update-task-status.ts +0 -64
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -8
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for project_list, sk_assign, sk_unassign, and sk_export tools.
|
|
3
|
-
*
|
|
4
|
-
* project_list delegates to GET /api/mcp/projects via nexusGet().
|
|
5
|
-
* sk_assign / sk_unassign / sk_export delegate to POST /api/mcp/skills via nexusPost().
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
9
|
-
import { mockApiError, mockApiSuccess, parseToolResponse, TEST_IDS } from './helpers'
|
|
10
|
-
|
|
11
|
-
vi.mock('../nexus-api.js', () => ({
|
|
12
|
-
nexusGet: vi.fn(),
|
|
13
|
-
nexusPost: vi.fn(),
|
|
14
|
-
}))
|
|
15
|
-
|
|
16
|
-
import { nexusGet, nexusPost } from '../nexus-api.js'
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// project_list
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
describe('project_list', () => {
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
vi.restoreAllMocks()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('should return a list of projects', async () => {
|
|
28
|
-
vi.mocked(nexusGet).mockResolvedValue(
|
|
29
|
-
mockApiSuccess({
|
|
30
|
-
total: 2,
|
|
31
|
-
projects: [
|
|
32
|
-
{ id: TEST_IDS.projectId, name: 'Project Alpha', slug: 'alpha' },
|
|
33
|
-
{ id: '11111111-1111-1111-1111-111111111111', name: 'Project Beta', slug: 'beta' },
|
|
34
|
-
],
|
|
35
|
-
}),
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
const { projectList } = await import('../tools/project-list.js')
|
|
39
|
-
const result = await projectList({})
|
|
40
|
-
|
|
41
|
-
expect(result.isError).toBeUndefined()
|
|
42
|
-
const parsed = parseToolResponse(result)
|
|
43
|
-
expect(parsed.total).toBe(2)
|
|
44
|
-
expect(parsed.projects).toHaveLength(2)
|
|
45
|
-
expect(parsed.projects[0].name).toBe('Project Alpha')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should return an empty list when no projects exist', async () => {
|
|
49
|
-
vi.mocked(nexusGet).mockResolvedValue(
|
|
50
|
-
mockApiSuccess({
|
|
51
|
-
total: 0,
|
|
52
|
-
projects: [],
|
|
53
|
-
}),
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
const { projectList } = await import('../tools/project-list.js')
|
|
57
|
-
const result = await projectList({})
|
|
58
|
-
|
|
59
|
-
expect(result.isError).toBeUndefined()
|
|
60
|
-
const parsed = parseToolResponse(result)
|
|
61
|
-
expect(parsed.total).toBe(0)
|
|
62
|
-
expect(parsed.projects).toHaveLength(0)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should handle API errors gracefully', async () => {
|
|
66
|
-
vi.mocked(nexusGet).mockResolvedValue(mockApiError('Unauthorized', 401))
|
|
67
|
-
|
|
68
|
-
const { projectList } = await import('../tools/project-list.js')
|
|
69
|
-
const result = await projectList({})
|
|
70
|
-
|
|
71
|
-
expect(result.isError).toBe(true)
|
|
72
|
-
const parsed = parseToolResponse(result)
|
|
73
|
-
expect(parsed.error).toBe('Unauthorized')
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// sk_assign
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
describe('sk_assign', () => {
|
|
82
|
-
beforeEach(() => {
|
|
83
|
-
vi.restoreAllMocks()
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('should assign a skill to a project', async () => {
|
|
87
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
88
|
-
mockApiSuccess({
|
|
89
|
-
action: 'sk_assign',
|
|
90
|
-
assignment_id: TEST_IDS.assignmentId,
|
|
91
|
-
project_id: TEST_IDS.projectId,
|
|
92
|
-
skill_id: TEST_IDS.skillId,
|
|
93
|
-
enabled: true,
|
|
94
|
-
}),
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
const { skAssign } = await import('../tools/skill-assign.js')
|
|
98
|
-
const result = await skAssign({
|
|
99
|
-
project_id: TEST_IDS.projectId,
|
|
100
|
-
skill_id: 'nx-init-nexus',
|
|
101
|
-
user_id: TEST_IDS.userId,
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
expect(result.isError).toBeUndefined()
|
|
105
|
-
const parsed = parseToolResponse(result)
|
|
106
|
-
expect(parsed.action).toBe('sk_assign')
|
|
107
|
-
expect(parsed.assignment_id).toBe(TEST_IDS.assignmentId)
|
|
108
|
-
expect(parsed.project_id).toBe(TEST_IDS.projectId)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('should support pinned_version parameter', async () => {
|
|
112
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
113
|
-
mockApiSuccess({
|
|
114
|
-
action: 'sk_assign',
|
|
115
|
-
assignment_id: TEST_IDS.assignmentId,
|
|
116
|
-
project_id: TEST_IDS.projectId,
|
|
117
|
-
skill_id: TEST_IDS.skillId,
|
|
118
|
-
pinned_version: 3,
|
|
119
|
-
enabled: true,
|
|
120
|
-
}),
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
const { skAssign } = await import('../tools/skill-assign.js')
|
|
124
|
-
const result = await skAssign({
|
|
125
|
-
project_id: TEST_IDS.projectId,
|
|
126
|
-
skill_id: 'nx-init-nexus',
|
|
127
|
-
pinned_version: 3,
|
|
128
|
-
user_id: TEST_IDS.userId,
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
expect(result.isError).toBeUndefined()
|
|
132
|
-
const parsed = parseToolResponse(result)
|
|
133
|
-
expect(parsed.pinned_version).toBe(3)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('should return error on duplicate assignment (409)', async () => {
|
|
137
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
138
|
-
mockApiError('Skill already assigned to this project', 409),
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
const { skAssign } = await import('../tools/skill-assign.js')
|
|
142
|
-
const result = await skAssign({
|
|
143
|
-
project_id: TEST_IDS.projectId,
|
|
144
|
-
skill_id: 'nx-init-nexus',
|
|
145
|
-
user_id: TEST_IDS.userId,
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
expect(result.isError).toBe(true)
|
|
149
|
-
const parsed = parseToolResponse(result)
|
|
150
|
-
expect(parsed.error).toBe('Skill already assigned to this project')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should return error on API failure', async () => {
|
|
154
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Internal server error', 500))
|
|
155
|
-
|
|
156
|
-
const { skAssign } = await import('../tools/skill-assign.js')
|
|
157
|
-
const result = await skAssign({
|
|
158
|
-
project_id: TEST_IDS.projectId,
|
|
159
|
-
skill_id: 'nx-init-nexus',
|
|
160
|
-
user_id: TEST_IDS.userId,
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
expect(result.isError).toBe(true)
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
// sk_unassign
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
|
|
171
|
-
describe('sk_unassign', () => {
|
|
172
|
-
beforeEach(() => {
|
|
173
|
-
vi.restoreAllMocks()
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('should unassign a skill from a project', async () => {
|
|
177
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
178
|
-
mockApiSuccess({
|
|
179
|
-
action: 'sk_unassign',
|
|
180
|
-
project_id: TEST_IDS.projectId,
|
|
181
|
-
skill_id: TEST_IDS.skillId,
|
|
182
|
-
removed: true,
|
|
183
|
-
}),
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
const { skUnassign } = await import('../tools/skill-assign.js')
|
|
187
|
-
const result = await skUnassign({
|
|
188
|
-
project_id: TEST_IDS.projectId,
|
|
189
|
-
skill_id: 'nx-init-nexus',
|
|
190
|
-
user_id: TEST_IDS.userId,
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
expect(result.isError).toBeUndefined()
|
|
194
|
-
const parsed = parseToolResponse(result)
|
|
195
|
-
expect(parsed.action).toBe('sk_unassign')
|
|
196
|
-
expect(parsed.removed).toBe(true)
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it('should return error on API failure', async () => {
|
|
200
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Assignment not found', 404))
|
|
201
|
-
|
|
202
|
-
const { skUnassign } = await import('../tools/skill-assign.js')
|
|
203
|
-
const result = await skUnassign({
|
|
204
|
-
project_id: TEST_IDS.projectId,
|
|
205
|
-
skill_id: 'nx-nonexistent',
|
|
206
|
-
user_id: TEST_IDS.userId,
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
expect(result.isError).toBe(true)
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// sk_export
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
describe('sk_export', () => {
|
|
218
|
-
beforeEach(() => {
|
|
219
|
-
vi.restoreAllMocks()
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('should export skills assigned to a project', async () => {
|
|
223
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
224
|
-
mockApiSuccess({
|
|
225
|
-
action: 'sk_export',
|
|
226
|
-
project_id: TEST_IDS.projectId,
|
|
227
|
-
count: 2,
|
|
228
|
-
skills: [
|
|
229
|
-
{ skill_id: 'nx-init-nexus', name: 'Init Nexus', status: 'active', pinned_version: null },
|
|
230
|
-
{ skill_id: 'nx-git-commit', name: 'Git Commit', status: 'active', pinned_version: 2 },
|
|
231
|
-
],
|
|
232
|
-
}),
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
const { skExport } = await import('../tools/skill-assign.js')
|
|
236
|
-
const result = await skExport({
|
|
237
|
-
project_id: TEST_IDS.projectId,
|
|
238
|
-
user_id: TEST_IDS.userId,
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
expect(result.isError).toBeUndefined()
|
|
242
|
-
const parsed = parseToolResponse(result)
|
|
243
|
-
expect(parsed.action).toBe('sk_export')
|
|
244
|
-
expect(parsed.count).toBe(2)
|
|
245
|
-
expect(parsed.skills).toHaveLength(2)
|
|
246
|
-
expect(parsed.skills[0].skill_id).toBe('nx-init-nexus')
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('should return empty list for a project with no assigned skills', async () => {
|
|
250
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
251
|
-
mockApiSuccess({
|
|
252
|
-
action: 'sk_export',
|
|
253
|
-
project_id: TEST_IDS.projectId,
|
|
254
|
-
count: 0,
|
|
255
|
-
skills: [],
|
|
256
|
-
}),
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
const { skExport } = await import('../tools/skill-assign.js')
|
|
260
|
-
const result = await skExport({
|
|
261
|
-
project_id: TEST_IDS.projectId,
|
|
262
|
-
user_id: TEST_IDS.userId,
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
expect(result.isError).toBeUndefined()
|
|
266
|
-
const parsed = parseToolResponse(result)
|
|
267
|
-
expect(parsed.count).toBe(0)
|
|
268
|
-
expect(parsed.skills).toHaveLength(0)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('should return error on API failure', async () => {
|
|
272
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Project not found', 404))
|
|
273
|
-
|
|
274
|
-
const { skExport } = await import('../tools/skill-assign.js')
|
|
275
|
-
const result = await skExport({
|
|
276
|
-
project_id: TEST_IDS.projectId,
|
|
277
|
-
user_id: TEST_IDS.userId,
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
expect(result.isError).toBe(true)
|
|
281
|
-
})
|
|
282
|
-
})
|
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for review lifecycle tools:
|
|
3
|
-
* - rv_list
|
|
4
|
-
* - rv_get
|
|
5
|
-
* - rv_create
|
|
6
|
-
* - rv_decide
|
|
7
|
-
* - rv_comment
|
|
8
|
-
*
|
|
9
|
-
* All tools delegate to the Nexus API via nexusPost().
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
13
|
-
import { mockApiError, mockApiSuccess, parseToolResponse, TEST_IDS } from './helpers'
|
|
14
|
-
|
|
15
|
-
vi.mock('../nexus-api.js', () => ({
|
|
16
|
-
nexusPost: vi.fn(),
|
|
17
|
-
}))
|
|
18
|
-
|
|
19
|
-
import { nexusPost } from '../nexus-api.js'
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// rv_list
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
describe('rv_list', () => {
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.restoreAllMocks()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('should return a list of reviews', async () => {
|
|
31
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
32
|
-
mockApiSuccess({
|
|
33
|
-
action: 'rv_list',
|
|
34
|
-
count: 2,
|
|
35
|
-
reviews: [
|
|
36
|
-
{
|
|
37
|
-
id: TEST_IDS.reviewId,
|
|
38
|
-
entity_type: 'skill',
|
|
39
|
-
entity_id: TEST_IDS.skillId,
|
|
40
|
-
status: 'pending',
|
|
41
|
-
created_at: '2026-04-12T00:00:00Z',
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: '22222222-1111-1111-1111-111111111111',
|
|
45
|
-
entity_type: 'agent',
|
|
46
|
-
entity_id: TEST_IDS.agentId,
|
|
47
|
-
status: 'accepted',
|
|
48
|
-
created_at: '2026-04-11T00:00:00Z',
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
}),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
const { rvList } = await import('../tools/reviews.js')
|
|
55
|
-
const result = await rvList({ user_id: TEST_IDS.userId })
|
|
56
|
-
|
|
57
|
-
expect(result.isError).toBeUndefined()
|
|
58
|
-
const parsed = parseToolResponse(result)
|
|
59
|
-
expect(parsed.action).toBe('rv_list')
|
|
60
|
-
expect(parsed.count).toBe(2)
|
|
61
|
-
expect(parsed.reviews).toHaveLength(2)
|
|
62
|
-
expect(parsed.reviews[0].id).toBe(TEST_IDS.reviewId)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should filter by entity_type', async () => {
|
|
66
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
67
|
-
mockApiSuccess({
|
|
68
|
-
action: 'rv_list',
|
|
69
|
-
count: 1,
|
|
70
|
-
reviews: [
|
|
71
|
-
{
|
|
72
|
-
id: TEST_IDS.reviewId,
|
|
73
|
-
entity_type: 'skill',
|
|
74
|
-
entity_id: TEST_IDS.skillId,
|
|
75
|
-
status: 'pending',
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
}),
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
const { rvList } = await import('../tools/reviews.js')
|
|
82
|
-
const result = await rvList({
|
|
83
|
-
entity_type: 'skill',
|
|
84
|
-
user_id: TEST_IDS.userId,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
expect(result.isError).toBeUndefined()
|
|
88
|
-
const parsed = parseToolResponse(result)
|
|
89
|
-
expect(parsed.count).toBe(1)
|
|
90
|
-
expect(parsed.reviews[0].entity_type).toBe('skill')
|
|
91
|
-
|
|
92
|
-
expect(nexusPost).toHaveBeenCalledWith('/api/mcp/reviews', {
|
|
93
|
-
action: 'rv_list',
|
|
94
|
-
entity_type: 'skill',
|
|
95
|
-
status: undefined,
|
|
96
|
-
limit: undefined,
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should return error on API failure', async () => {
|
|
101
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Internal server error', 500))
|
|
102
|
-
|
|
103
|
-
const { rvList } = await import('../tools/reviews.js')
|
|
104
|
-
const result = await rvList({ user_id: TEST_IDS.userId })
|
|
105
|
-
|
|
106
|
-
expect(result.isError).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// rv_get
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
describe('rv_get', () => {
|
|
115
|
-
beforeEach(() => {
|
|
116
|
-
vi.restoreAllMocks()
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('should get a review by review_id', async () => {
|
|
120
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
121
|
-
mockApiSuccess({
|
|
122
|
-
action: 'rv_get',
|
|
123
|
-
review: {
|
|
124
|
-
id: TEST_IDS.reviewId,
|
|
125
|
-
entity_type: 'skill',
|
|
126
|
-
entity_id: TEST_IDS.skillId,
|
|
127
|
-
status: 'pending',
|
|
128
|
-
created_at: '2026-04-12T00:00:00Z',
|
|
129
|
-
comments: [],
|
|
130
|
-
},
|
|
131
|
-
}),
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
const { rvGet } = await import('../tools/reviews.js')
|
|
135
|
-
const result = await rvGet({
|
|
136
|
-
review_id: TEST_IDS.reviewId,
|
|
137
|
-
user_id: TEST_IDS.userId,
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
expect(result.isError).toBeUndefined()
|
|
141
|
-
const parsed = parseToolResponse(result)
|
|
142
|
-
expect(parsed.action).toBe('rv_get')
|
|
143
|
-
expect(parsed.review.id).toBe(TEST_IDS.reviewId)
|
|
144
|
-
expect(parsed.review.entity_type).toBe('skill')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should get a review by entity_type and entity_id', async () => {
|
|
148
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
149
|
-
mockApiSuccess({
|
|
150
|
-
action: 'rv_get',
|
|
151
|
-
review: {
|
|
152
|
-
id: TEST_IDS.reviewId,
|
|
153
|
-
entity_type: 'skill',
|
|
154
|
-
entity_id: TEST_IDS.skillId,
|
|
155
|
-
status: 'pending',
|
|
156
|
-
},
|
|
157
|
-
}),
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
const { rvGet } = await import('../tools/reviews.js')
|
|
161
|
-
const result = await rvGet({
|
|
162
|
-
entity_type: 'skill',
|
|
163
|
-
entity_id: TEST_IDS.skillId,
|
|
164
|
-
user_id: TEST_IDS.userId,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
expect(result.isError).toBeUndefined()
|
|
168
|
-
const parsed = parseToolResponse(result)
|
|
169
|
-
expect(parsed.review.entity_type).toBe('skill')
|
|
170
|
-
expect(parsed.review.entity_id).toBe(TEST_IDS.skillId)
|
|
171
|
-
|
|
172
|
-
expect(nexusPost).toHaveBeenCalledWith('/api/mcp/reviews', {
|
|
173
|
-
action: 'rv_get',
|
|
174
|
-
review_id: undefined,
|
|
175
|
-
entity_type: 'skill',
|
|
176
|
-
entity_id: TEST_IDS.skillId,
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it('should return error on API failure', async () => {
|
|
181
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Review not found', 404))
|
|
182
|
-
|
|
183
|
-
const { rvGet } = await import('../tools/reviews.js')
|
|
184
|
-
const result = await rvGet({
|
|
185
|
-
review_id: TEST_IDS.reviewId,
|
|
186
|
-
user_id: TEST_IDS.userId,
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
expect(result.isError).toBe(true)
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
// rv_create
|
|
195
|
-
// ---------------------------------------------------------------------------
|
|
196
|
-
|
|
197
|
-
describe('rv_create', () => {
|
|
198
|
-
beforeEach(() => {
|
|
199
|
-
vi.restoreAllMocks()
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
it('should create a review for an entity', async () => {
|
|
203
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
204
|
-
mockApiSuccess({
|
|
205
|
-
action: 'rv_create',
|
|
206
|
-
review_id: TEST_IDS.reviewId,
|
|
207
|
-
entity_type: 'skill',
|
|
208
|
-
entity_id: TEST_IDS.skillId,
|
|
209
|
-
status: 'pending',
|
|
210
|
-
}),
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
const { rvCreate } = await import('../tools/reviews.js')
|
|
214
|
-
const result = await rvCreate({
|
|
215
|
-
entity_type: 'skill',
|
|
216
|
-
entity_id: TEST_IDS.skillId,
|
|
217
|
-
user_id: TEST_IDS.userId,
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
expect(result.isError).toBeUndefined()
|
|
221
|
-
const parsed = parseToolResponse(result)
|
|
222
|
-
expect(parsed.action).toBe('rv_create')
|
|
223
|
-
expect(parsed.review_id).toBe(TEST_IDS.reviewId)
|
|
224
|
-
expect(parsed.entity_type).toBe('skill')
|
|
225
|
-
expect(parsed.status).toBe('pending')
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('should return error on duplicate review (409)', async () => {
|
|
229
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
230
|
-
mockApiError('Review already exists for this entity', 409),
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
const { rvCreate } = await import('../tools/reviews.js')
|
|
234
|
-
const result = await rvCreate({
|
|
235
|
-
entity_type: 'skill',
|
|
236
|
-
entity_id: TEST_IDS.skillId,
|
|
237
|
-
user_id: TEST_IDS.userId,
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
expect(result.isError).toBe(true)
|
|
241
|
-
const parsed = parseToolResponse(result)
|
|
242
|
-
expect(parsed.error).toBe('Review already exists for this entity')
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('should return error on API failure', async () => {
|
|
246
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Entity not found', 404))
|
|
247
|
-
|
|
248
|
-
const { rvCreate } = await import('../tools/reviews.js')
|
|
249
|
-
const result = await rvCreate({
|
|
250
|
-
entity_type: 'skill',
|
|
251
|
-
entity_id: TEST_IDS.skillId,
|
|
252
|
-
user_id: TEST_IDS.userId,
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
expect(result.isError).toBe(true)
|
|
256
|
-
})
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// ---------------------------------------------------------------------------
|
|
260
|
-
// rv_decide
|
|
261
|
-
// ---------------------------------------------------------------------------
|
|
262
|
-
|
|
263
|
-
describe('rv_decide', () => {
|
|
264
|
-
beforeEach(() => {
|
|
265
|
-
vi.restoreAllMocks()
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
it('should accept a review', async () => {
|
|
269
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
270
|
-
mockApiSuccess({
|
|
271
|
-
action: 'rv_decide',
|
|
272
|
-
review_id: TEST_IDS.reviewId,
|
|
273
|
-
transition: 'accept',
|
|
274
|
-
previous_status: 'pending',
|
|
275
|
-
new_status: 'accepted',
|
|
276
|
-
}),
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
const { rvDecide } = await import('../tools/reviews.js')
|
|
280
|
-
const result = await rvDecide({
|
|
281
|
-
review_id: TEST_IDS.reviewId,
|
|
282
|
-
transition: 'accept',
|
|
283
|
-
user_id: TEST_IDS.userId,
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
expect(result.isError).toBeUndefined()
|
|
287
|
-
const parsed = parseToolResponse(result)
|
|
288
|
-
expect(parsed.action).toBe('rv_decide')
|
|
289
|
-
expect(parsed.transition).toBe('accept')
|
|
290
|
-
expect(parsed.new_status).toBe('accepted')
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
it('should reject a review with rationale', async () => {
|
|
294
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
295
|
-
mockApiSuccess({
|
|
296
|
-
action: 'rv_decide',
|
|
297
|
-
review_id: TEST_IDS.reviewId,
|
|
298
|
-
transition: 'reject',
|
|
299
|
-
previous_status: 'pending',
|
|
300
|
-
new_status: 'rejected',
|
|
301
|
-
}),
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
const { rvDecide } = await import('../tools/reviews.js')
|
|
305
|
-
const result = await rvDecide({
|
|
306
|
-
review_id: TEST_IDS.reviewId,
|
|
307
|
-
transition: 'reject',
|
|
308
|
-
rationale: 'Does not meet quality standards',
|
|
309
|
-
user_id: TEST_IDS.userId,
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
expect(result.isError).toBeUndefined()
|
|
313
|
-
const parsed = parseToolResponse(result)
|
|
314
|
-
expect(parsed.transition).toBe('reject')
|
|
315
|
-
expect(parsed.new_status).toBe('rejected')
|
|
316
|
-
|
|
317
|
-
expect(nexusPost).toHaveBeenCalledWith('/api/mcp/reviews', {
|
|
318
|
-
action: 'rv_decide',
|
|
319
|
-
review_id: TEST_IDS.reviewId,
|
|
320
|
-
transition: 'reject',
|
|
321
|
-
rationale: 'Does not meet quality standards',
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
it('should return error on API failure', async () => {
|
|
326
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
327
|
-
mockApiError('Review must be in pending state', 409),
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
const { rvDecide } = await import('../tools/reviews.js')
|
|
331
|
-
const result = await rvDecide({
|
|
332
|
-
review_id: TEST_IDS.reviewId,
|
|
333
|
-
transition: 'accept',
|
|
334
|
-
user_id: TEST_IDS.userId,
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
expect(result.isError).toBe(true)
|
|
338
|
-
})
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
// ---------------------------------------------------------------------------
|
|
342
|
-
// rv_comment
|
|
343
|
-
// ---------------------------------------------------------------------------
|
|
344
|
-
|
|
345
|
-
describe('rv_comment', () => {
|
|
346
|
-
beforeEach(() => {
|
|
347
|
-
vi.restoreAllMocks()
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
it('should add a comment to a review', async () => {
|
|
351
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
352
|
-
mockApiSuccess({
|
|
353
|
-
action: 'rv_comment',
|
|
354
|
-
comment_id: 'comment-rv-1',
|
|
355
|
-
review_id: TEST_IDS.reviewId,
|
|
356
|
-
}),
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
const { rvComment } = await import('../tools/reviews.js')
|
|
360
|
-
const result = await rvComment({
|
|
361
|
-
review_id: TEST_IDS.reviewId,
|
|
362
|
-
body: 'Looks good overall, minor nit on line 42.',
|
|
363
|
-
user_id: TEST_IDS.userId,
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
expect(result.isError).toBeUndefined()
|
|
367
|
-
const parsed = parseToolResponse(result)
|
|
368
|
-
expect(parsed.action).toBe('rv_comment')
|
|
369
|
-
expect(parsed.comment_id).toBe('comment-rv-1')
|
|
370
|
-
expect(parsed.review_id).toBe(TEST_IDS.reviewId)
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
it('should support line range for inline comments', async () => {
|
|
374
|
-
vi.mocked(nexusPost).mockResolvedValue(
|
|
375
|
-
mockApiSuccess({
|
|
376
|
-
action: 'rv_comment',
|
|
377
|
-
comment_id: 'comment-rv-2',
|
|
378
|
-
review_id: TEST_IDS.reviewId,
|
|
379
|
-
line_start: 10,
|
|
380
|
-
line_end: 15,
|
|
381
|
-
}),
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
const { rvComment } = await import('../tools/reviews.js')
|
|
385
|
-
const result = await rvComment({
|
|
386
|
-
review_id: TEST_IDS.reviewId,
|
|
387
|
-
body: 'This block could be simplified.',
|
|
388
|
-
line_start: 10,
|
|
389
|
-
line_end: 15,
|
|
390
|
-
user_id: TEST_IDS.userId,
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
expect(result.isError).toBeUndefined()
|
|
394
|
-
const parsed = parseToolResponse(result)
|
|
395
|
-
expect(parsed.line_start).toBe(10)
|
|
396
|
-
expect(parsed.line_end).toBe(15)
|
|
397
|
-
|
|
398
|
-
expect(nexusPost).toHaveBeenCalledWith('/api/mcp/reviews', {
|
|
399
|
-
action: 'rv_comment',
|
|
400
|
-
review_id: TEST_IDS.reviewId,
|
|
401
|
-
body: 'This block could be simplified.',
|
|
402
|
-
agent_id: undefined,
|
|
403
|
-
line_start: 10,
|
|
404
|
-
line_end: 15,
|
|
405
|
-
})
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
it('should return error on API failure', async () => {
|
|
409
|
-
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Review not found', 404))
|
|
410
|
-
|
|
411
|
-
const { rvComment } = await import('../tools/reviews.js')
|
|
412
|
-
const result = await rvComment({
|
|
413
|
-
review_id: TEST_IDS.reviewId,
|
|
414
|
-
body: 'Comment on missing review',
|
|
415
|
-
user_id: TEST_IDS.userId,
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
expect(result.isError).toBe(true)
|
|
419
|
-
})
|
|
420
|
-
})
|