@jskit-ai/workspaces-core 0.1.30 → 0.1.32
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/package.descriptor.mjs +11 -22
- package/package.json +11 -9
- package/src/server/WorkspacesCoreServiceProvider.js +22 -2
- package/src/server/common/repositories/workspaceInvitesRepository.js +233 -78
- package/src/server/common/repositories/workspaceMembershipsRepository.js +177 -86
- package/src/server/common/repositories/workspacesRepository.js +179 -86
- package/src/server/common/services/workspaceContextService.js +26 -24
- package/src/server/common/validators/routeParamsValidator.js +36 -53
- package/src/server/registerWorkspaceCore.js +6 -7
- package/src/server/registerWorkspaceRepositories.js +7 -3
- package/src/server/support/workspaceServerScopeSupport.js +1 -1
- package/src/server/workspaceBootstrapContributor.js +5 -14
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +54 -27
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +30 -24
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +70 -32
- package/src/server/workspaceMembers/workspaceMembersActions.js +61 -27
- package/src/server/workspaceMembers/workspaceMembersService.js +43 -7
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +28 -13
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +13 -15
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +33 -10
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +32 -13
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +5 -1
- package/src/server/workspaceSettings/workspaceSettingsActions.js +18 -12
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +104 -91
- package/src/server/workspaceSettings/workspaceSettingsService.js +5 -6
- package/src/shared/jsonApiTransports.js +79 -0
- package/src/shared/resources/workspaceInvitesResource.js +158 -0
- package/src/shared/resources/workspaceMembersResource.js +176 -311
- package/src/shared/resources/workspaceMembershipsResource.js +96 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +25 -72
- package/src/shared/resources/workspaceResource.js +113 -144
- package/src/shared/resources/workspaceRoleCatalogSchema.js +31 -0
- package/src/shared/resources/workspaceSettingsResource.js +276 -148
- package/test/repositoryContracts.test.js +16 -4
- package/test/resourcesCanonical.test.js +39 -16
- package/test/routeParamsValidator.test.js +37 -19
- package/test/usersRouteResources.test.js +27 -17
- package/test/workspaceActionContextContributor.test.js +1 -1
- package/test/workspaceInternalCrudResources.test.js +98 -0
- package/test/workspaceInvitesRepository.test.js +196 -148
- package/test/workspaceMembersResource.test.js +35 -0
- package/test/workspaceMembershipsRepository.test.js +155 -115
- package/test/workspacePendingInvitationsResource.test.js +18 -23
- package/test/workspacePendingInvitationsService.test.js +2 -1
- package/test/workspaceServerScopeSupport.test.js +21 -3
- package/test/workspaceSettingsActions.test.js +5 -7
- package/test/workspaceSettingsInternalResource.test.js +8 -0
- package/test/workspaceSettingsRepository.test.js +158 -123
- package/test/workspaceSettingsResource.test.js +51 -62
- package/test/workspaceSettingsService.test.js +0 -1
- package/test/workspacesRepository.test.js +318 -174
- package/test/workspacesRouteRequestInputValidator.test.js +25 -11
- package/src/server/common/resources/workspaceInvitesResource.js +0 -207
- package/src/server/common/resources/workspaceMembershipsResource.js +0 -154
- package/src/server/common/resources/workspacesResource.js +0 -170
- package/src/server/common/validators/authenticatedUserValidator.js +0 -43
- package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
- package/src/shared/resources/workspaceSettingsFields.js +0 -65
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
- package/test/settingsFieldRegistriesSingleton.test.js +0 -14
- package/test-support/registerDefaultSettingsFields.js +0 -1
|
@@ -3,108 +3,146 @@ import test from "node:test";
|
|
|
3
3
|
import { toIsoString } from "@jskit-ai/database-runtime/shared";
|
|
4
4
|
import { createRepository } from "../src/server/common/repositories/workspaceMembershipsRepository.js";
|
|
5
5
|
|
|
6
|
-
function createKnexStub({
|
|
7
|
-
|
|
6
|
+
function createKnexStub() {
|
|
7
|
+
const knex = Object.assign(() => {
|
|
8
|
+
throw new Error("query execution not expected");
|
|
9
|
+
}, {
|
|
10
|
+
async transaction(work) {
|
|
11
|
+
return work({ trxId: "trx-1" });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return knex;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toWorkspaceMembershipResource(row = {}) {
|
|
19
|
+
return {
|
|
20
|
+
type: "workspaceMemberships",
|
|
21
|
+
id: String(row.id || ""),
|
|
22
|
+
attributes: {
|
|
23
|
+
roleSid: row.roleSid,
|
|
24
|
+
status: row.status,
|
|
25
|
+
createdAt: row.createdAt,
|
|
26
|
+
updatedAt: row.updatedAt
|
|
27
|
+
},
|
|
28
|
+
relationships: {
|
|
29
|
+
workspace: {
|
|
30
|
+
data: row?.workspace?.id == null ? null : { type: "workspaces", id: String(row.workspace.id) }
|
|
31
|
+
},
|
|
32
|
+
user: {
|
|
33
|
+
data: row?.user?.id == null
|
|
34
|
+
? null
|
|
35
|
+
: {
|
|
36
|
+
type: "userProfiles",
|
|
37
|
+
id: String(row.user.id)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createWorkspaceMembershipsApiStub({
|
|
8
45
|
rowByComposite = new Map(),
|
|
9
|
-
memberSummaryRows = []
|
|
46
|
+
memberSummaryRows = [],
|
|
47
|
+
rowById = new Map()
|
|
10
48
|
} = {}) {
|
|
11
49
|
const state = {
|
|
12
|
-
|
|
13
|
-
|
|
50
|
+
postPayload: null,
|
|
51
|
+
patchPayload: null
|
|
14
52
|
};
|
|
15
53
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
const api = {
|
|
55
|
+
resources: {
|
|
56
|
+
workspaceMemberships: {
|
|
57
|
+
async query({ queryParams }) {
|
|
58
|
+
const filters = queryParams?.filters || {};
|
|
59
|
+
const includeUser = Array.isArray(queryParams?.include) && queryParams.include.includes("user");
|
|
60
|
+
|
|
61
|
+
if (Object.hasOwn(filters, "workspace") && Object.hasOwn(filters, "user")) {
|
|
62
|
+
const row = rowByComposite.get(`${filters.workspace}:${filters.user}`) || null;
|
|
63
|
+
return { data: row ? [toWorkspaceMembershipResource(row)] : [] };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (Object.hasOwn(filters, "workspace") && Object.hasOwn(filters, "status") && includeUser) {
|
|
67
|
+
return {
|
|
68
|
+
data: memberSummaryRows.map((row) => toWorkspaceMembershipResource(row)),
|
|
69
|
+
included: memberSummaryRows
|
|
70
|
+
.filter((row) => row?.user?.id != null)
|
|
71
|
+
.map((row) => ({
|
|
72
|
+
type: "userProfiles",
|
|
73
|
+
id: String(row.user.id),
|
|
74
|
+
attributes: {
|
|
75
|
+
displayName: row.user.displayName,
|
|
76
|
+
email: row.user.email
|
|
77
|
+
}
|
|
78
|
+
}))
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (Object.hasOwn(filters, "user") && Object.hasOwn(filters, "status")) {
|
|
83
|
+
const rows = [...rowByComposite.values()].filter((row) => (
|
|
84
|
+
String(row?.user?.id || "") === String(filters.user) &&
|
|
85
|
+
String(row?.status || "") === String(filters.status)
|
|
86
|
+
));
|
|
87
|
+
return { data: rows.map((row) => toWorkspaceMembershipResource(row)) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { data: [] };
|
|
24
91
|
},
|
|
25
|
-
|
|
26
|
-
|
|
92
|
+
async post(payload) {
|
|
93
|
+
assert.equal(payload?.simplified, false);
|
|
94
|
+
const inputRecord = payload?.inputRecord?.data || {};
|
|
95
|
+
state.postPayload = inputRecord;
|
|
96
|
+
const row = rowById.get("1") || {
|
|
97
|
+
id: "1",
|
|
98
|
+
workspace: { id: String(inputRecord.relationships?.workspace?.data?.id || "") },
|
|
99
|
+
user: { id: String(inputRecord.relationships?.user?.data?.id || "") },
|
|
100
|
+
roleSid: String(inputRecord.attributes?.roleSid || ""),
|
|
101
|
+
status: String(inputRecord.attributes?.status || ""),
|
|
102
|
+
createdAt: "2026-03-09 00:26:35.710",
|
|
103
|
+
updatedAt: "2026-03-09 00:26:35.710"
|
|
104
|
+
};
|
|
105
|
+
rowByComposite.set(`${row.workspace.id}:${row.user.id}`, row);
|
|
106
|
+
rowById.set(String(row.id), row);
|
|
107
|
+
return { data: toWorkspaceMembershipResource(row) };
|
|
27
108
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
insert(payload) {
|
|
40
|
-
state.insertPayload = payload;
|
|
41
|
-
const insertedRow = rowById.get("1");
|
|
42
|
-
if (insertedRow) {
|
|
43
|
-
rowByComposite.set(`${payload.workspace_id}:${payload.user_id}`, insertedRow);
|
|
44
|
-
}
|
|
45
|
-
return Promise.resolve([1]);
|
|
46
|
-
},
|
|
47
|
-
where(criteria) {
|
|
48
|
-
this.criteriaList.push(criteria);
|
|
49
|
-
return this;
|
|
50
|
-
},
|
|
51
|
-
update(payload) {
|
|
52
|
-
state.updatePayload = payload;
|
|
53
|
-
const criteria = Object.assign({}, ...this.criteriaList);
|
|
54
|
-
const existingRow = rowById.get(String(criteria.id));
|
|
55
|
-
if (existingRow) {
|
|
56
|
-
const updatedRow = {
|
|
57
|
-
...existingRow,
|
|
58
|
-
...payload
|
|
109
|
+
async patch(payload) {
|
|
110
|
+
assert.equal(payload?.simplified, false);
|
|
111
|
+
const inputRecord = payload?.inputRecord?.data || {};
|
|
112
|
+
state.patchPayload = inputRecord;
|
|
113
|
+
const existing = rowById.get(String(inputRecord.id));
|
|
114
|
+
const updated = {
|
|
115
|
+
...(existing || {}),
|
|
116
|
+
...(inputRecord.attributes || {}),
|
|
117
|
+
id: String(inputRecord.id),
|
|
118
|
+
workspace: existing?.workspace || { id: "" },
|
|
119
|
+
user: existing?.user || { id: "" }
|
|
59
120
|
};
|
|
60
|
-
rowById.set(String(
|
|
61
|
-
rowByComposite.set(`${
|
|
121
|
+
rowById.set(String(updated.id), updated);
|
|
122
|
+
rowByComposite.set(`${updated.workspace.id}:${updated.user.id}`, updated);
|
|
123
|
+
return { data: toWorkspaceMembershipResource(updated) };
|
|
62
124
|
}
|
|
63
|
-
return Promise.resolve(1);
|
|
64
|
-
},
|
|
65
|
-
first() {
|
|
66
|
-
const criteria = Object.assign({}, ...this.criteriaList);
|
|
67
|
-
if (Object.hasOwn(criteria, "id")) {
|
|
68
|
-
return Promise.resolve(rowById.get(String(criteria.id)) || null);
|
|
69
|
-
}
|
|
70
|
-
if (Object.hasOwn(criteria, "workspace_id") && Object.hasOwn(criteria, "user_id")) {
|
|
71
|
-
return Promise.resolve(
|
|
72
|
-
rowByComposite.get(`${criteria.workspace_id}:${criteria.user_id}`) || null
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
return Promise.resolve(null);
|
|
76
125
|
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return query;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function knex(tableName) {
|
|
83
|
-
if (tableName === "workspace_memberships" || tableName === "workspace_memberships as wm") {
|
|
84
|
-
return buildMembershipsQuery(tableName);
|
|
85
126
|
}
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
knex.transaction = async (work) => work(knex);
|
|
127
|
+
};
|
|
90
128
|
|
|
91
|
-
return {
|
|
129
|
+
return { api, state };
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
test("workspaceMembershipsRepository.findByWorkspaceIdAndUserId normalizes canonical membership rows via the internal resource", async () => {
|
|
95
133
|
const membershipRow = {
|
|
96
|
-
id: 11,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
134
|
+
id: "11",
|
|
135
|
+
workspace: { id: "7" },
|
|
136
|
+
user: { id: "9" },
|
|
137
|
+
roleSid: "owner",
|
|
100
138
|
status: "active",
|
|
101
|
-
|
|
102
|
-
|
|
139
|
+
createdAt: "2026-03-09 00:26:35.710",
|
|
140
|
+
updatedAt: "2026-03-10 00:26:35.710"
|
|
103
141
|
};
|
|
104
|
-
const {
|
|
142
|
+
const { api } = createWorkspaceMembershipsApiStub({
|
|
105
143
|
rowByComposite: new Map([["7:9", membershipRow]])
|
|
106
144
|
});
|
|
107
|
-
const repository = createRepository(knex);
|
|
145
|
+
const repository = createRepository({ api, knex: createKnexStub() });
|
|
108
146
|
|
|
109
147
|
const membership = await repository.findByWorkspaceIdAndUserId("7", "9");
|
|
110
148
|
|
|
@@ -121,31 +159,31 @@ test("workspaceMembershipsRepository.findByWorkspaceIdAndUserId normalizes canon
|
|
|
121
159
|
|
|
122
160
|
test("workspaceMembershipsRepository.ensureOwnerMembership upgrades an existing membership through the runtime update path", async () => {
|
|
123
161
|
const existingRow = {
|
|
124
|
-
id: 11,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
162
|
+
id: "11",
|
|
163
|
+
workspace: { id: "7" },
|
|
164
|
+
user: { id: "9" },
|
|
165
|
+
roleSid: "member",
|
|
128
166
|
status: "pending",
|
|
129
|
-
|
|
130
|
-
|
|
167
|
+
createdAt: "2026-03-09 00:26:35.710",
|
|
168
|
+
updatedAt: "2026-03-09 00:26:35.710"
|
|
131
169
|
};
|
|
132
170
|
const refreshedRow = {
|
|
133
171
|
...existingRow,
|
|
134
|
-
|
|
172
|
+
roleSid: "owner",
|
|
135
173
|
status: "active",
|
|
136
|
-
|
|
174
|
+
updatedAt: "2026-03-10 00:26:35.710"
|
|
137
175
|
};
|
|
138
|
-
const {
|
|
176
|
+
const { api, state } = createWorkspaceMembershipsApiStub({
|
|
139
177
|
rowById: new Map([["11", refreshedRow]]),
|
|
140
178
|
rowByComposite: new Map([["7:9", existingRow]])
|
|
141
179
|
});
|
|
142
|
-
const repository = createRepository(knex);
|
|
180
|
+
const repository = createRepository({ api, knex: createKnexStub() });
|
|
143
181
|
|
|
144
182
|
const membership = await repository.ensureOwnerMembership("7", "9");
|
|
145
183
|
|
|
146
|
-
assert.equal(state.
|
|
147
|
-
assert.equal(state.
|
|
148
|
-
assert.equal(typeof state.
|
|
184
|
+
assert.equal(state.patchPayload.attributes?.roleSid, "owner");
|
|
185
|
+
assert.equal(state.patchPayload.attributes?.status, "active");
|
|
186
|
+
assert.equal(typeof state.patchPayload.attributes?.updatedAt, "object");
|
|
149
187
|
assert.deepEqual(membership, {
|
|
150
188
|
id: "11",
|
|
151
189
|
workspaceId: "7",
|
|
@@ -153,50 +191,52 @@ test("workspaceMembershipsRepository.ensureOwnerMembership upgrades an existing
|
|
|
153
191
|
roleSid: "owner",
|
|
154
192
|
status: "active",
|
|
155
193
|
createdAt: toIsoString("2026-03-09 00:26:35.710"),
|
|
156
|
-
updatedAt: toIsoString(state.
|
|
194
|
+
updatedAt: toIsoString(state.patchPayload.attributes.updatedAt)
|
|
157
195
|
});
|
|
158
196
|
});
|
|
159
197
|
|
|
160
198
|
test("workspaceMembershipsRepository.upsertMembership creates normalized memberships through the runtime create path", async () => {
|
|
161
199
|
const createdRow = {
|
|
162
|
-
id: 1,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
200
|
+
id: "1",
|
|
201
|
+
workspace: { id: "7" },
|
|
202
|
+
user: { id: "9" },
|
|
203
|
+
roleSid: "admin",
|
|
166
204
|
status: "active",
|
|
167
|
-
|
|
168
|
-
|
|
205
|
+
createdAt: "2026-03-09 00:26:35.710",
|
|
206
|
+
updatedAt: "2026-03-09 00:26:35.710"
|
|
169
207
|
};
|
|
170
|
-
const {
|
|
208
|
+
const { api, state } = createWorkspaceMembershipsApiStub({
|
|
171
209
|
rowById: new Map([["1", createdRow]]),
|
|
172
210
|
rowByComposite: new Map()
|
|
173
211
|
});
|
|
174
|
-
const repository = createRepository(knex);
|
|
212
|
+
const repository = createRepository({ api, knex: createKnexStub() });
|
|
175
213
|
|
|
176
214
|
await repository.upsertMembership("7", "9", {
|
|
177
215
|
roleSid: "ADMIN",
|
|
178
216
|
status: "ACTIVE"
|
|
179
217
|
});
|
|
180
218
|
|
|
181
|
-
assert.equal(state.
|
|
182
|
-
assert.equal(state.
|
|
183
|
-
assert.equal(state.
|
|
184
|
-
assert.equal(state.
|
|
219
|
+
assert.equal(state.postPayload.relationships?.workspace?.data?.id, "7");
|
|
220
|
+
assert.equal(state.postPayload.relationships?.user?.data?.id, "9");
|
|
221
|
+
assert.equal(state.postPayload.attributes?.roleSid, "admin");
|
|
222
|
+
assert.equal(state.postPayload.attributes?.status, "active");
|
|
185
223
|
});
|
|
186
224
|
|
|
187
225
|
test("workspaceMembershipsRepository.listActiveByWorkspaceId keeps summary rows separate from the canonical membership resource", async () => {
|
|
188
|
-
const {
|
|
226
|
+
const { api } = createWorkspaceMembershipsApiStub({
|
|
189
227
|
memberSummaryRows: [
|
|
190
228
|
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
229
|
+
user: {
|
|
230
|
+
id: "9",
|
|
231
|
+
displayName: "Chiara",
|
|
232
|
+
email: "CHIARA@example.com"
|
|
233
|
+
},
|
|
234
|
+
roleSid: "owner",
|
|
235
|
+
status: "active"
|
|
196
236
|
}
|
|
197
237
|
]
|
|
198
238
|
});
|
|
199
|
-
const repository = createRepository(knex);
|
|
239
|
+
const repository = createRepository({ api, knex: createKnexStub() });
|
|
200
240
|
|
|
201
241
|
const members = await repository.listActiveByWorkspaceId("7");
|
|
202
242
|
|
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveStructuredSchemaTransportSchema } from "@jskit-ai/kernel/shared/validators";
|
|
4
4
|
import { workspacePendingInvitationsResource } from "../src/shared/resources/workspacePendingInvitationsResource.js";
|
|
5
5
|
|
|
6
|
-
test("workspacePendingInvitationsResource output
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
test("workspacePendingInvitationsResource output schema accepts already-shaped invite payloads", () => {
|
|
7
|
+
const outputSchema = resolveStructuredSchemaTransportSchema(workspacePendingInvitationsResource.operations.list.output, {
|
|
8
|
+
context: "workspacePendingInvitations.list.output",
|
|
9
|
+
defaultMode: "replace"
|
|
10
|
+
});
|
|
11
|
+
const result = {
|
|
10
12
|
pendingInvites: [
|
|
11
13
|
{
|
|
12
14
|
id: "10",
|
|
13
15
|
workspaceId: "3",
|
|
14
16
|
workspaceSlug: "tonymobily3",
|
|
15
|
-
workspaceName: "",
|
|
17
|
+
workspaceName: "TonyMobily3",
|
|
16
18
|
workspaceAvatarUrl: "",
|
|
17
|
-
roleSid: "
|
|
18
|
-
status: "
|
|
19
|
+
roleSid: "member",
|
|
20
|
+
status: "pending",
|
|
19
21
|
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
20
|
-
|
|
22
|
+
token: "opaque-token"
|
|
21
23
|
}
|
|
22
24
|
]
|
|
23
|
-
}
|
|
25
|
+
};
|
|
24
26
|
|
|
25
|
-
assert.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
workspaceAvatarUrl: "",
|
|
32
|
-
roleSid: "member",
|
|
33
|
-
status: "pending",
|
|
34
|
-
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
35
|
-
token: encodeInviteTokenHash(tokenHash)
|
|
36
|
-
}
|
|
37
|
-
]);
|
|
27
|
+
assert.equal(outputSchema.type, "object");
|
|
28
|
+
assert.equal(outputSchema.additionalProperties, false);
|
|
29
|
+
assert.equal(outputSchema.properties.pendingInvites.type, "array");
|
|
30
|
+
assert.equal(outputSchema.properties.pendingInvites.items["x-json-rest-schema"]?.castType, "object");
|
|
31
|
+
assert.equal(Array.isArray(outputSchema.properties.pendingInvites.items.allOf), true);
|
|
32
|
+
assert.equal(result.pendingInvites[0].roleSid, "member");
|
|
38
33
|
});
|
|
@@ -78,8 +78,9 @@ test("listPendingInvitesForUser returns raw pending invite rows for the action l
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
assert.equal(pendingInvites.length, 1);
|
|
81
|
-
assert.equal(pendingInvites[0].
|
|
81
|
+
assert.equal(pendingInvites[0].token, encodeInviteTokenHash(tokenHash));
|
|
82
82
|
assert.equal(pendingInvites[0].workspaceName, "TonyMobily3");
|
|
83
|
+
assert.equal(pendingInvites[0].status, "pending");
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
test("acceptInviteByToken accepts opaque invite token and resolves invite by decoded hash", async () => {
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import { AUTH_POLICY_CONTEXT_RESOLVER_TAG } from "@jskit-ai/auth-core/server/authPolicyContextResolverRegistry";
|
|
4
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
5
|
import { createWorkspaceServerScopeSupport } from "../src/server/support/workspaceServerScopeSupport.js";
|
|
4
6
|
import { registerWorkspaceCore } from "../src/server/registerWorkspaceCore.js";
|
|
5
7
|
|
|
6
|
-
test("workspace server scope support exposes the canonical workspace helper surface", () => {
|
|
8
|
+
test("workspace server scope support exposes the canonical workspace helper surface", async () => {
|
|
7
9
|
const support = createWorkspaceServerScopeSupport();
|
|
8
10
|
|
|
9
11
|
assert.equal(support.available, true);
|
|
10
|
-
assert.equal(typeof support.
|
|
12
|
+
assert.equal(typeof support.params?.schema, "object");
|
|
13
|
+
assert.equal(support.params?.mode, "patch");
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
await validateSchemaPayload(support.params, { workspaceSlug: " ACME " }, {
|
|
16
|
+
phase: "input",
|
|
17
|
+
context: "workspaceServerScopeSupport.params"
|
|
18
|
+
}),
|
|
19
|
+
{
|
|
20
|
+
workspaceSlug: "acme"
|
|
21
|
+
}
|
|
22
|
+
);
|
|
11
23
|
assert.deepEqual(support.buildInputFromRouteParams({ workspaceSlug: " ACME " }), {
|
|
12
24
|
workspaceSlug: "acme"
|
|
13
25
|
});
|
|
@@ -34,12 +46,17 @@ test("workspace server scope support exposes the canonical workspace helper surf
|
|
|
34
46
|
|
|
35
47
|
test("registerWorkspaceCore registers the workspace server scope support token", () => {
|
|
36
48
|
const singletons = new Map();
|
|
49
|
+
const tags = new Map();
|
|
37
50
|
const app = {
|
|
38
51
|
singleton(token, factory) {
|
|
39
52
|
singletons.set(token, factory);
|
|
40
53
|
return this;
|
|
41
54
|
},
|
|
42
|
-
tag() {
|
|
55
|
+
tag(token, tagName) {
|
|
56
|
+
const key = String(tagName || "");
|
|
57
|
+
const list = tags.get(key) || [];
|
|
58
|
+
list.push(String(token || ""));
|
|
59
|
+
tags.set(key, list);
|
|
43
60
|
return this;
|
|
44
61
|
},
|
|
45
62
|
has() {
|
|
@@ -50,6 +67,7 @@ test("registerWorkspaceCore registers the workspace server scope support token",
|
|
|
50
67
|
registerWorkspaceCore(app);
|
|
51
68
|
|
|
52
69
|
assert.equal(singletons.has("workspaces.server.scope-support"), true);
|
|
70
|
+
assert.deepEqual(tags.get(AUTH_POLICY_CONTEXT_RESOLVER_TAG), ["workspaces.core.authPolicyContextResolver"]);
|
|
53
71
|
const support = singletons.get("workspaces.server.scope-support")();
|
|
54
72
|
assert.equal(support.available, true);
|
|
55
73
|
assert.equal(typeof support.buildInputFromRouteParams, "function");
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
3
|
import { workspaceDirectoryActions } from "../src/server/workspaceDirectory/workspaceDirectoryActions.js";
|
|
5
4
|
import { workspacePendingInvitationsActions } from "../src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js";
|
|
6
5
|
import { workspaceMembersActions } from "../src/server/workspaceMembers/workspaceMembersActions.js";
|
|
7
6
|
import { workspaceSettingsActions } from "../src/server/workspaceSettings/workspaceSettingsActions.js";
|
|
8
|
-
import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
|
|
9
7
|
|
|
10
8
|
test("workspace settings actions live in their own action array", () => {
|
|
11
9
|
assert.deepEqual(
|
|
@@ -35,18 +33,18 @@ test("workspace actions array no longer owns workspace settings actions", () =>
|
|
|
35
33
|
);
|
|
36
34
|
});
|
|
37
35
|
|
|
38
|
-
test("workspace directory actions
|
|
36
|
+
test("workspace directory actions stay thin and defer output validation to routes", () => {
|
|
39
37
|
const listAction = workspaceDirectoryActions.find((action) => action.id === "workspace.workspaces.list");
|
|
40
38
|
assert.ok(listAction);
|
|
41
|
-
assert.equal(listAction.
|
|
39
|
+
assert.equal(listAction.output, null);
|
|
42
40
|
});
|
|
43
41
|
|
|
44
|
-
test("workspace directory read/update actions
|
|
42
|
+
test("workspace directory read/update actions stay thin and defer output validation to routes", () => {
|
|
45
43
|
const readAction = workspaceDirectoryActions.find((action) => action.id === "workspace.workspaces.read");
|
|
46
44
|
const updateAction = workspaceDirectoryActions.find((action) => action.id === "workspace.workspaces.update");
|
|
47
45
|
|
|
48
46
|
assert.ok(readAction);
|
|
49
47
|
assert.ok(updateAction);
|
|
50
|
-
assert.equal(readAction.
|
|
51
|
-
assert.equal(updateAction.
|
|
48
|
+
assert.equal(readAction.output, null);
|
|
49
|
+
assert.equal(updateAction.output, null);
|
|
52
50
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
4
|
+
|
|
5
|
+
test("shared workspace settings resource declares workspace_id as the resource id column", () => {
|
|
6
|
+
assert.equal(workspaceSettingsResource.idProperty, "workspace_id");
|
|
7
|
+
assert.equal(workspaceSettingsResource.schema.id?.storage?.column, "workspace_id");
|
|
8
|
+
});
|