@mpowr/nexus-mcp 0.5.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.
- package/README.md +59 -0
- package/dist/auth.d.ts +39 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +47 -0
- package/dist/auth.js.map +1 -0
- package/dist/nexus-api.d.ts +29 -0
- package/dist/nexus-api.d.ts.map +1 -0
- package/dist/nexus-api.js +76 -0
- package/dist/nexus-api.js.map +1 -0
- package/dist/server.d.ts +65 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +183 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/add-task-note.d.ts +34 -0
- package/dist/tools/add-task-note.d.ts.map +1 -0
- package/dist/tools/add-task-note.js +39 -0
- package/dist/tools/add-task-note.js.map +1 -0
- package/dist/tools/append-session-entry.d.ts +53 -0
- package/dist/tools/append-session-entry.d.ts.map +1 -0
- package/dist/tools/append-session-entry.js +67 -0
- package/dist/tools/append-session-entry.js.map +1 -0
- package/dist/tools/create-task.d.ts +52 -0
- package/dist/tools/create-task.d.ts.map +1 -0
- package/dist/tools/create-task.js +51 -0
- package/dist/tools/create-task.js.map +1 -0
- package/dist/tools/decision-comments.d.ts +54 -0
- package/dist/tools/decision-comments.d.ts.map +1 -0
- package/dist/tools/decision-comments.js +80 -0
- package/dist/tools/decision-comments.js.map +1 -0
- package/dist/tools/get-document.d.ts +47 -0
- package/dist/tools/get-document.d.ts.map +1 -0
- package/dist/tools/get-document.js +68 -0
- package/dist/tools/get-document.js.map +1 -0
- package/dist/tools/get-project-memory.d.ts +47 -0
- package/dist/tools/get-project-memory.d.ts.map +1 -0
- package/dist/tools/get-project-memory.js +53 -0
- package/dist/tools/get-project-memory.js.map +1 -0
- package/dist/tools/get-related-entities.d.ts +44 -0
- package/dist/tools/get-related-entities.d.ts.map +1 -0
- package/dist/tools/get-related-entities.js +60 -0
- package/dist/tools/get-related-entities.js.map +1 -0
- package/dist/tools/governance.d.ts +90 -0
- package/dist/tools/governance.d.ts.map +1 -0
- package/dist/tools/governance.js +124 -0
- package/dist/tools/governance.js.map +1 -0
- package/dist/tools/ingest-document.d.ts +40 -0
- package/dist/tools/ingest-document.d.ts.map +1 -0
- package/dist/tools/ingest-document.js +48 -0
- package/dist/tools/ingest-document.js.map +1 -0
- package/dist/tools/letter-inbox.d.ts +80 -0
- package/dist/tools/letter-inbox.d.ts.map +1 -0
- package/dist/tools/letter-inbox.js +118 -0
- package/dist/tools/letter-inbox.js.map +1 -0
- package/dist/tools/letters.d.ts +91 -0
- package/dist/tools/letters.d.ts.map +1 -0
- package/dist/tools/letters.js +112 -0
- package/dist/tools/letters.js.map +1 -0
- package/dist/tools/project-list.d.ts +28 -0
- package/dist/tools/project-list.d.ts.map +1 -0
- package/dist/tools/project-list.js +43 -0
- package/dist/tools/project-list.js.map +1 -0
- package/dist/tools/reviews.d.ts +145 -0
- package/dist/tools/reviews.d.ts.map +1 -0
- package/dist/tools/reviews.js +216 -0
- package/dist/tools/reviews.js.map +1 -0
- package/dist/tools/search-knowledge.d.ts +48 -0
- package/dist/tools/search-knowledge.d.ts.map +1 -0
- package/dist/tools/search-knowledge.js +54 -0
- package/dist/tools/search-knowledge.js.map +1 -0
- package/dist/tools/sessions.d.ts +81 -0
- package/dist/tools/sessions.d.ts.map +1 -0
- package/dist/tools/sessions.js +120 -0
- package/dist/tools/sessions.js.map +1 -0
- package/dist/tools/skill-assign.d.ts +77 -0
- package/dist/tools/skill-assign.d.ts.map +1 -0
- package/dist/tools/skill-assign.js +108 -0
- package/dist/tools/skill-assign.js.map +1 -0
- package/dist/tools/skills.d.ts +138 -0
- package/dist/tools/skills.d.ts.map +1 -0
- package/dist/tools/skills.js +192 -0
- package/dist/tools/skills.js.map +1 -0
- package/dist/tools/update-task-status.d.ts +48 -0
- package/dist/tools/update-task-status.d.ts.map +1 -0
- package/dist/tools/update-task-status.js +51 -0
- package/dist/tools/update-task-status.js.map +1 -0
- package/package.json +30 -0
- package/src/__tests__/auth.test.ts +162 -0
- package/src/__tests__/decision-comments.test.ts +173 -0
- package/src/__tests__/helpers.ts +58 -0
- package/src/__tests__/layer1-knowledge.test.ts +302 -0
- package/src/__tests__/layer2-coordination.test.ts +775 -0
- package/src/__tests__/layer3-governance.test.ts +205 -0
- package/src/__tests__/project-list-and-skill-assign.test.ts +282 -0
- package/src/__tests__/reviews.test.ts +420 -0
- package/src/__tests__/server.test.ts +238 -0
- package/src/__tests__/setup.ts +15 -0
- package/src/auth.ts +81 -0
- package/src/nexus-api.ts +110 -0
- package/src/server.ts +499 -0
- package/src/tools/add-task-note.ts +50 -0
- package/src/tools/append-session-entry.ts +83 -0
- package/src/tools/create-task.ts +66 -0
- package/src/tools/decision-comments.ts +102 -0
- package/src/tools/get-document.ts +80 -0
- package/src/tools/get-project-memory.ts +65 -0
- package/src/tools/get-related-entities.ts +73 -0
- package/src/tools/governance.ts +162 -0
- package/src/tools/ingest-document.ts +64 -0
- package/src/tools/letter-inbox.ts +157 -0
- package/src/tools/letters.ts +144 -0
- package/src/tools/project-list.ts +52 -0
- package/src/tools/reviews.ts +277 -0
- package/src/tools/search-knowledge.ts +68 -0
- package/src/tools/sessions.ts +154 -0
- package/src/tools/skill-assign.ts +142 -0
- package/src/tools/skills.ts +252 -0
- package/src/tools/update-task-status.ts +64 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Layer 3 Governance tools.
|
|
3
|
+
* All tools delegate to the Nexus API via nexusPost().
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
+
import { mockApiError, mockApiSuccess, parseToolResponse, TEST_IDS } from './helpers'
|
|
8
|
+
|
|
9
|
+
vi.mock('../nexus-api.js', () => ({
|
|
10
|
+
nexusPost: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
import { nexusPost } from '../nexus-api.js'
|
|
14
|
+
|
|
15
|
+
describe('Layer 3: ADR Governance tools', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.restoreAllMocks()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('createAdrDraft', () => {
|
|
21
|
+
it('should create an ADR draft with auto-incremented number', async () => {
|
|
22
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
23
|
+
mockApiSuccess({
|
|
24
|
+
action: 'adr_create',
|
|
25
|
+
adr_id: TEST_IDS.adrId,
|
|
26
|
+
adr_number: '0006',
|
|
27
|
+
project_id: TEST_IDS.projectId,
|
|
28
|
+
title: 'New ADR',
|
|
29
|
+
status: 'draft',
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const { createAdrDraft } = await import('../tools/governance.js')
|
|
34
|
+
const result = await createAdrDraft({
|
|
35
|
+
project_id: TEST_IDS.projectId,
|
|
36
|
+
title: 'New ADR',
|
|
37
|
+
context: 'ADR context',
|
|
38
|
+
decision: 'ADR decision content',
|
|
39
|
+
user_id: TEST_IDS.userId,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(result.isError).toBeUndefined()
|
|
43
|
+
const parsed = parseToolResponse(result)
|
|
44
|
+
expect(parsed.action).toBe('adr_create')
|
|
45
|
+
expect(parsed.adr_number).toBe('0006')
|
|
46
|
+
expect(parsed.status).toBe('draft')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should return error on API failure', async () => {
|
|
50
|
+
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Failed to create ADR draft'))
|
|
51
|
+
|
|
52
|
+
const { createAdrDraft } = await import('../tools/governance.js')
|
|
53
|
+
const result = await createAdrDraft({
|
|
54
|
+
project_id: TEST_IDS.projectId,
|
|
55
|
+
title: 'Fail',
|
|
56
|
+
context: 'Context',
|
|
57
|
+
decision: 'Decision',
|
|
58
|
+
user_id: TEST_IDS.userId,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(result.isError).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('submitAdrReview', () => {
|
|
66
|
+
it('should transition a draft ADR to under_review', async () => {
|
|
67
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
68
|
+
mockApiSuccess({
|
|
69
|
+
action: 'adr_submit',
|
|
70
|
+
adr_id: TEST_IDS.adrId,
|
|
71
|
+
title: 'ADR to review',
|
|
72
|
+
new_state: 'under_review',
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const { submitAdrReview } = await import('../tools/governance.js')
|
|
77
|
+
const result = await submitAdrReview({
|
|
78
|
+
adr_id: TEST_IDS.adrId,
|
|
79
|
+
user_id: TEST_IDS.userId,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
expect(result.isError).toBeUndefined()
|
|
83
|
+
const parsed = parseToolResponse(result)
|
|
84
|
+
expect(parsed.action).toBe('adr_submit')
|
|
85
|
+
expect(parsed.new_state).toBe('under_review')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should return error for non-draft ADR', async () => {
|
|
89
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
90
|
+
mockApiError('ADR must be in draft state to submit for review', 409),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const { submitAdrReview } = await import('../tools/governance.js')
|
|
94
|
+
const result = await submitAdrReview({
|
|
95
|
+
adr_id: TEST_IDS.adrId,
|
|
96
|
+
user_id: TEST_IDS.userId,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(result.isError).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should return error if ADR not found', async () => {
|
|
103
|
+
vi.mocked(nexusPost).mockResolvedValue(mockApiError('ADR not found', 404))
|
|
104
|
+
|
|
105
|
+
const { submitAdrReview } = await import('../tools/governance.js')
|
|
106
|
+
const result = await submitAdrReview({
|
|
107
|
+
adr_id: TEST_IDS.adrId,
|
|
108
|
+
user_id: TEST_IDS.userId,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
expect(result.isError).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('recordAdrDecision', () => {
|
|
116
|
+
it('should accept an ADR under review', async () => {
|
|
117
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
118
|
+
mockApiSuccess({
|
|
119
|
+
action: 'adr_decide',
|
|
120
|
+
adr_id: TEST_IDS.adrId,
|
|
121
|
+
title: 'Test ADR',
|
|
122
|
+
decision: 'accepted',
|
|
123
|
+
new_state: 'accepted',
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const { recordAdrDecision } = await import('../tools/governance.js')
|
|
128
|
+
const result = await recordAdrDecision({
|
|
129
|
+
adr_id: TEST_IDS.adrId,
|
|
130
|
+
decision: 'accepted',
|
|
131
|
+
user_id: TEST_IDS.userId,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
expect(result.isError).toBeUndefined()
|
|
135
|
+
const parsed = parseToolResponse(result)
|
|
136
|
+
expect(parsed.action).toBe('adr_decide')
|
|
137
|
+
expect(parsed.decision).toBe('accepted')
|
|
138
|
+
expect(parsed.new_state).toBe('accepted')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should reject an ADR under review', async () => {
|
|
142
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
143
|
+
mockApiSuccess({
|
|
144
|
+
action: 'adr_decide',
|
|
145
|
+
adr_id: TEST_IDS.adrId,
|
|
146
|
+
title: 'Test ADR',
|
|
147
|
+
decision: 'rejected',
|
|
148
|
+
new_state: 'rejected',
|
|
149
|
+
}),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const { recordAdrDecision } = await import('../tools/governance.js')
|
|
153
|
+
const result = await recordAdrDecision({
|
|
154
|
+
adr_id: TEST_IDS.adrId,
|
|
155
|
+
decision: 'rejected',
|
|
156
|
+
rationale: 'Does not align with platform direction',
|
|
157
|
+
user_id: TEST_IDS.userId,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
expect(result.isError).toBeUndefined()
|
|
161
|
+
const parsed = parseToolResponse(result)
|
|
162
|
+
expect(parsed.decision).toBe('rejected')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should include superseded_adr in response when accepting with supersedes', async () => {
|
|
166
|
+
const supersededId = '11111111-1111-1111-1111-111111111111'
|
|
167
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
168
|
+
mockApiSuccess({
|
|
169
|
+
action: 'adr_decide',
|
|
170
|
+
adr_id: TEST_IDS.adrId,
|
|
171
|
+
title: 'Superseding ADR',
|
|
172
|
+
decision: 'accepted',
|
|
173
|
+
new_state: 'accepted',
|
|
174
|
+
superseded_adr: supersededId,
|
|
175
|
+
}),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
const { recordAdrDecision } = await import('../tools/governance.js')
|
|
179
|
+
const result = await recordAdrDecision({
|
|
180
|
+
adr_id: TEST_IDS.adrId,
|
|
181
|
+
decision: 'accepted',
|
|
182
|
+
user_id: TEST_IDS.userId,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
expect(result.isError).toBeUndefined()
|
|
186
|
+
const parsed = parseToolResponse(result)
|
|
187
|
+
expect(parsed.superseded_adr).toBe(supersededId)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should return error for non-review ADR', async () => {
|
|
191
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
192
|
+
mockApiError('ADR must be under review to record a decision', 409),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const { recordAdrDecision } = await import('../tools/governance.js')
|
|
196
|
+
const result = await recordAdrDecision({
|
|
197
|
+
adr_id: TEST_IDS.adrId,
|
|
198
|
+
decision: 'accepted',
|
|
199
|
+
user_id: TEST_IDS.userId,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
expect(result.isError).toBe(true)
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
@@ -0,0 +1,282 @@
|
|
|
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
|
+
})
|