@vibescope/mcp-server 0.0.1 → 0.1.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 +113 -98
- package/dist/api-client.d.ts +1114 -0
- package/dist/api-client.js +698 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +39 -240
- package/dist/config/tool-categories.d.ts +31 -0
- package/dist/config/tool-categories.js +253 -0
- package/dist/handlers/blockers.js +57 -58
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +106 -476
- package/dist/handlers/cost.d.ts +1 -0
- package/dist/handlers/cost.js +35 -113
- package/dist/handlers/decisions.d.ts +2 -0
- package/dist/handlers/decisions.js +28 -27
- package/dist/handlers/deployment.js +112 -828
- package/dist/handlers/discovery.js +31 -0
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +39 -134
- package/dist/handlers/findings.js +43 -67
- package/dist/handlers/git-issues.d.ts +9 -13
- package/dist/handlers/git-issues.js +80 -225
- package/dist/handlers/ideas.d.ts +3 -0
- package/dist/handlers/ideas.js +53 -134
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.d.ts +2 -0
- package/dist/handlers/milestones.js +51 -98
- package/dist/handlers/organizations.js +79 -275
- package/dist/handlers/progress.d.ts +2 -0
- package/dist/handlers/progress.js +25 -123
- package/dist/handlers/project.js +42 -221
- package/dist/handlers/requests.d.ts +2 -0
- package/dist/handlers/requests.js +23 -83
- package/dist/handlers/session.js +99 -585
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +274 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +230 -900
- package/dist/handlers/tool-docs.d.ts +8 -0
- package/dist/handlers/tool-docs.js +657 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +26 -153
- package/dist/index.js +473 -160
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +4 -0
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1752 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +210 -0
- package/src/handlers/__test-utils__.ts +4 -134
- package/src/handlers/blockers.test.ts +114 -124
- package/src/handlers/blockers.ts +68 -70
- package/src/handlers/bodies-of-work.test.ts +236 -831
- package/src/handlers/bodies-of-work.ts +194 -525
- package/src/handlers/cost.test.ts +149 -113
- package/src/handlers/cost.ts +44 -132
- package/src/handlers/decisions.test.ts +111 -209
- package/src/handlers/decisions.ts +35 -27
- package/src/handlers/deployment.test.ts +193 -239
- package/src/handlers/deployment.ts +140 -895
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +32 -0
- package/src/handlers/fallback.test.ts +128 -361
- package/src/handlers/fallback.ts +62 -148
- package/src/handlers/findings.test.ts +127 -345
- package/src/handlers/findings.ts +49 -66
- package/src/handlers/git-issues.test.ts +623 -0
- package/src/handlers/git-issues.ts +174 -0
- package/src/handlers/ideas.test.ts +229 -343
- package/src/handlers/ideas.ts +69 -143
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +167 -281
- package/src/handlers/milestones.ts +54 -93
- package/src/handlers/organizations.test.ts +275 -467
- package/src/handlers/organizations.ts +84 -294
- package/src/handlers/progress.test.ts +112 -218
- package/src/handlers/progress.ts +29 -142
- package/src/handlers/project.test.ts +203 -226
- package/src/handlers/project.ts +48 -238
- package/src/handlers/requests.test.ts +74 -342
- package/src/handlers/requests.ts +25 -83
- package/src/handlers/session.test.ts +241 -206
- package/src/handlers/session.ts +110 -657
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +497 -0
- package/src/handlers/tasks.test.ts +608 -353
- package/src/handlers/tasks.ts +248 -1025
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +189 -572
- package/src/handlers/validation.ts +29 -166
- package/src/index.ts +473 -184
- package/src/knowledge.ts +107 -9
- package/src/tools.ts +2506 -0
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +127 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +14 -13
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -367
- package/dist/handlers/__test-utils__.d.ts +0 -72
- package/dist/handlers/__test-utils__.js +0 -176
- package/dist/handlers/checkouts.d.ts +0 -37
- package/dist/handlers/checkouts.js +0 -377
- package/dist/handlers/knowledge-query.d.ts +0 -22
- package/dist/handlers/knowledge-query.js +0 -253
- package/dist/handlers/knowledge.d.ts +0 -12
- package/dist/handlers/knowledge.js +0 -108
- package/dist/handlers/roles.d.ts +0 -30
- package/dist/handlers/roles.js +0 -281
- package/dist/handlers/tasks.test.d.ts +0 -1
- package/dist/handlers/tasks.test.js +0 -431
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -532
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -176
- package/src/tmpclaude-0078-cwd +0 -1
- package/src/tmpclaude-0ee1-cwd +0 -1
- package/src/tmpclaude-2dd5-cwd +0 -1
- package/src/tmpclaude-344c-cwd +0 -1
- package/src/tmpclaude-3860-cwd +0 -1
- package/src/tmpclaude-4b63-cwd +0 -1
- package/src/tmpclaude-5c73-cwd +0 -1
- package/src/tmpclaude-5ee3-cwd +0 -1
- package/src/tmpclaude-6795-cwd +0 -1
- package/src/tmpclaude-709e-cwd +0 -1
- package/src/tmpclaude-9839-cwd +0 -1
- package/src/tmpclaude-d829-cwd +0 -1
- package/src/tmpclaude-e072-cwd +0 -1
- package/src/tmpclaude-f6ee-cwd +0 -1
- package/tmpclaude-0439-cwd +0 -1
- package/tmpclaude-132f-cwd +0 -1
- package/tmpclaude-15bb-cwd +0 -1
- package/tmpclaude-165a-cwd +0 -1
- package/tmpclaude-1ba9-cwd +0 -1
- package/tmpclaude-21a3-cwd +0 -1
- package/tmpclaude-2a38-cwd +0 -1
- package/tmpclaude-2adf-cwd +0 -1
- package/tmpclaude-2f56-cwd +0 -1
- package/tmpclaude-3626-cwd +0 -1
- package/tmpclaude-3727-cwd +0 -1
- package/tmpclaude-40bc-cwd +0 -1
- package/tmpclaude-436f-cwd +0 -1
- package/tmpclaude-4783-cwd +0 -1
- package/tmpclaude-4b6d-cwd +0 -1
- package/tmpclaude-4ba4-cwd +0 -1
- package/tmpclaude-51e6-cwd +0 -1
- package/tmpclaude-5ecf-cwd +0 -1
- package/tmpclaude-6f97-cwd +0 -1
- package/tmpclaude-7fb2-cwd +0 -1
- package/tmpclaude-825c-cwd +0 -1
- package/tmpclaude-8baf-cwd +0 -1
- package/tmpclaude-8d9f-cwd +0 -1
- package/tmpclaude-975c-cwd +0 -1
- package/tmpclaude-9983-cwd +0 -1
- package/tmpclaude-a045-cwd +0 -1
- package/tmpclaude-ac4a-cwd +0 -1
- package/tmpclaude-b593-cwd +0 -1
- package/tmpclaude-b891-cwd +0 -1
- package/tmpclaude-c032-cwd +0 -1
- package/tmpclaude-cf43-cwd +0 -1
- package/tmpclaude-d040-cwd +0 -1
- package/tmpclaude-dcdd-cwd +0 -1
- package/tmpclaude-dcee-cwd +0 -1
- package/tmpclaude-e16b-cwd +0 -1
- package/tmpclaude-ecd2-cwd +0 -1
- package/tmpclaude-f48d-cwd +0 -1
|
@@ -15,7 +15,15 @@ import {
|
|
|
15
15
|
listProjectShares,
|
|
16
16
|
} from './organizations.js';
|
|
17
17
|
import { ValidationError } from '../validators.js';
|
|
18
|
-
import {
|
|
18
|
+
import { createMockContext } from './__test-utils__.js';
|
|
19
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Test Utilities
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
|
|
26
|
+
const OTHER_UUID = '223e4567-e89b-12d3-a456-426614174001';
|
|
19
27
|
|
|
20
28
|
// ============================================================================
|
|
21
29
|
// listOrganizations Tests
|
|
@@ -25,10 +33,11 @@ describe('listOrganizations', () => {
|
|
|
25
33
|
beforeEach(() => vi.clearAllMocks());
|
|
26
34
|
|
|
27
35
|
it('should return empty array when no organizations', async () => {
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
mockApiClient.listOrganizations.mockResolvedValue({
|
|
37
|
+
ok: true,
|
|
38
|
+
data: { organizations: [], count: 0 },
|
|
30
39
|
});
|
|
31
|
-
const ctx = createMockContext(
|
|
40
|
+
const ctx = createMockContext();
|
|
32
41
|
|
|
33
42
|
const result = await listOrganizations({}, ctx);
|
|
34
43
|
|
|
@@ -39,44 +48,34 @@ describe('listOrganizations', () => {
|
|
|
39
48
|
});
|
|
40
49
|
|
|
41
50
|
it('should return organizations with role and joined_at', async () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
logo_url: null,
|
|
54
|
-
owner_id: 'user-123',
|
|
55
|
-
created_at: '2025-01-01T00:00:00Z',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
error: null,
|
|
51
|
+
mockApiClient.listOrganizations.mockResolvedValue({
|
|
52
|
+
ok: true,
|
|
53
|
+
data: {
|
|
54
|
+
organizations: [{
|
|
55
|
+
id: 'org-1',
|
|
56
|
+
name: 'Test Org',
|
|
57
|
+
slug: 'test-org',
|
|
58
|
+
role: 'admin',
|
|
59
|
+
joined_at: '2025-01-14T12:00:00Z',
|
|
60
|
+
}],
|
|
61
|
+
count: 1,
|
|
60
62
|
},
|
|
61
63
|
});
|
|
62
|
-
const ctx = createMockContext(
|
|
64
|
+
const ctx = createMockContext();
|
|
63
65
|
|
|
64
66
|
const result = await listOrganizations({}, ctx);
|
|
65
67
|
|
|
66
|
-
expect(result.result).toMatchObject({
|
|
67
|
-
count: 1,
|
|
68
|
-
});
|
|
68
|
+
expect(result.result).toMatchObject({ count: 1 });
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it('should throw error when query fails', async () => {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
mockApiClient.listOrganizations.mockResolvedValue({
|
|
73
|
+
ok: false,
|
|
74
|
+
error: 'Failed to list organizations',
|
|
74
75
|
});
|
|
75
|
-
const ctx = createMockContext(
|
|
76
|
+
const ctx = createMockContext();
|
|
76
77
|
|
|
77
|
-
await expect(listOrganizations({}, ctx)).rejects.toThrow(
|
|
78
|
-
'Failed to list organizations: Query failed'
|
|
79
|
-
);
|
|
78
|
+
await expect(listOrganizations({}, ctx)).rejects.toThrow('Failed to list organizations');
|
|
80
79
|
});
|
|
81
80
|
});
|
|
82
81
|
|
|
@@ -88,17 +87,16 @@ describe('createOrganization', () => {
|
|
|
88
87
|
beforeEach(() => vi.clearAllMocks());
|
|
89
88
|
|
|
90
89
|
it('should throw error for missing name', async () => {
|
|
91
|
-
const
|
|
92
|
-
const ctx = createMockContext(supabase);
|
|
93
|
-
|
|
90
|
+
const ctx = createMockContext();
|
|
94
91
|
await expect(createOrganization({}, ctx)).rejects.toThrow(ValidationError);
|
|
95
92
|
});
|
|
96
93
|
|
|
97
94
|
it('should throw error when slug is taken', async () => {
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
mockApiClient.createOrganization.mockResolvedValue({
|
|
96
|
+
ok: false,
|
|
97
|
+
error: 'Organization slug "test-org" is already taken',
|
|
100
98
|
});
|
|
101
|
-
const ctx = createMockContext(
|
|
99
|
+
const ctx = createMockContext();
|
|
102
100
|
|
|
103
101
|
await expect(
|
|
104
102
|
createOrganization({ name: 'Test Org' }, ctx)
|
|
@@ -106,19 +104,16 @@ describe('createOrganization', () => {
|
|
|
106
104
|
});
|
|
107
105
|
|
|
108
106
|
it('should create organization with auto-generated slug', async () => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
owner_id: 'user-123',
|
|
117
|
-
},
|
|
118
|
-
error: null,
|
|
107
|
+
mockApiClient.createOrganization.mockResolvedValue({
|
|
108
|
+
ok: true,
|
|
109
|
+
data: {
|
|
110
|
+
success: true,
|
|
111
|
+
organization_id: 'org-1',
|
|
112
|
+
slug: 'test-org',
|
|
113
|
+
message: 'Organization "Test Org" created. You are the owner.',
|
|
119
114
|
},
|
|
120
115
|
});
|
|
121
|
-
const ctx = createMockContext(
|
|
116
|
+
const ctx = createMockContext();
|
|
122
117
|
|
|
123
118
|
const result = await createOrganization({ name: 'Test Org' }, ctx);
|
|
124
119
|
|
|
@@ -129,40 +124,30 @@ describe('createOrganization', () => {
|
|
|
129
124
|
});
|
|
130
125
|
|
|
131
126
|
it('should create organization with custom slug', async () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
data: {
|
|
136
|
-
id: 'org-1',
|
|
137
|
-
name: 'Test Org',
|
|
138
|
-
slug: 'my-custom-slug',
|
|
139
|
-
owner_id: 'user-123',
|
|
140
|
-
},
|
|
141
|
-
error: null,
|
|
142
|
-
},
|
|
127
|
+
mockApiClient.createOrganization.mockResolvedValue({
|
|
128
|
+
ok: true,
|
|
129
|
+
data: { success: true, organization_id: 'org-1', slug: 'my-custom-slug' },
|
|
143
130
|
});
|
|
144
|
-
const ctx = createMockContext(
|
|
131
|
+
const ctx = createMockContext();
|
|
145
132
|
|
|
146
133
|
const result = await createOrganization(
|
|
147
134
|
{ name: 'Test Org', slug: 'my-custom-slug' },
|
|
148
135
|
ctx
|
|
149
136
|
);
|
|
150
137
|
|
|
151
|
-
expect(result.result).toMatchObject({
|
|
152
|
-
success: true,
|
|
153
|
-
});
|
|
138
|
+
expect(result.result).toMatchObject({ success: true });
|
|
154
139
|
});
|
|
155
140
|
|
|
156
141
|
it('should throw error when insert fails', async () => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
mockApiClient.createOrganization.mockResolvedValue({
|
|
143
|
+
ok: false,
|
|
144
|
+
error: 'Failed to create organization',
|
|
160
145
|
});
|
|
161
|
-
const ctx = createMockContext(
|
|
146
|
+
const ctx = createMockContext();
|
|
162
147
|
|
|
163
148
|
await expect(
|
|
164
149
|
createOrganization({ name: 'Test Org' }, ctx)
|
|
165
|
-
).rejects.toThrow('Failed to create organization
|
|
150
|
+
).rejects.toThrow('Failed to create organization');
|
|
166
151
|
});
|
|
167
152
|
});
|
|
168
153
|
|
|
@@ -174,64 +159,51 @@ describe('updateOrganization', () => {
|
|
|
174
159
|
beforeEach(() => vi.clearAllMocks());
|
|
175
160
|
|
|
176
161
|
it('should throw error for missing organization_id', async () => {
|
|
177
|
-
const
|
|
178
|
-
const ctx = createMockContext(supabase);
|
|
179
|
-
|
|
162
|
+
const ctx = createMockContext();
|
|
180
163
|
await expect(
|
|
181
164
|
updateOrganization({ name: 'New Name' }, ctx)
|
|
182
165
|
).rejects.toThrow(ValidationError);
|
|
183
166
|
});
|
|
184
167
|
|
|
185
168
|
it('should throw error for invalid organization_id UUID', async () => {
|
|
186
|
-
const
|
|
187
|
-
const ctx = createMockContext(supabase);
|
|
188
|
-
|
|
169
|
+
const ctx = createMockContext();
|
|
189
170
|
await expect(
|
|
190
171
|
updateOrganization({ organization_id: 'invalid', name: 'New Name' }, ctx)
|
|
191
172
|
).rejects.toThrow(ValidationError);
|
|
192
173
|
});
|
|
193
174
|
|
|
194
175
|
it('should throw error when no updates provided', async () => {
|
|
195
|
-
const
|
|
196
|
-
const ctx = createMockContext(supabase);
|
|
197
|
-
|
|
176
|
+
const ctx = createMockContext();
|
|
198
177
|
await expect(
|
|
199
|
-
updateOrganization({ organization_id:
|
|
178
|
+
updateOrganization({ organization_id: VALID_UUID }, ctx)
|
|
200
179
|
).rejects.toThrow('No updates provided');
|
|
201
180
|
});
|
|
202
181
|
|
|
203
182
|
it('should update organization successfully', async () => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
error: null,
|
|
208
|
-
},
|
|
183
|
+
mockApiClient.updateOrganization.mockResolvedValue({
|
|
184
|
+
ok: true,
|
|
185
|
+
data: { success: true, organization_id: VALID_UUID },
|
|
209
186
|
});
|
|
210
|
-
const ctx = createMockContext(
|
|
187
|
+
const ctx = createMockContext();
|
|
211
188
|
|
|
212
189
|
const result = await updateOrganization(
|
|
213
|
-
{
|
|
214
|
-
organization_id: testUUID(),
|
|
215
|
-
name: 'Updated Org',
|
|
216
|
-
description: 'New desc',
|
|
217
|
-
},
|
|
190
|
+
{ organization_id: VALID_UUID, name: 'Updated Org', description: 'New desc' },
|
|
218
191
|
ctx
|
|
219
192
|
);
|
|
220
193
|
|
|
221
|
-
expect(result.result).toMatchObject({
|
|
222
|
-
success: true,
|
|
223
|
-
});
|
|
194
|
+
expect(result.result).toMatchObject({ success: true });
|
|
224
195
|
});
|
|
225
196
|
|
|
226
197
|
it('should throw error when update fails', async () => {
|
|
227
|
-
|
|
228
|
-
|
|
198
|
+
mockApiClient.updateOrganization.mockResolvedValue({
|
|
199
|
+
ok: false,
|
|
200
|
+
error: 'Failed to update organization',
|
|
229
201
|
});
|
|
230
|
-
const ctx = createMockContext(
|
|
202
|
+
const ctx = createMockContext();
|
|
231
203
|
|
|
232
204
|
await expect(
|
|
233
|
-
updateOrganization({ organization_id:
|
|
234
|
-
).rejects.toThrow('Failed to update organization
|
|
205
|
+
updateOrganization({ organization_id: VALID_UUID, name: 'New Name' }, ctx)
|
|
206
|
+
).rejects.toThrow('Failed to update organization');
|
|
235
207
|
});
|
|
236
208
|
});
|
|
237
209
|
|
|
@@ -243,28 +215,25 @@ describe('deleteOrganization', () => {
|
|
|
243
215
|
beforeEach(() => vi.clearAllMocks());
|
|
244
216
|
|
|
245
217
|
it('should throw error for missing organization_id', async () => {
|
|
246
|
-
const
|
|
247
|
-
const ctx = createMockContext(supabase);
|
|
248
|
-
|
|
218
|
+
const ctx = createMockContext();
|
|
249
219
|
await expect(deleteOrganization({}, ctx)).rejects.toThrow(ValidationError);
|
|
250
220
|
});
|
|
251
221
|
|
|
252
222
|
it('should throw error for invalid organization_id UUID', async () => {
|
|
253
|
-
const
|
|
254
|
-
const ctx = createMockContext(supabase);
|
|
255
|
-
|
|
223
|
+
const ctx = createMockContext();
|
|
256
224
|
await expect(
|
|
257
225
|
deleteOrganization({ organization_id: 'invalid' }, ctx)
|
|
258
226
|
).rejects.toThrow(ValidationError);
|
|
259
227
|
});
|
|
260
228
|
|
|
261
229
|
it('should delete organization successfully', async () => {
|
|
262
|
-
|
|
263
|
-
|
|
230
|
+
mockApiClient.deleteOrganization.mockResolvedValue({
|
|
231
|
+
ok: true,
|
|
232
|
+
data: { success: true, message: 'Organization deleted. All shares have been removed.' },
|
|
264
233
|
});
|
|
265
|
-
const ctx = createMockContext(
|
|
234
|
+
const ctx = createMockContext();
|
|
266
235
|
|
|
267
|
-
const result = await deleteOrganization({ organization_id:
|
|
236
|
+
const result = await deleteOrganization({ organization_id: VALID_UUID }, ctx);
|
|
268
237
|
|
|
269
238
|
expect(result.result).toMatchObject({
|
|
270
239
|
success: true,
|
|
@@ -273,14 +242,15 @@ describe('deleteOrganization', () => {
|
|
|
273
242
|
});
|
|
274
243
|
|
|
275
244
|
it('should throw error when delete fails', async () => {
|
|
276
|
-
|
|
277
|
-
|
|
245
|
+
mockApiClient.deleteOrganization.mockResolvedValue({
|
|
246
|
+
ok: false,
|
|
247
|
+
error: 'Failed to delete organization',
|
|
278
248
|
});
|
|
279
|
-
const ctx = createMockContext(
|
|
249
|
+
const ctx = createMockContext();
|
|
280
250
|
|
|
281
251
|
await expect(
|
|
282
|
-
deleteOrganization({ organization_id:
|
|
283
|
-
).rejects.toThrow('Failed to delete organization
|
|
252
|
+
deleteOrganization({ organization_id: VALID_UUID }, ctx)
|
|
253
|
+
).rejects.toThrow('Failed to delete organization');
|
|
284
254
|
});
|
|
285
255
|
});
|
|
286
256
|
|
|
@@ -292,63 +262,57 @@ describe('listOrgMembers', () => {
|
|
|
292
262
|
beforeEach(() => vi.clearAllMocks());
|
|
293
263
|
|
|
294
264
|
it('should throw error for missing organization_id', async () => {
|
|
295
|
-
const
|
|
296
|
-
const ctx = createMockContext(supabase);
|
|
297
|
-
|
|
265
|
+
const ctx = createMockContext();
|
|
298
266
|
await expect(listOrgMembers({}, ctx)).rejects.toThrow(ValidationError);
|
|
299
267
|
});
|
|
300
268
|
|
|
301
269
|
it('should throw error for invalid organization_id UUID', async () => {
|
|
302
|
-
const
|
|
303
|
-
const ctx = createMockContext(supabase);
|
|
304
|
-
|
|
270
|
+
const ctx = createMockContext();
|
|
305
271
|
await expect(
|
|
306
272
|
listOrgMembers({ organization_id: 'invalid' }, ctx)
|
|
307
273
|
).rejects.toThrow(ValidationError);
|
|
308
274
|
});
|
|
309
275
|
|
|
310
276
|
it('should return empty array when no members', async () => {
|
|
311
|
-
|
|
312
|
-
|
|
277
|
+
mockApiClient.listOrgMembers.mockResolvedValue({
|
|
278
|
+
ok: true,
|
|
279
|
+
data: { members: [], count: 0 },
|
|
313
280
|
});
|
|
314
|
-
const ctx = createMockContext(
|
|
281
|
+
const ctx = createMockContext();
|
|
315
282
|
|
|
316
|
-
const result = await listOrgMembers({ organization_id:
|
|
283
|
+
const result = await listOrgMembers({ organization_id: VALID_UUID }, ctx);
|
|
317
284
|
|
|
318
|
-
expect(result.result).toMatchObject({
|
|
319
|
-
members: [],
|
|
320
|
-
count: 0,
|
|
321
|
-
});
|
|
285
|
+
expect(result.result).toMatchObject({ members: [], count: 0 });
|
|
322
286
|
});
|
|
323
287
|
|
|
324
288
|
it('should return members list', async () => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
{ id: 'm-
|
|
289
|
+
mockApiClient.listOrgMembers.mockResolvedValue({
|
|
290
|
+
ok: true,
|
|
291
|
+
data: {
|
|
292
|
+
members: [
|
|
293
|
+
{ id: 'm-1', user_id: 'u-1', role: 'owner' },
|
|
294
|
+
{ id: 'm-2', user_id: 'u-2', role: 'member' },
|
|
330
295
|
],
|
|
331
|
-
|
|
296
|
+
count: 2,
|
|
332
297
|
},
|
|
333
298
|
});
|
|
334
|
-
const ctx = createMockContext(
|
|
299
|
+
const ctx = createMockContext();
|
|
335
300
|
|
|
336
|
-
const result = await listOrgMembers({ organization_id:
|
|
301
|
+
const result = await listOrgMembers({ organization_id: VALID_UUID }, ctx);
|
|
337
302
|
|
|
338
|
-
expect(result.result).toMatchObject({
|
|
339
|
-
count: 2,
|
|
340
|
-
});
|
|
303
|
+
expect(result.result).toMatchObject({ count: 2 });
|
|
341
304
|
});
|
|
342
305
|
|
|
343
306
|
it('should throw error when query fails', async () => {
|
|
344
|
-
|
|
345
|
-
|
|
307
|
+
mockApiClient.listOrgMembers.mockResolvedValue({
|
|
308
|
+
ok: false,
|
|
309
|
+
error: 'Failed to list members',
|
|
346
310
|
});
|
|
347
|
-
const ctx = createMockContext(
|
|
311
|
+
const ctx = createMockContext();
|
|
348
312
|
|
|
349
313
|
await expect(
|
|
350
|
-
listOrgMembers({ organization_id:
|
|
351
|
-
).rejects.toThrow('Failed to list members
|
|
314
|
+
listOrgMembers({ organization_id: VALID_UUID }, ctx)
|
|
315
|
+
).rejects.toThrow('Failed to list members');
|
|
352
316
|
});
|
|
353
317
|
});
|
|
354
318
|
|
|
@@ -360,69 +324,44 @@ describe('inviteMember', () => {
|
|
|
360
324
|
beforeEach(() => vi.clearAllMocks());
|
|
361
325
|
|
|
362
326
|
it('should throw error for missing organization_id', async () => {
|
|
363
|
-
const
|
|
364
|
-
const ctx = createMockContext(supabase);
|
|
365
|
-
|
|
327
|
+
const ctx = createMockContext();
|
|
366
328
|
await expect(
|
|
367
329
|
inviteMember({ email: 'test@example.com' }, ctx)
|
|
368
330
|
).rejects.toThrow(ValidationError);
|
|
369
331
|
});
|
|
370
332
|
|
|
371
333
|
it('should throw error for missing email', async () => {
|
|
372
|
-
const
|
|
373
|
-
const ctx = createMockContext(supabase);
|
|
374
|
-
|
|
334
|
+
const ctx = createMockContext();
|
|
375
335
|
await expect(
|
|
376
|
-
inviteMember({ organization_id:
|
|
336
|
+
inviteMember({ organization_id: VALID_UUID }, ctx)
|
|
377
337
|
).rejects.toThrow(ValidationError);
|
|
378
338
|
});
|
|
379
339
|
|
|
380
|
-
it('should throw error for invalid role', async () => {
|
|
381
|
-
const supabase = createMockSupabase({
|
|
382
|
-
selectResult: { data: null, error: null }, // No existing invite
|
|
383
|
-
});
|
|
384
|
-
const ctx = createMockContext(supabase);
|
|
385
|
-
|
|
386
|
-
await expect(
|
|
387
|
-
inviteMember({
|
|
388
|
-
organization_id: testUUID(),
|
|
389
|
-
email: 'test@example.com',
|
|
390
|
-
role: 'owner', // Invalid - can't invite as owner
|
|
391
|
-
}, ctx)
|
|
392
|
-
).rejects.toThrow('Invalid role. Must be admin, member, or viewer.');
|
|
393
|
-
});
|
|
394
|
-
|
|
395
340
|
it('should throw error when invite already exists', async () => {
|
|
396
|
-
|
|
397
|
-
|
|
341
|
+
mockApiClient.inviteMember.mockResolvedValue({
|
|
342
|
+
ok: false,
|
|
343
|
+
error: 'A pending invite already exists for test@example.com',
|
|
398
344
|
});
|
|
399
|
-
const ctx = createMockContext(
|
|
345
|
+
const ctx = createMockContext();
|
|
400
346
|
|
|
401
347
|
await expect(
|
|
402
|
-
inviteMember({
|
|
403
|
-
organization_id: testUUID(),
|
|
404
|
-
email: 'test@example.com',
|
|
405
|
-
}, ctx)
|
|
348
|
+
inviteMember({ organization_id: VALID_UUID, email: 'test@example.com' }, ctx)
|
|
406
349
|
).rejects.toThrow('A pending invite already exists for test@example.com');
|
|
407
350
|
});
|
|
408
351
|
|
|
409
352
|
it('should create invite successfully with default role', async () => {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
role: 'member',
|
|
417
|
-
token: 'abc123',
|
|
418
|
-
},
|
|
419
|
-
error: null,
|
|
353
|
+
mockApiClient.inviteMember.mockResolvedValue({
|
|
354
|
+
ok: true,
|
|
355
|
+
data: {
|
|
356
|
+
success: true,
|
|
357
|
+
invite_id: 'invite-1',
|
|
358
|
+
message: 'Invite sent to test@example.com with role "member"',
|
|
420
359
|
},
|
|
421
360
|
});
|
|
422
|
-
const ctx = createMockContext(
|
|
361
|
+
const ctx = createMockContext();
|
|
423
362
|
|
|
424
363
|
const result = await inviteMember({
|
|
425
|
-
organization_id:
|
|
364
|
+
organization_id: VALID_UUID,
|
|
426
365
|
email: 'test@example.com',
|
|
427
366
|
}, ctx);
|
|
428
367
|
|
|
@@ -433,18 +372,15 @@ describe('inviteMember', () => {
|
|
|
433
372
|
});
|
|
434
373
|
|
|
435
374
|
it('should throw error when insert fails', async () => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
375
|
+
mockApiClient.inviteMember.mockResolvedValue({
|
|
376
|
+
ok: false,
|
|
377
|
+
error: 'Failed to create invite',
|
|
439
378
|
});
|
|
440
|
-
const ctx = createMockContext(
|
|
379
|
+
const ctx = createMockContext();
|
|
441
380
|
|
|
442
381
|
await expect(
|
|
443
|
-
inviteMember({
|
|
444
|
-
|
|
445
|
-
email: 'test@example.com',
|
|
446
|
-
}, ctx)
|
|
447
|
-
).rejects.toThrow('Failed to create invite: Insert failed');
|
|
382
|
+
inviteMember({ organization_id: VALID_UUID, email: 'test@example.com' }, ctx)
|
|
383
|
+
).rejects.toThrow('Failed to create invite');
|
|
448
384
|
});
|
|
449
385
|
});
|
|
450
386
|
|
|
@@ -456,105 +392,72 @@ describe('updateMemberRole', () => {
|
|
|
456
392
|
beforeEach(() => vi.clearAllMocks());
|
|
457
393
|
|
|
458
394
|
it('should throw error for missing organization_id', async () => {
|
|
459
|
-
const
|
|
460
|
-
const ctx = createMockContext(supabase);
|
|
461
|
-
|
|
395
|
+
const ctx = createMockContext();
|
|
462
396
|
await expect(
|
|
463
|
-
updateMemberRole({ user_id:
|
|
397
|
+
updateMemberRole({ user_id: VALID_UUID, role: 'admin' }, ctx)
|
|
464
398
|
).rejects.toThrow(ValidationError);
|
|
465
399
|
});
|
|
466
400
|
|
|
467
401
|
it('should throw error for missing user_id', async () => {
|
|
468
|
-
const
|
|
469
|
-
const ctx = createMockContext(supabase);
|
|
470
|
-
|
|
402
|
+
const ctx = createMockContext();
|
|
471
403
|
await expect(
|
|
472
|
-
updateMemberRole({ organization_id:
|
|
404
|
+
updateMemberRole({ organization_id: VALID_UUID, role: 'admin' }, ctx)
|
|
473
405
|
).rejects.toThrow(ValidationError);
|
|
474
406
|
});
|
|
475
407
|
|
|
476
408
|
it('should throw error for missing role', async () => {
|
|
477
|
-
const
|
|
478
|
-
const ctx = createMockContext(supabase);
|
|
479
|
-
|
|
409
|
+
const ctx = createMockContext();
|
|
480
410
|
await expect(
|
|
481
|
-
updateMemberRole({ organization_id:
|
|
411
|
+
updateMemberRole({ organization_id: VALID_UUID, user_id: OTHER_UUID }, ctx)
|
|
482
412
|
).rejects.toThrow(ValidationError);
|
|
483
413
|
});
|
|
484
414
|
|
|
485
|
-
it('should throw error for invalid role', async () => {
|
|
486
|
-
const supabase = createMockSupabase();
|
|
487
|
-
const ctx = createMockContext(supabase);
|
|
488
|
-
|
|
489
|
-
await expect(
|
|
490
|
-
updateMemberRole({
|
|
491
|
-
organization_id: testUUID(),
|
|
492
|
-
user_id: testUUID(),
|
|
493
|
-
role: 'superadmin',
|
|
494
|
-
}, ctx)
|
|
495
|
-
).rejects.toThrow('Invalid role. Must be one of: viewer, member, admin, owner');
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('should throw error when trying to assign owner role', async () => {
|
|
499
|
-
const supabase = createMockSupabase();
|
|
500
|
-
const ctx = createMockContext(supabase);
|
|
501
|
-
|
|
502
|
-
await expect(
|
|
503
|
-
updateMemberRole({
|
|
504
|
-
organization_id: testUUID(),
|
|
505
|
-
user_id: testUUID(),
|
|
506
|
-
role: 'owner',
|
|
507
|
-
}, ctx)
|
|
508
|
-
).rejects.toThrow('Cannot assign owner role. Use transfer ownership instead.');
|
|
509
|
-
});
|
|
510
|
-
|
|
511
415
|
it('should throw error when changing own role', async () => {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
416
|
+
mockApiClient.updateMemberRole.mockResolvedValue({
|
|
417
|
+
ok: false,
|
|
418
|
+
error: 'Cannot change your own role',
|
|
419
|
+
});
|
|
420
|
+
const ctx = createMockContext({ userId: VALID_UUID });
|
|
515
421
|
|
|
516
422
|
await expect(
|
|
517
423
|
updateMemberRole({
|
|
518
|
-
organization_id:
|
|
519
|
-
user_id:
|
|
424
|
+
organization_id: OTHER_UUID,
|
|
425
|
+
user_id: VALID_UUID,
|
|
520
426
|
role: 'admin',
|
|
521
427
|
}, ctx)
|
|
522
428
|
).rejects.toThrow('Cannot change your own role');
|
|
523
429
|
});
|
|
524
430
|
|
|
525
431
|
it('should update member role successfully', async () => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
error: null,
|
|
530
|
-
},
|
|
432
|
+
mockApiClient.updateMemberRole.mockResolvedValue({
|
|
433
|
+
ok: true,
|
|
434
|
+
data: { success: true, user_id: OTHER_UUID, role: 'admin' },
|
|
531
435
|
});
|
|
532
|
-
const ctx = createMockContext(
|
|
436
|
+
const ctx = createMockContext();
|
|
533
437
|
|
|
534
438
|
const result = await updateMemberRole({
|
|
535
|
-
organization_id:
|
|
536
|
-
user_id:
|
|
439
|
+
organization_id: VALID_UUID,
|
|
440
|
+
user_id: OTHER_UUID,
|
|
537
441
|
role: 'admin',
|
|
538
442
|
}, ctx);
|
|
539
443
|
|
|
540
|
-
expect(result.result).toMatchObject({
|
|
541
|
-
success: true,
|
|
542
|
-
});
|
|
444
|
+
expect(result.result).toMatchObject({ success: true });
|
|
543
445
|
});
|
|
544
446
|
|
|
545
447
|
it('should throw error when update fails', async () => {
|
|
546
|
-
|
|
547
|
-
|
|
448
|
+
mockApiClient.updateMemberRole.mockResolvedValue({
|
|
449
|
+
ok: false,
|
|
450
|
+
error: 'Failed to update member role',
|
|
548
451
|
});
|
|
549
|
-
const ctx = createMockContext(
|
|
452
|
+
const ctx = createMockContext();
|
|
550
453
|
|
|
551
454
|
await expect(
|
|
552
455
|
updateMemberRole({
|
|
553
|
-
organization_id:
|
|
554
|
-
user_id:
|
|
456
|
+
organization_id: VALID_UUID,
|
|
457
|
+
user_id: OTHER_UUID,
|
|
555
458
|
role: 'admin',
|
|
556
459
|
}, ctx)
|
|
557
|
-
).rejects.toThrow('Failed to update member role
|
|
460
|
+
).rejects.toThrow('Failed to update member role');
|
|
558
461
|
});
|
|
559
462
|
});
|
|
560
463
|
|
|
@@ -566,32 +469,32 @@ describe('removeMember', () => {
|
|
|
566
469
|
beforeEach(() => vi.clearAllMocks());
|
|
567
470
|
|
|
568
471
|
it('should throw error for missing organization_id', async () => {
|
|
569
|
-
const
|
|
570
|
-
const ctx = createMockContext(supabase);
|
|
571
|
-
|
|
472
|
+
const ctx = createMockContext();
|
|
572
473
|
await expect(
|
|
573
|
-
removeMember({ user_id:
|
|
474
|
+
removeMember({ user_id: VALID_UUID }, ctx)
|
|
574
475
|
).rejects.toThrow(ValidationError);
|
|
575
476
|
});
|
|
576
477
|
|
|
577
478
|
it('should throw error for missing user_id', async () => {
|
|
578
|
-
const
|
|
579
|
-
const ctx = createMockContext(supabase);
|
|
580
|
-
|
|
479
|
+
const ctx = createMockContext();
|
|
581
480
|
await expect(
|
|
582
|
-
removeMember({ organization_id:
|
|
481
|
+
removeMember({ organization_id: VALID_UUID }, ctx)
|
|
583
482
|
).rejects.toThrow(ValidationError);
|
|
584
483
|
});
|
|
585
484
|
|
|
586
485
|
it('should remove member successfully', async () => {
|
|
587
|
-
|
|
588
|
-
|
|
486
|
+
mockApiClient.removeMember.mockResolvedValue({
|
|
487
|
+
ok: true,
|
|
488
|
+
data: {
|
|
489
|
+
success: true,
|
|
490
|
+
message: 'Member removed. Their org-scoped API keys have been invalidated.',
|
|
491
|
+
},
|
|
589
492
|
});
|
|
590
|
-
const ctx = createMockContext(
|
|
493
|
+
const ctx = createMockContext();
|
|
591
494
|
|
|
592
495
|
const result = await removeMember({
|
|
593
|
-
organization_id:
|
|
594
|
-
user_id:
|
|
496
|
+
organization_id: VALID_UUID,
|
|
497
|
+
user_id: OTHER_UUID,
|
|
595
498
|
}, ctx);
|
|
596
499
|
|
|
597
500
|
expect(result.result).toMatchObject({
|
|
@@ -601,17 +504,15 @@ describe('removeMember', () => {
|
|
|
601
504
|
});
|
|
602
505
|
|
|
603
506
|
it('should throw error when delete fails', async () => {
|
|
604
|
-
|
|
605
|
-
|
|
507
|
+
mockApiClient.removeMember.mockResolvedValue({
|
|
508
|
+
ok: false,
|
|
509
|
+
error: 'Failed to remove member',
|
|
606
510
|
});
|
|
607
|
-
const ctx = createMockContext(
|
|
511
|
+
const ctx = createMockContext();
|
|
608
512
|
|
|
609
513
|
await expect(
|
|
610
|
-
removeMember({
|
|
611
|
-
|
|
612
|
-
user_id: testUUID(),
|
|
613
|
-
}, ctx)
|
|
614
|
-
).rejects.toThrow('Failed to remove member: Delete failed');
|
|
514
|
+
removeMember({ organization_id: VALID_UUID, user_id: OTHER_UUID }, ctx)
|
|
515
|
+
).rejects.toThrow('Failed to remove member');
|
|
615
516
|
});
|
|
616
517
|
});
|
|
617
518
|
|
|
@@ -623,31 +524,30 @@ describe('leaveOrganization', () => {
|
|
|
623
524
|
beforeEach(() => vi.clearAllMocks());
|
|
624
525
|
|
|
625
526
|
it('should throw error for missing organization_id', async () => {
|
|
626
|
-
const
|
|
627
|
-
const ctx = createMockContext(supabase);
|
|
628
|
-
|
|
527
|
+
const ctx = createMockContext();
|
|
629
528
|
await expect(leaveOrganization({}, ctx)).rejects.toThrow(ValidationError);
|
|
630
529
|
});
|
|
631
530
|
|
|
632
531
|
it('should throw error when user is owner', async () => {
|
|
633
|
-
|
|
634
|
-
|
|
532
|
+
mockApiClient.leaveOrganization.mockResolvedValue({
|
|
533
|
+
ok: false,
|
|
534
|
+
error: 'Owner cannot leave. Transfer ownership first or delete the organization.',
|
|
635
535
|
});
|
|
636
|
-
const ctx = createMockContext(
|
|
536
|
+
const ctx = createMockContext();
|
|
637
537
|
|
|
638
538
|
await expect(
|
|
639
|
-
leaveOrganization({ organization_id:
|
|
539
|
+
leaveOrganization({ organization_id: VALID_UUID }, ctx)
|
|
640
540
|
).rejects.toThrow('Owner cannot leave. Transfer ownership first or delete the organization.');
|
|
641
541
|
});
|
|
642
542
|
|
|
643
543
|
it('should leave organization successfully', async () => {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
544
|
+
mockApiClient.leaveOrganization.mockResolvedValue({
|
|
545
|
+
ok: true,
|
|
546
|
+
data: { success: true, message: 'You have left the organization.' },
|
|
647
547
|
});
|
|
648
|
-
const ctx = createMockContext(
|
|
548
|
+
const ctx = createMockContext();
|
|
649
549
|
|
|
650
|
-
const result = await leaveOrganization({ organization_id:
|
|
550
|
+
const result = await leaveOrganization({ organization_id: VALID_UUID }, ctx);
|
|
651
551
|
|
|
652
552
|
expect(result.result).toMatchObject({
|
|
653
553
|
success: true,
|
|
@@ -656,15 +556,15 @@ describe('leaveOrganization', () => {
|
|
|
656
556
|
});
|
|
657
557
|
|
|
658
558
|
it('should throw error when delete fails', async () => {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
559
|
+
mockApiClient.leaveOrganization.mockResolvedValue({
|
|
560
|
+
ok: false,
|
|
561
|
+
error: 'Failed to leave organization',
|
|
662
562
|
});
|
|
663
|
-
const ctx = createMockContext(
|
|
563
|
+
const ctx = createMockContext();
|
|
664
564
|
|
|
665
565
|
await expect(
|
|
666
|
-
leaveOrganization({ organization_id:
|
|
667
|
-
).rejects.toThrow('Failed to leave organization
|
|
566
|
+
leaveOrganization({ organization_id: VALID_UUID }, ctx)
|
|
567
|
+
).rejects.toThrow('Failed to leave organization');
|
|
668
568
|
});
|
|
669
569
|
});
|
|
670
570
|
|
|
@@ -676,109 +576,55 @@ describe('shareProjectWithOrg', () => {
|
|
|
676
576
|
beforeEach(() => vi.clearAllMocks());
|
|
677
577
|
|
|
678
578
|
it('should throw error for missing project_id', async () => {
|
|
679
|
-
const
|
|
680
|
-
const ctx = createMockContext(supabase);
|
|
681
|
-
|
|
579
|
+
const ctx = createMockContext();
|
|
682
580
|
await expect(
|
|
683
|
-
shareProjectWithOrg({ organization_id:
|
|
581
|
+
shareProjectWithOrg({ organization_id: VALID_UUID }, ctx)
|
|
684
582
|
).rejects.toThrow(ValidationError);
|
|
685
583
|
});
|
|
686
584
|
|
|
687
585
|
it('should throw error for missing organization_id', async () => {
|
|
688
|
-
const
|
|
689
|
-
const ctx = createMockContext(supabase);
|
|
690
|
-
|
|
586
|
+
const ctx = createMockContext();
|
|
691
587
|
await expect(
|
|
692
|
-
shareProjectWithOrg({ project_id:
|
|
588
|
+
shareProjectWithOrg({ project_id: VALID_UUID }, ctx)
|
|
693
589
|
).rejects.toThrow(ValidationError);
|
|
694
590
|
});
|
|
695
591
|
|
|
696
|
-
it('should throw error for invalid permission', async () => {
|
|
697
|
-
const supabase = createMockSupabase();
|
|
698
|
-
const ctx = createMockContext(supabase);
|
|
699
|
-
|
|
700
|
-
await expect(
|
|
701
|
-
shareProjectWithOrg({
|
|
702
|
-
project_id: testUUID(),
|
|
703
|
-
organization_id: testUUID(),
|
|
704
|
-
permission: 'invalid',
|
|
705
|
-
}, ctx)
|
|
706
|
-
).rejects.toThrow('Invalid permission. Must be one of: read, write, admin');
|
|
707
|
-
});
|
|
708
|
-
|
|
709
592
|
it('should throw error when project not found or not owned', async () => {
|
|
710
|
-
|
|
711
|
-
|
|
593
|
+
mockApiClient.shareProjectWithOrg.mockResolvedValue({
|
|
594
|
+
ok: false,
|
|
595
|
+
error: 'Project not found or you are not the owner',
|
|
712
596
|
});
|
|
713
|
-
const ctx = createMockContext(
|
|
597
|
+
const ctx = createMockContext();
|
|
714
598
|
|
|
715
599
|
await expect(
|
|
716
|
-
shareProjectWithOrg({
|
|
717
|
-
project_id: testUUID(),
|
|
718
|
-
organization_id: testUUID(),
|
|
719
|
-
}, ctx)
|
|
600
|
+
shareProjectWithOrg({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
|
|
720
601
|
).rejects.toThrow('Project not found or you are not the owner');
|
|
721
602
|
});
|
|
722
603
|
|
|
723
604
|
it('should share project successfully with default permission', async () => {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
return supabase;
|
|
730
|
-
});
|
|
731
|
-
supabase.single = vi.fn(() => {
|
|
732
|
-
if (selectCount === 1) {
|
|
733
|
-
// First call: get project
|
|
734
|
-
return Promise.resolve({ data: { id: testUUID(), name: 'Test Project' }, error: null });
|
|
735
|
-
}
|
|
736
|
-
// Insert result
|
|
737
|
-
return Promise.resolve({
|
|
738
|
-
data: { id: 'share-1', permission: 'read' },
|
|
739
|
-
error: null,
|
|
740
|
-
});
|
|
741
|
-
});
|
|
742
|
-
supabase.maybeSingle = vi.fn(() => {
|
|
743
|
-
// Check for existing share
|
|
744
|
-
return Promise.resolve({ data: null, error: null });
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
const ctx = createMockContext(supabase);
|
|
605
|
+
mockApiClient.shareProjectWithOrg.mockResolvedValue({
|
|
606
|
+
ok: true,
|
|
607
|
+
data: { success: true, share_id: 'share-1', permission: 'read' },
|
|
608
|
+
});
|
|
609
|
+
const ctx = createMockContext();
|
|
748
610
|
|
|
749
611
|
const result = await shareProjectWithOrg({
|
|
750
|
-
project_id:
|
|
751
|
-
organization_id:
|
|
612
|
+
project_id: VALID_UUID,
|
|
613
|
+
organization_id: OTHER_UUID,
|
|
752
614
|
}, ctx);
|
|
753
615
|
|
|
754
|
-
expect(result.result).toMatchObject({
|
|
755
|
-
success: true,
|
|
756
|
-
});
|
|
616
|
+
expect(result.result).toMatchObject({ success: true });
|
|
757
617
|
});
|
|
758
618
|
|
|
759
619
|
it('should throw error when share already exists', async () => {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
selectCount++;
|
|
764
|
-
return supabase;
|
|
765
|
-
});
|
|
766
|
-
supabase.single = vi.fn(() => {
|
|
767
|
-
// First call: get project
|
|
768
|
-
return Promise.resolve({ data: { id: testUUID(), name: 'Test Project' }, error: null });
|
|
620
|
+
mockApiClient.shareProjectWithOrg.mockResolvedValue({
|
|
621
|
+
ok: false,
|
|
622
|
+
error: 'Project is already shared with this organization',
|
|
769
623
|
});
|
|
770
|
-
|
|
771
|
-
// Check for existing share - exists
|
|
772
|
-
return Promise.resolve({ data: { id: 'existing-share' }, error: null });
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
const ctx = createMockContext(supabase);
|
|
624
|
+
const ctx = createMockContext();
|
|
776
625
|
|
|
777
626
|
await expect(
|
|
778
|
-
shareProjectWithOrg({
|
|
779
|
-
project_id: testUUID(),
|
|
780
|
-
organization_id: testUUID(),
|
|
781
|
-
}, ctx)
|
|
627
|
+
shareProjectWithOrg({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
|
|
782
628
|
).rejects.toThrow('Project is already shared with this organization');
|
|
783
629
|
});
|
|
784
630
|
});
|
|
@@ -791,78 +637,56 @@ describe('updateProjectShare', () => {
|
|
|
791
637
|
beforeEach(() => vi.clearAllMocks());
|
|
792
638
|
|
|
793
639
|
it('should throw error for missing project_id', async () => {
|
|
794
|
-
const
|
|
795
|
-
const ctx = createMockContext(supabase);
|
|
796
|
-
|
|
640
|
+
const ctx = createMockContext();
|
|
797
641
|
await expect(
|
|
798
|
-
updateProjectShare({ organization_id:
|
|
642
|
+
updateProjectShare({ organization_id: VALID_UUID, permission: 'write' }, ctx)
|
|
799
643
|
).rejects.toThrow(ValidationError);
|
|
800
644
|
});
|
|
801
645
|
|
|
802
646
|
it('should throw error for missing organization_id', async () => {
|
|
803
|
-
const
|
|
804
|
-
const ctx = createMockContext(supabase);
|
|
805
|
-
|
|
647
|
+
const ctx = createMockContext();
|
|
806
648
|
await expect(
|
|
807
|
-
updateProjectShare({ project_id:
|
|
649
|
+
updateProjectShare({ project_id: VALID_UUID, permission: 'write' }, ctx)
|
|
808
650
|
).rejects.toThrow(ValidationError);
|
|
809
651
|
});
|
|
810
652
|
|
|
811
653
|
it('should throw error for missing permission', async () => {
|
|
812
|
-
const
|
|
813
|
-
const ctx = createMockContext(supabase);
|
|
814
|
-
|
|
654
|
+
const ctx = createMockContext();
|
|
815
655
|
await expect(
|
|
816
|
-
updateProjectShare({ project_id:
|
|
656
|
+
updateProjectShare({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
|
|
817
657
|
).rejects.toThrow(ValidationError);
|
|
818
658
|
});
|
|
819
659
|
|
|
820
|
-
it('should throw error for invalid permission', async () => {
|
|
821
|
-
const supabase = createMockSupabase();
|
|
822
|
-
const ctx = createMockContext(supabase);
|
|
823
|
-
|
|
824
|
-
await expect(
|
|
825
|
-
updateProjectShare({
|
|
826
|
-
project_id: testUUID(),
|
|
827
|
-
organization_id: testUUID(),
|
|
828
|
-
permission: 'invalid',
|
|
829
|
-
}, ctx)
|
|
830
|
-
).rejects.toThrow('Invalid permission. Must be one of: read, write, admin');
|
|
831
|
-
});
|
|
832
|
-
|
|
833
660
|
it('should update share permission successfully', async () => {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
error: null,
|
|
838
|
-
},
|
|
661
|
+
mockApiClient.updateProjectShare.mockResolvedValue({
|
|
662
|
+
ok: true,
|
|
663
|
+
data: { success: true, share_id: 'share-1', permission: 'write' },
|
|
839
664
|
});
|
|
840
|
-
const ctx = createMockContext(
|
|
665
|
+
const ctx = createMockContext();
|
|
841
666
|
|
|
842
667
|
const result = await updateProjectShare({
|
|
843
|
-
project_id:
|
|
844
|
-
organization_id:
|
|
668
|
+
project_id: VALID_UUID,
|
|
669
|
+
organization_id: OTHER_UUID,
|
|
845
670
|
permission: 'write',
|
|
846
671
|
}, ctx);
|
|
847
672
|
|
|
848
|
-
expect(result.result).toMatchObject({
|
|
849
|
-
success: true,
|
|
850
|
-
});
|
|
673
|
+
expect(result.result).toMatchObject({ success: true });
|
|
851
674
|
});
|
|
852
675
|
|
|
853
676
|
it('should throw error when update fails', async () => {
|
|
854
|
-
|
|
855
|
-
|
|
677
|
+
mockApiClient.updateProjectShare.mockResolvedValue({
|
|
678
|
+
ok: false,
|
|
679
|
+
error: 'Failed to update share',
|
|
856
680
|
});
|
|
857
|
-
const ctx = createMockContext(
|
|
681
|
+
const ctx = createMockContext();
|
|
858
682
|
|
|
859
683
|
await expect(
|
|
860
684
|
updateProjectShare({
|
|
861
|
-
project_id:
|
|
862
|
-
organization_id:
|
|
685
|
+
project_id: VALID_UUID,
|
|
686
|
+
organization_id: OTHER_UUID,
|
|
863
687
|
permission: 'write',
|
|
864
688
|
}, ctx)
|
|
865
|
-
).rejects.toThrow('Failed to update share
|
|
689
|
+
).rejects.toThrow('Failed to update share');
|
|
866
690
|
});
|
|
867
691
|
});
|
|
868
692
|
|
|
@@ -874,32 +698,32 @@ describe('unshareProject', () => {
|
|
|
874
698
|
beforeEach(() => vi.clearAllMocks());
|
|
875
699
|
|
|
876
700
|
it('should throw error for missing project_id', async () => {
|
|
877
|
-
const
|
|
878
|
-
const ctx = createMockContext(supabase);
|
|
879
|
-
|
|
701
|
+
const ctx = createMockContext();
|
|
880
702
|
await expect(
|
|
881
|
-
unshareProject({ organization_id:
|
|
703
|
+
unshareProject({ organization_id: VALID_UUID }, ctx)
|
|
882
704
|
).rejects.toThrow(ValidationError);
|
|
883
705
|
});
|
|
884
706
|
|
|
885
707
|
it('should throw error for missing organization_id', async () => {
|
|
886
|
-
const
|
|
887
|
-
const ctx = createMockContext(supabase);
|
|
888
|
-
|
|
708
|
+
const ctx = createMockContext();
|
|
889
709
|
await expect(
|
|
890
|
-
unshareProject({ project_id:
|
|
710
|
+
unshareProject({ project_id: VALID_UUID }, ctx)
|
|
891
711
|
).rejects.toThrow(ValidationError);
|
|
892
712
|
});
|
|
893
713
|
|
|
894
714
|
it('should unshare project successfully', async () => {
|
|
895
|
-
|
|
896
|
-
|
|
715
|
+
mockApiClient.unshareProject.mockResolvedValue({
|
|
716
|
+
ok: true,
|
|
717
|
+
data: {
|
|
718
|
+
success: true,
|
|
719
|
+
message: 'Project share removed. Org members can no longer access this project.',
|
|
720
|
+
},
|
|
897
721
|
});
|
|
898
|
-
const ctx = createMockContext(
|
|
722
|
+
const ctx = createMockContext();
|
|
899
723
|
|
|
900
724
|
const result = await unshareProject({
|
|
901
|
-
project_id:
|
|
902
|
-
organization_id:
|
|
725
|
+
project_id: VALID_UUID,
|
|
726
|
+
organization_id: OTHER_UUID,
|
|
903
727
|
}, ctx);
|
|
904
728
|
|
|
905
729
|
expect(result.result).toMatchObject({
|
|
@@ -909,17 +733,15 @@ describe('unshareProject', () => {
|
|
|
909
733
|
});
|
|
910
734
|
|
|
911
735
|
it('should throw error when delete fails', async () => {
|
|
912
|
-
|
|
913
|
-
|
|
736
|
+
mockApiClient.unshareProject.mockResolvedValue({
|
|
737
|
+
ok: false,
|
|
738
|
+
error: 'Failed to unshare project',
|
|
914
739
|
});
|
|
915
|
-
const ctx = createMockContext(
|
|
740
|
+
const ctx = createMockContext();
|
|
916
741
|
|
|
917
742
|
await expect(
|
|
918
|
-
unshareProject({
|
|
919
|
-
|
|
920
|
-
organization_id: testUUID(),
|
|
921
|
-
}, ctx)
|
|
922
|
-
).rejects.toThrow('Failed to unshare project: Delete failed');
|
|
743
|
+
unshareProject({ project_id: VALID_UUID, organization_id: OTHER_UUID }, ctx)
|
|
744
|
+
).rejects.toThrow('Failed to unshare project');
|
|
923
745
|
});
|
|
924
746
|
});
|
|
925
747
|
|
|
@@ -931,67 +753,53 @@ describe('listProjectShares', () => {
|
|
|
931
753
|
beforeEach(() => vi.clearAllMocks());
|
|
932
754
|
|
|
933
755
|
it('should throw error for missing project_id', async () => {
|
|
934
|
-
const
|
|
935
|
-
const ctx = createMockContext(supabase);
|
|
936
|
-
|
|
756
|
+
const ctx = createMockContext();
|
|
937
757
|
await expect(listProjectShares({}, ctx)).rejects.toThrow(ValidationError);
|
|
938
758
|
});
|
|
939
759
|
|
|
940
760
|
it('should throw error for invalid project_id UUID', async () => {
|
|
941
|
-
const
|
|
942
|
-
const ctx = createMockContext(supabase);
|
|
943
|
-
|
|
761
|
+
const ctx = createMockContext();
|
|
944
762
|
await expect(
|
|
945
763
|
listProjectShares({ project_id: 'invalid' }, ctx)
|
|
946
764
|
).rejects.toThrow(ValidationError);
|
|
947
765
|
});
|
|
948
766
|
|
|
949
767
|
it('should return empty array when no shares', async () => {
|
|
950
|
-
|
|
951
|
-
|
|
768
|
+
mockApiClient.listProjectShares.mockResolvedValue({
|
|
769
|
+
ok: true,
|
|
770
|
+
data: { shares: [], count: 0 },
|
|
952
771
|
});
|
|
953
|
-
const ctx = createMockContext(
|
|
772
|
+
const ctx = createMockContext();
|
|
954
773
|
|
|
955
|
-
const result = await listProjectShares({ project_id:
|
|
774
|
+
const result = await listProjectShares({ project_id: VALID_UUID }, ctx);
|
|
956
775
|
|
|
957
|
-
expect(result.result).toMatchObject({
|
|
958
|
-
shares: [],
|
|
959
|
-
count: 0,
|
|
960
|
-
});
|
|
776
|
+
expect(result.result).toMatchObject({ shares: [], count: 0 });
|
|
961
777
|
});
|
|
962
778
|
|
|
963
779
|
it('should return shares list', async () => {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
permission: 'read',
|
|
970
|
-
shared_at: '2025-01-14T12:00:00Z',
|
|
971
|
-
shared_by: 'user-123',
|
|
972
|
-
organizations: { id: 'org-1', name: 'Test Org', slug: 'test-org' },
|
|
973
|
-
},
|
|
974
|
-
],
|
|
975
|
-
error: null,
|
|
780
|
+
mockApiClient.listProjectShares.mockResolvedValue({
|
|
781
|
+
ok: true,
|
|
782
|
+
data: {
|
|
783
|
+
shares: [{ id: 'share-1', permission: 'read', organization: { name: 'Test Org' } }],
|
|
784
|
+
count: 1,
|
|
976
785
|
},
|
|
977
786
|
});
|
|
978
|
-
const ctx = createMockContext(
|
|
787
|
+
const ctx = createMockContext();
|
|
979
788
|
|
|
980
|
-
const result = await listProjectShares({ project_id:
|
|
789
|
+
const result = await listProjectShares({ project_id: VALID_UUID }, ctx);
|
|
981
790
|
|
|
982
|
-
expect(result.result).toMatchObject({
|
|
983
|
-
count: 1,
|
|
984
|
-
});
|
|
791
|
+
expect(result.result).toMatchObject({ count: 1 });
|
|
985
792
|
});
|
|
986
793
|
|
|
987
794
|
it('should throw error when query fails', async () => {
|
|
988
|
-
|
|
989
|
-
|
|
795
|
+
mockApiClient.listProjectShares.mockResolvedValue({
|
|
796
|
+
ok: false,
|
|
797
|
+
error: 'Failed to list shares',
|
|
990
798
|
});
|
|
991
|
-
const ctx = createMockContext(
|
|
799
|
+
const ctx = createMockContext();
|
|
992
800
|
|
|
993
801
|
await expect(
|
|
994
|
-
listProjectShares({ project_id:
|
|
995
|
-
).rejects.toThrow('Failed to list shares
|
|
802
|
+
listProjectShares({ project_id: VALID_UUID }, ctx)
|
|
803
|
+
).rejects.toThrow('Failed to list shares');
|
|
996
804
|
});
|
|
997
805
|
});
|