@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
|
@@ -1,119 +1,154 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
3
|
import { toIsoString } from "@jskit-ai/database-runtime/shared";
|
|
5
4
|
import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
|
|
6
5
|
import { createRepository } from "../src/server/workspaceSettings/workspaceSettingsRepository.js";
|
|
7
6
|
|
|
8
|
-
function
|
|
9
|
-
|
|
7
|
+
function createKnexStub() {
|
|
8
|
+
const knex = Object.assign(() => {
|
|
9
|
+
throw new Error("query execution not expected");
|
|
10
|
+
}, {
|
|
11
|
+
async transaction(work) {
|
|
12
|
+
return work({ trxId: "trx-1" });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return knex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeWorkspaceColor(value) {
|
|
20
|
+
return typeof value === "string" ? value.toUpperCase() : value;
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
const
|
|
23
|
+
function createWorkspaceSettingsApiStub(rowOverrides = {}) {
|
|
24
|
+
const DEFAULT_WORKSPACE_THEME = resolveWorkspaceThemePalettes({});
|
|
25
|
+
const STUB_CREATED_AT = "2026-03-09 00:26:35.710";
|
|
26
|
+
const STUB_CREATED_AT_ISO = toIsoString(STUB_CREATED_AT);
|
|
14
27
|
|
|
15
|
-
function createKnexStub(rowOverrides = {}) {
|
|
16
28
|
const state = {
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
postPayload: null,
|
|
30
|
+
patchPayload: null,
|
|
19
31
|
row: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
id: "1",
|
|
33
|
+
lightPrimaryColor: DEFAULT_WORKSPACE_THEME.light.color,
|
|
34
|
+
lightSecondaryColor: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
|
|
35
|
+
lightSurfaceColor: DEFAULT_WORKSPACE_THEME.light.surfaceColor,
|
|
36
|
+
lightSurfaceVariantColor: DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor,
|
|
37
|
+
darkPrimaryColor: DEFAULT_WORKSPACE_THEME.dark.color,
|
|
38
|
+
darkSecondaryColor: DEFAULT_WORKSPACE_THEME.dark.secondaryColor,
|
|
39
|
+
darkSurfaceColor: DEFAULT_WORKSPACE_THEME.dark.surfaceColor,
|
|
40
|
+
darkSurfaceVariantColor: DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor,
|
|
41
|
+
invitesEnabled: true,
|
|
42
|
+
createdAt: STUB_CREATED_AT_ISO,
|
|
43
|
+
updatedAt: STUB_CREATED_AT_ISO,
|
|
32
44
|
...rowOverrides
|
|
33
45
|
}
|
|
34
46
|
};
|
|
35
47
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (Object.hasOwn(payload, "dark_surface_variant_color")) {
|
|
92
|
-
state.row.dark_surface_variant_color = payload.dark_surface_variant_color;
|
|
48
|
+
const api = {
|
|
49
|
+
resources: {
|
|
50
|
+
workspaceSettings: {
|
|
51
|
+
async query({ queryParams }) {
|
|
52
|
+
const id = String(queryParams?.filters?.id || "");
|
|
53
|
+
if (!state.row || (id && String(state.row.id) !== id)) {
|
|
54
|
+
return { data: [] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
data: [{
|
|
59
|
+
type: "workspaceSettings",
|
|
60
|
+
id: String(state.row.id),
|
|
61
|
+
attributes: {
|
|
62
|
+
lightPrimaryColor: state.row.lightPrimaryColor,
|
|
63
|
+
lightSecondaryColor: state.row.lightSecondaryColor,
|
|
64
|
+
lightSurfaceColor: state.row.lightSurfaceColor,
|
|
65
|
+
lightSurfaceVariantColor: state.row.lightSurfaceVariantColor,
|
|
66
|
+
darkPrimaryColor: state.row.darkPrimaryColor,
|
|
67
|
+
darkSecondaryColor: state.row.darkSecondaryColor,
|
|
68
|
+
darkSurfaceColor: state.row.darkSurfaceColor,
|
|
69
|
+
darkSurfaceVariantColor: state.row.darkSurfaceVariantColor,
|
|
70
|
+
invitesEnabled: state.row.invitesEnabled,
|
|
71
|
+
createdAt: state.row.createdAt,
|
|
72
|
+
updatedAt: state.row.updatedAt
|
|
73
|
+
}
|
|
74
|
+
}]
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
async post(payload) {
|
|
78
|
+
assert.equal(payload?.simplified, false);
|
|
79
|
+
const inputRecord = payload?.inputRecord?.data || {};
|
|
80
|
+
const attributes = inputRecord.attributes || {};
|
|
81
|
+
state.postPayload = inputRecord;
|
|
82
|
+
state.row = {
|
|
83
|
+
id: String(inputRecord.id),
|
|
84
|
+
lightPrimaryColor: normalizeWorkspaceColor(attributes.lightPrimaryColor ?? DEFAULT_WORKSPACE_THEME.light.color),
|
|
85
|
+
lightSecondaryColor: normalizeWorkspaceColor(attributes.lightSecondaryColor ?? DEFAULT_WORKSPACE_THEME.light.secondaryColor),
|
|
86
|
+
lightSurfaceColor: normalizeWorkspaceColor(attributes.lightSurfaceColor ?? DEFAULT_WORKSPACE_THEME.light.surfaceColor),
|
|
87
|
+
lightSurfaceVariantColor: normalizeWorkspaceColor(attributes.lightSurfaceVariantColor ?? DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor),
|
|
88
|
+
darkPrimaryColor: normalizeWorkspaceColor(attributes.darkPrimaryColor ?? DEFAULT_WORKSPACE_THEME.dark.color),
|
|
89
|
+
darkSecondaryColor: normalizeWorkspaceColor(attributes.darkSecondaryColor ?? DEFAULT_WORKSPACE_THEME.dark.secondaryColor),
|
|
90
|
+
darkSurfaceColor: normalizeWorkspaceColor(attributes.darkSurfaceColor ?? DEFAULT_WORKSPACE_THEME.dark.surfaceColor),
|
|
91
|
+
darkSurfaceVariantColor: normalizeWorkspaceColor(attributes.darkSurfaceVariantColor ?? DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor),
|
|
92
|
+
invitesEnabled: attributes.invitesEnabled ?? true,
|
|
93
|
+
createdAt: toIsoString("2026-03-10 00:00:00.000"),
|
|
94
|
+
updatedAt: toIsoString("2026-03-10 00:00:00.000")
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
data: {
|
|
98
|
+
type: "workspaceSettings",
|
|
99
|
+
id: String(state.row.id),
|
|
100
|
+
attributes: {
|
|
101
|
+
...state.row
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
async patch(payload) {
|
|
107
|
+
assert.equal(payload?.simplified, false);
|
|
108
|
+
const inputRecord = payload?.inputRecord?.data || {};
|
|
109
|
+
const attributes = inputRecord.attributes || {};
|
|
110
|
+
state.patchPayload = inputRecord;
|
|
111
|
+
state.row = {
|
|
112
|
+
...state.row,
|
|
113
|
+
...attributes,
|
|
114
|
+
...(Object.hasOwn(attributes, "lightPrimaryColor") ? { lightPrimaryColor: normalizeWorkspaceColor(attributes.lightPrimaryColor) } : {}),
|
|
115
|
+
...(Object.hasOwn(attributes, "lightSecondaryColor") ? { lightSecondaryColor: normalizeWorkspaceColor(attributes.lightSecondaryColor) } : {}),
|
|
116
|
+
...(Object.hasOwn(attributes, "lightSurfaceColor") ? { lightSurfaceColor: normalizeWorkspaceColor(attributes.lightSurfaceColor) } : {}),
|
|
117
|
+
...(Object.hasOwn(attributes, "lightSurfaceVariantColor") ? { lightSurfaceVariantColor: normalizeWorkspaceColor(attributes.lightSurfaceVariantColor) } : {}),
|
|
118
|
+
...(Object.hasOwn(attributes, "darkPrimaryColor") ? { darkPrimaryColor: normalizeWorkspaceColor(attributes.darkPrimaryColor) } : {}),
|
|
119
|
+
...(Object.hasOwn(attributes, "darkSecondaryColor") ? { darkSecondaryColor: normalizeWorkspaceColor(attributes.darkSecondaryColor) } : {}),
|
|
120
|
+
...(Object.hasOwn(attributes, "darkSurfaceColor") ? { darkSurfaceColor: normalizeWorkspaceColor(attributes.darkSurfaceColor) } : {}),
|
|
121
|
+
...(Object.hasOwn(attributes, "darkSurfaceVariantColor") ? { darkSurfaceVariantColor: normalizeWorkspaceColor(attributes.darkSurfaceVariantColor) } : {}),
|
|
122
|
+
id: String(inputRecord.id || state.row?.id || "")
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
data: {
|
|
126
|
+
type: "workspaceSettings",
|
|
127
|
+
id: String(state.row.id),
|
|
128
|
+
attributes: {
|
|
129
|
+
...state.row
|
|
130
|
+
}
|
|
96
131
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
};
|
|
132
|
+
};
|
|
133
|
+
}
|
|
100
134
|
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
103
137
|
|
|
104
|
-
return {
|
|
138
|
+
return { api, state, DEFAULT_WORKSPACE_THEME, STUB_CREATED_AT };
|
|
105
139
|
}
|
|
106
140
|
|
|
107
|
-
test("workspaceSettingsRepository.findByWorkspaceId
|
|
108
|
-
const {
|
|
109
|
-
const repository = createRepository(
|
|
110
|
-
|
|
141
|
+
test("workspaceSettingsRepository.findByWorkspaceId returns the canonical workspace-settings row", async () => {
|
|
142
|
+
const { api, DEFAULT_WORKSPACE_THEME, STUB_CREATED_AT } = createWorkspaceSettingsApiStub();
|
|
143
|
+
const repository = createRepository({
|
|
144
|
+
api,
|
|
145
|
+
knex: createKnexStub()
|
|
111
146
|
});
|
|
112
147
|
|
|
113
148
|
const record = await repository.findByWorkspaceId("1");
|
|
114
149
|
|
|
115
150
|
assert.deepEqual(record, {
|
|
116
|
-
|
|
151
|
+
id: "1",
|
|
117
152
|
lightPrimaryColor: DEFAULT_WORKSPACE_THEME.light.color,
|
|
118
153
|
lightSecondaryColor: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
|
|
119
154
|
lightSurfaceColor: DEFAULT_WORKSPACE_THEME.light.surfaceColor,
|
|
@@ -129,44 +164,40 @@ test("workspaceSettingsRepository.findByWorkspaceId maps the stored row", async
|
|
|
129
164
|
});
|
|
130
165
|
|
|
131
166
|
test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates invitesEnabled only", async () => {
|
|
132
|
-
const {
|
|
133
|
-
const repository = createRepository(
|
|
134
|
-
|
|
167
|
+
const { api, state } = createWorkspaceSettingsApiStub();
|
|
168
|
+
const repository = createRepository({
|
|
169
|
+
api,
|
|
170
|
+
knex: createKnexStub()
|
|
135
171
|
});
|
|
136
172
|
|
|
137
173
|
const updated = await repository.updateSettingsByWorkspaceId("1", {
|
|
138
174
|
invitesEnabled: false
|
|
139
175
|
});
|
|
140
176
|
|
|
141
|
-
assert.equal(state.
|
|
177
|
+
assert.equal(state.patchPayload.attributes?.invitesEnabled, false);
|
|
142
178
|
assert.equal(updated.invitesEnabled, false);
|
|
143
179
|
});
|
|
144
180
|
|
|
145
|
-
test("workspaceSettingsRepository.ensureForWorkspaceId
|
|
146
|
-
const {
|
|
181
|
+
test("workspaceSettingsRepository.ensureForWorkspaceId delegates defaults to the resource create path", async () => {
|
|
182
|
+
const { api, state, DEFAULT_WORKSPACE_THEME } = createWorkspaceSettingsApiStub();
|
|
147
183
|
state.row = null;
|
|
148
|
-
const repository = createRepository(
|
|
149
|
-
|
|
184
|
+
const repository = createRepository({
|
|
185
|
+
api,
|
|
186
|
+
knex: createKnexStub()
|
|
150
187
|
});
|
|
151
188
|
|
|
152
189
|
const record = await repository.ensureForWorkspaceId("5");
|
|
153
190
|
|
|
154
|
-
assert.equal(state.
|
|
155
|
-
assert.equal(state.
|
|
156
|
-
assert.equal(state.
|
|
157
|
-
assert.equal(state.
|
|
158
|
-
assert.equal(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
);
|
|
162
|
-
assert.equal(state.
|
|
163
|
-
assert.equal(state.
|
|
164
|
-
assert.equal(state.insertedRow.dark_surface_color, DEFAULT_WORKSPACE_THEME.dark.surfaceColor);
|
|
165
|
-
assert.equal(
|
|
166
|
-
state.insertedRow.dark_surface_variant_color,
|
|
167
|
-
DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor
|
|
168
|
-
);
|
|
169
|
-
assert.equal(state.insertedRow.invites_enabled, false);
|
|
191
|
+
assert.equal(state.postPayload.id, "5");
|
|
192
|
+
assert.equal(state.postPayload.attributes?.lightPrimaryColor, DEFAULT_WORKSPACE_THEME.light.color);
|
|
193
|
+
assert.equal(state.postPayload.attributes?.lightSecondaryColor, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
|
|
194
|
+
assert.equal(state.postPayload.attributes?.lightSurfaceColor, DEFAULT_WORKSPACE_THEME.light.surfaceColor);
|
|
195
|
+
assert.equal(state.postPayload.attributes?.lightSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor);
|
|
196
|
+
assert.equal(state.postPayload.attributes?.darkPrimaryColor, DEFAULT_WORKSPACE_THEME.dark.color);
|
|
197
|
+
assert.equal(state.postPayload.attributes?.darkSecondaryColor, DEFAULT_WORKSPACE_THEME.dark.secondaryColor);
|
|
198
|
+
assert.equal(state.postPayload.attributes?.darkSurfaceColor, DEFAULT_WORKSPACE_THEME.dark.surfaceColor);
|
|
199
|
+
assert.equal(state.postPayload.attributes?.darkSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor);
|
|
200
|
+
assert.equal(state.postPayload.attributes?.invitesEnabled, true);
|
|
170
201
|
assert.equal(record.lightPrimaryColor, DEFAULT_WORKSPACE_THEME.light.color);
|
|
171
202
|
assert.equal(record.lightSecondaryColor, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
|
|
172
203
|
assert.equal(record.lightSurfaceColor, DEFAULT_WORKSPACE_THEME.light.surfaceColor);
|
|
@@ -175,28 +206,32 @@ test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defa
|
|
|
175
206
|
assert.equal(record.darkSecondaryColor, DEFAULT_WORKSPACE_THEME.dark.secondaryColor);
|
|
176
207
|
assert.equal(record.darkSurfaceColor, DEFAULT_WORKSPACE_THEME.dark.surfaceColor);
|
|
177
208
|
assert.equal(record.darkSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor);
|
|
178
|
-
assert.equal(record.invitesEnabled,
|
|
179
|
-
assert.equal(record.
|
|
209
|
+
assert.equal(record.invitesEnabled, true);
|
|
210
|
+
assert.equal(record.id, "5");
|
|
180
211
|
});
|
|
181
212
|
|
|
182
|
-
test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates workspace settings
|
|
183
|
-
const {
|
|
184
|
-
const repository = createRepository(
|
|
185
|
-
|
|
213
|
+
test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates workspace settings fields", async () => {
|
|
214
|
+
const { api, state } = createWorkspaceSettingsApiStub();
|
|
215
|
+
const repository = createRepository({
|
|
216
|
+
api,
|
|
217
|
+
knex: createKnexStub()
|
|
186
218
|
});
|
|
187
219
|
|
|
188
220
|
const updated = await repository.updateSettingsByWorkspaceId("1", {
|
|
189
221
|
lightPrimaryColor: "#123abc"
|
|
190
222
|
});
|
|
191
223
|
|
|
192
|
-
assert.equal(state.
|
|
224
|
+
assert.equal(state.patchPayload.attributes?.lightPrimaryColor, "#123abc");
|
|
193
225
|
assert.equal(updated.lightPrimaryColor, "#123ABC");
|
|
194
226
|
});
|
|
195
227
|
|
|
196
228
|
test("workspaceSettingsRepository can be constructed without validating app config shape", () => {
|
|
197
|
-
const {
|
|
229
|
+
const { api } = createWorkspaceSettingsApiStub();
|
|
198
230
|
|
|
199
|
-
const repository = createRepository(
|
|
231
|
+
const repository = createRepository({
|
|
232
|
+
api,
|
|
233
|
+
knex: createKnexStub()
|
|
234
|
+
});
|
|
200
235
|
|
|
201
236
|
assert.ok(repository);
|
|
202
237
|
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
|
|
4
|
-
import "
|
|
4
|
+
import { resolveStructuredSchemaTransportSchema } from "@jskit-ai/kernel/shared/validators";
|
|
5
5
|
import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
WORKSPACE_SETTINGS_FIELD_KEYS,
|
|
8
|
+
workspaceSettingsResource
|
|
9
|
+
} from "../src/shared/resources/workspaceSettingsResource.js";
|
|
7
10
|
import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
|
|
8
11
|
|
|
9
12
|
function createRoleCatalog() {
|
|
@@ -41,13 +44,13 @@ function createRoleCatalog() {
|
|
|
41
44
|
function parseBody(operation, payload = {}) {
|
|
42
45
|
return validateOperationSection({
|
|
43
46
|
operation,
|
|
44
|
-
section: "
|
|
47
|
+
section: "body",
|
|
45
48
|
value: payload
|
|
46
49
|
});
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
test("workspace settings patch body
|
|
50
|
-
const parsed = parseBody(workspaceSettingsResource.operations.patch, {
|
|
52
|
+
test("workspace settings patch body validates valid payload without reshaping it", async () => {
|
|
53
|
+
const parsed = await parseBody(workspaceSettingsResource.operations.patch, {
|
|
51
54
|
lightPrimaryColor: "#0f6b54",
|
|
52
55
|
lightSecondaryColor: "#0b4d3c",
|
|
53
56
|
lightSurfaceColor: "#eef5f3",
|
|
@@ -62,10 +65,10 @@ test("workspace settings patch body normalizes valid payload before validation",
|
|
|
62
65
|
assert.equal(parsed.ok, true);
|
|
63
66
|
assert.deepEqual(parsed.fieldErrors, {});
|
|
64
67
|
assert.deepEqual(parsed.value, {
|
|
65
|
-
lightPrimaryColor: "#
|
|
66
|
-
lightSecondaryColor: "#
|
|
67
|
-
lightSurfaceColor: "#
|
|
68
|
-
lightSurfaceVariantColor: "#
|
|
68
|
+
lightPrimaryColor: "#0f6b54",
|
|
69
|
+
lightSecondaryColor: "#0b4d3c",
|
|
70
|
+
lightSurfaceColor: "#eef5f3",
|
|
71
|
+
lightSurfaceVariantColor: "#ddeae7",
|
|
69
72
|
darkPrimaryColor: "#123456",
|
|
70
73
|
darkSecondaryColor: "#234567",
|
|
71
74
|
darkSurfaceColor: "#345678",
|
|
@@ -74,18 +77,17 @@ test("workspace settings patch body normalizes valid payload before validation",
|
|
|
74
77
|
});
|
|
75
78
|
});
|
|
76
79
|
|
|
77
|
-
test("workspace settings patch body
|
|
78
|
-
const parsed = parseBody(workspaceSettingsResource.operations.patch, {
|
|
80
|
+
test("workspace settings patch body rejects unknown fields", async () => {
|
|
81
|
+
const parsed = await parseBody(workspaceSettingsResource.operations.patch, {
|
|
79
82
|
avatarUrl: "https://example.com/avatar.png"
|
|
80
83
|
});
|
|
81
84
|
|
|
82
|
-
assert.equal(parsed.ok,
|
|
83
|
-
assert.
|
|
84
|
-
assert.deepEqual(parsed.value, {});
|
|
85
|
+
assert.equal(parsed.ok, false);
|
|
86
|
+
assert.equal(typeof parsed.fieldErrors.avatarUrl, "string");
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
test("workspace settings create body requires full-write fields", () => {
|
|
88
|
-
const parsed = parseBody(workspaceSettingsResource.operations.create, {});
|
|
89
|
+
test("workspace settings create body requires full-write fields", async () => {
|
|
90
|
+
const parsed = await parseBody(workspaceSettingsResource.operations.create, {});
|
|
89
91
|
|
|
90
92
|
assert.equal(parsed.ok, false);
|
|
91
93
|
assert.equal(parsed.fieldErrors.lightPrimaryColor, "Light primary color is required.");
|
|
@@ -99,24 +101,17 @@ test("workspace settings create body requires full-write fields", () => {
|
|
|
99
101
|
assert.equal(parsed.fieldErrors.invitesEnabled, "invitesEnabled is required.");
|
|
100
102
|
});
|
|
101
103
|
|
|
102
|
-
test("workspace settings output
|
|
104
|
+
test("workspace settings output schema accepts already-shaped service payloads", () => {
|
|
105
|
+
const outputSchema = resolveStructuredSchemaTransportSchema(workspaceSettingsResource.operations.view.output, {
|
|
106
|
+
context: "workspaceSettings.view.output",
|
|
107
|
+
defaultMode: "replace"
|
|
108
|
+
});
|
|
109
|
+
const settingsRef = outputSchema.properties.settings.allOf[0].$ref.replace(/^#\/definitions\//, "");
|
|
110
|
+
const settingsSchema = outputSchema.definitions[settingsRef];
|
|
103
111
|
const expectedTheme = resolveWorkspaceThemePalettes({
|
|
104
112
|
lightPrimaryColor: "#0F6B54"
|
|
105
113
|
});
|
|
106
|
-
const
|
|
107
|
-
workspace: {
|
|
108
|
-
id: "7",
|
|
109
|
-
slug: " mercury ",
|
|
110
|
-
ownerUserId: "9"
|
|
111
|
-
},
|
|
112
|
-
settings: {
|
|
113
|
-
lightPrimaryColor: "#0f6b54",
|
|
114
|
-
invitesEnabled: false
|
|
115
|
-
},
|
|
116
|
-
roleCatalog: createRoleCatalog()
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
assert.deepEqual(normalized, {
|
|
114
|
+
const payload = {
|
|
120
115
|
workspace: {
|
|
121
116
|
id: "7",
|
|
122
117
|
slug: "mercury",
|
|
@@ -135,35 +130,29 @@ test("workspace settings output normalizes raw service payloads", () => {
|
|
|
135
130
|
invitesAvailable: true,
|
|
136
131
|
invitesEffective: false
|
|
137
132
|
},
|
|
138
|
-
roleCatalog:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
permissions: ["workspace.settings.view"]
|
|
164
|
-
}
|
|
165
|
-
],
|
|
166
|
-
assignableRoleIds: ["admin", "member"]
|
|
167
|
-
}
|
|
168
|
-
});
|
|
133
|
+
roleCatalog: createRoleCatalog()
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
assert.equal(outputSchema.type, "object");
|
|
137
|
+
assert.equal(outputSchema.additionalProperties, false);
|
|
138
|
+
assert.equal(outputSchema.properties.workspace["x-json-rest-schema"]?.castType, "object");
|
|
139
|
+
assert.equal(outputSchema.properties.settings["x-json-rest-schema"]?.castType, "object");
|
|
140
|
+
assert.equal(outputSchema.properties.roleCatalog["x-json-rest-schema"]?.castType, "object");
|
|
141
|
+
assert.equal(settingsSchema.properties.lightPrimaryColor.type, "string");
|
|
142
|
+
assert.equal(payload.settings.lightPrimaryColor, "#0F6B54");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
async function importWithIdentity(url, identity) {
|
|
146
|
+
return import(`${url.href}?identity=${identity}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
test("workspace settings key exports stay stable across module identities", async () => {
|
|
150
|
+
const workspaceModuleUrl = new URL("../src/shared/resources/workspaceSettingsResource.js", import.meta.url);
|
|
151
|
+
|
|
152
|
+
const workspaceA = await importWithIdentity(workspaceModuleUrl, "workspace-a");
|
|
153
|
+
const workspaceB = await importWithIdentity(workspaceModuleUrl, "workspace-b");
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(workspaceA.WORKSPACE_SETTINGS_FIELD_KEYS, workspaceB.WORKSPACE_SETTINGS_FIELD_KEYS);
|
|
156
|
+
assert.deepEqual(workspaceA.WORKSPACE_SETTINGS_FIELD_KEYS, WORKSPACE_SETTINGS_FIELD_KEYS);
|
|
157
|
+
assert.ok(Object.isFrozen(workspaceA.WORKSPACE_SETTINGS_FIELD_KEYS));
|
|
169
158
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
3
|
import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
|
|
5
4
|
import { createService } from "../src/server/workspaceSettings/workspaceSettingsService.js";
|
|
6
5
|
|