@jskit-ai/workspaces-core 0.1.13 → 0.1.15

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.
Files changed (83) hide show
  1. package/package.descriptor.mjs +2 -2
  2. package/package.json +18 -3
  3. package/src/server/WorkspacesCoreServiceProvider.js +41 -2
  4. package/src/server/common/contributors/workspaceActionContextContributor.js +88 -0
  5. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
  6. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +78 -0
  7. package/src/server/common/formatters/workspaceFormatter.js +53 -0
  8. package/src/server/common/repositories/repositoryUtils.js +59 -0
  9. package/src/server/common/repositories/workspaceInvitesRepository.js +208 -0
  10. package/src/server/common/repositories/workspaceMembershipsRepository.js +190 -0
  11. package/src/server/common/repositories/workspacesRepository.js +202 -0
  12. package/src/server/common/services/workspaceContextService.js +281 -0
  13. package/src/server/common/support/deepFreeze.js +1 -0
  14. package/src/server/common/support/realtimeServiceEvents.js +91 -0
  15. package/src/server/common/support/resolveActionUser.js +9 -0
  16. package/src/server/common/support/workspaceRoutePaths.js +18 -0
  17. package/src/server/common/validators/authenticatedUserValidator.js +43 -0
  18. package/src/server/common/validators/routeParamsValidator.js +62 -0
  19. package/src/server/registerWorkspaceBootstrap.js +27 -0
  20. package/src/server/registerWorkspaceCore.js +100 -0
  21. package/src/server/registerWorkspaceRepositories.js +26 -0
  22. package/src/server/support/resolveWorkspace.js +16 -0
  23. package/src/server/support/workspaceActionSurfaces.js +118 -0
  24. package/src/server/support/workspaceInvitationsPolicy.js +45 -0
  25. package/src/server/support/workspaceRouteInput.js +22 -0
  26. package/src/server/workspaceBootstrapContributor.js +233 -0
  27. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +133 -0
  28. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
  29. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +133 -0
  30. package/src/server/workspaceMembers/bootWorkspaceMembers.js +236 -0
  31. package/src/server/workspaceMembers/registerWorkspaceMembers.js +108 -0
  32. package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
  33. package/src/server/workspaceMembers/workspaceMembersService.js +222 -0
  34. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +62 -0
  35. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +119 -0
  36. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
  37. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +138 -0
  38. package/src/server/workspaceSettings/bootWorkspaceSettings.js +76 -0
  39. package/src/server/workspaceSettings/registerWorkspaceSettings.js +62 -0
  40. package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
  41. package/src/server/workspaceSettings/workspaceSettingsRepository.js +154 -0
  42. package/src/server/workspaceSettings/workspaceSettingsService.js +66 -0
  43. package/src/shared/operationMessages.js +16 -0
  44. package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
  45. package/src/shared/resources/workspaceMembersResource.js +354 -0
  46. package/src/shared/resources/workspacePendingInvitationsResource.js +82 -0
  47. package/src/shared/resources/workspaceResource.js +176 -0
  48. package/src/shared/resources/workspaceSettingsFields.js +59 -0
  49. package/src/shared/resources/workspaceSettingsResource.js +169 -0
  50. package/src/shared/roles.js +161 -0
  51. package/src/shared/settings.js +119 -0
  52. package/src/shared/support/workspacePathModel.js +145 -0
  53. package/src/shared/tenancyMode.js +35 -0
  54. package/src/shared/tenancyProfile.js +73 -0
  55. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +2 -2
  56. package/test/registerServiceRealtimeEvents.test.js +116 -0
  57. package/test/registerWorkspaceDirectory.test.js +31 -0
  58. package/test/registerWorkspaceSettings.test.js +40 -0
  59. package/test/repositoryContracts.test.js +34 -0
  60. package/test/resourcesCanonical.test.js +74 -0
  61. package/test/roles.test.js +159 -0
  62. package/test/routeParamsValidator.test.js +49 -0
  63. package/test/settingsFieldRegistriesSingleton.test.js +14 -0
  64. package/test/tenancyProfile.test.js +67 -0
  65. package/test/usersRouteResources.test.js +97 -0
  66. package/test/workspaceActionContextContributor.test.js +344 -0
  67. package/test/workspaceActionSurfaces.test.js +85 -0
  68. package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
  69. package/test/workspaceBootstrapContributor.test.js +169 -0
  70. package/test/workspaceInvitationsPolicy.test.js +71 -0
  71. package/test/workspaceInvitesRepository.test.js +111 -0
  72. package/test/workspaceMembersService.test.js +398 -0
  73. package/test/workspacePathModel.test.js +93 -0
  74. package/test/workspacePendingInvitationsResource.test.js +38 -0
  75. package/test/workspacePendingInvitationsService.test.js +151 -0
  76. package/test/workspaceRouteVisibilityResolver.test.js +83 -0
  77. package/test/workspaceService.test.js +546 -0
  78. package/test/workspaceSettingsActions.test.js +52 -0
  79. package/test/workspaceSettingsRepository.test.js +202 -0
  80. package/test/workspaceSettingsResource.test.js +169 -0
  81. package/test/workspaceSettingsService.test.js +140 -0
  82. package/test/workspacesRouteRequestInputValidator.test.js +5 -5
  83. package/test-support/registerDefaultSettingsFields.js +1 -0
@@ -0,0 +1,202 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import "../test-support/registerDefaultSettingsFields.js";
4
+ import { toIsoString } from "@jskit-ai/database-runtime/shared";
5
+ import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
6
+ import { createRepository } from "../src/server/workspaceSettings/workspaceSettingsRepository.js";
7
+
8
+ function createDefaultWorkspaceSettings() {
9
+ return true;
10
+ }
11
+
12
+ const DEFAULT_WORKSPACE_THEME = resolveWorkspaceThemePalettes({});
13
+ const STUB_CREATED_AT = "2026-03-09 00:26:35.710";
14
+
15
+ function createKnexStub(rowOverrides = {}) {
16
+ const state = {
17
+ insertedRow: null,
18
+ updatePayload: null,
19
+ row: {
20
+ workspace_id: 1,
21
+ light_primary_color: DEFAULT_WORKSPACE_THEME.light.color,
22
+ light_secondary_color: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
23
+ light_surface_color: DEFAULT_WORKSPACE_THEME.light.surfaceColor,
24
+ light_surface_variant_color: DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor,
25
+ dark_primary_color: DEFAULT_WORKSPACE_THEME.dark.color,
26
+ dark_secondary_color: DEFAULT_WORKSPACE_THEME.dark.secondaryColor,
27
+ dark_surface_color: DEFAULT_WORKSPACE_THEME.dark.surfaceColor,
28
+ dark_surface_variant_color: DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor,
29
+ invites_enabled: 1,
30
+ created_at: STUB_CREATED_AT,
31
+ updated_at: STUB_CREATED_AT,
32
+ ...rowOverrides
33
+ }
34
+ };
35
+
36
+ function tableBuilder(tableName) {
37
+ assert.equal(tableName, "workspace_settings");
38
+
39
+ return {
40
+ insert(payload) {
41
+ state.insertedRow = { ...payload };
42
+ state.row = {
43
+ workspace_id: payload.workspace_id,
44
+ light_primary_color: payload.light_primary_color,
45
+ light_secondary_color: payload.light_secondary_color,
46
+ light_surface_color: payload.light_surface_color,
47
+ light_surface_variant_color: payload.light_surface_variant_color,
48
+ dark_primary_color: payload.dark_primary_color,
49
+ dark_secondary_color: payload.dark_secondary_color,
50
+ dark_surface_color: payload.dark_surface_color,
51
+ dark_surface_variant_color: payload.dark_surface_variant_color,
52
+ invites_enabled: payload.invites_enabled,
53
+ created_at: "2026-03-10 00:00:00.000",
54
+ updated_at: "2026-03-10 00:00:00.000"
55
+ };
56
+ return Promise.resolve([1]);
57
+ },
58
+ where(criteria) {
59
+ assert.equal(typeof criteria, "object");
60
+
61
+ return {
62
+ first() {
63
+ return Promise.resolve(state.row ? { ...state.row } : null);
64
+ },
65
+ update(payload) {
66
+ state.updatePayload = payload;
67
+ if (Object.hasOwn(payload, "invites_enabled")) {
68
+ state.row.invites_enabled = payload.invites_enabled;
69
+ }
70
+ if (Object.hasOwn(payload, "light_primary_color")) {
71
+ state.row.light_primary_color = payload.light_primary_color;
72
+ }
73
+ if (Object.hasOwn(payload, "light_secondary_color")) {
74
+ state.row.light_secondary_color = payload.light_secondary_color;
75
+ }
76
+ if (Object.hasOwn(payload, "light_surface_color")) {
77
+ state.row.light_surface_color = payload.light_surface_color;
78
+ }
79
+ if (Object.hasOwn(payload, "light_surface_variant_color")) {
80
+ state.row.light_surface_variant_color = payload.light_surface_variant_color;
81
+ }
82
+ if (Object.hasOwn(payload, "dark_primary_color")) {
83
+ state.row.dark_primary_color = payload.dark_primary_color;
84
+ }
85
+ if (Object.hasOwn(payload, "dark_secondary_color")) {
86
+ state.row.dark_secondary_color = payload.dark_secondary_color;
87
+ }
88
+ if (Object.hasOwn(payload, "dark_surface_color")) {
89
+ state.row.dark_surface_color = payload.dark_surface_color;
90
+ }
91
+ if (Object.hasOwn(payload, "dark_surface_variant_color")) {
92
+ state.row.dark_surface_variant_color = payload.dark_surface_variant_color;
93
+ }
94
+ if (Object.hasOwn(payload, "updated_at")) {
95
+ state.row.updated_at = payload.updated_at;
96
+ }
97
+ return Promise.resolve(1);
98
+ }
99
+ };
100
+ }
101
+ };
102
+ }
103
+
104
+ return { knexStub: tableBuilder, state };
105
+ }
106
+
107
+ test("workspaceSettingsRepository.findByWorkspaceId maps the stored row", async () => {
108
+ const { knexStub } = createKnexStub();
109
+ const repository = createRepository(knexStub, {
110
+ defaultInvitesEnabled: createDefaultWorkspaceSettings()
111
+ });
112
+
113
+ const record = await repository.findByWorkspaceId("1");
114
+
115
+ assert.deepEqual(record, {
116
+ workspaceId: "1",
117
+ lightPrimaryColor: DEFAULT_WORKSPACE_THEME.light.color,
118
+ lightSecondaryColor: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
119
+ lightSurfaceColor: DEFAULT_WORKSPACE_THEME.light.surfaceColor,
120
+ lightSurfaceVariantColor: DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor,
121
+ darkPrimaryColor: DEFAULT_WORKSPACE_THEME.dark.color,
122
+ darkSecondaryColor: DEFAULT_WORKSPACE_THEME.dark.secondaryColor,
123
+ darkSurfaceColor: DEFAULT_WORKSPACE_THEME.dark.surfaceColor,
124
+ darkSurfaceVariantColor: DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor,
125
+ invitesEnabled: true,
126
+ createdAt: toIsoString(STUB_CREATED_AT),
127
+ updatedAt: toIsoString(STUB_CREATED_AT)
128
+ });
129
+ });
130
+
131
+ test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates invitesEnabled only", async () => {
132
+ const { knexStub, state } = createKnexStub();
133
+ const repository = createRepository(knexStub, {
134
+ defaultInvitesEnabled: createDefaultWorkspaceSettings()
135
+ });
136
+
137
+ const updated = await repository.updateSettingsByWorkspaceId("1", {
138
+ invitesEnabled: false
139
+ });
140
+
141
+ assert.equal(state.updatePayload.invites_enabled, false);
142
+ assert.equal(updated.invitesEnabled, false);
143
+ });
144
+
145
+ test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defaults exactly", async () => {
146
+ const { knexStub, state } = createKnexStub();
147
+ state.row = null;
148
+ const repository = createRepository(knexStub, {
149
+ defaultInvitesEnabled: false
150
+ });
151
+
152
+ const record = await repository.ensureForWorkspaceId("5");
153
+
154
+ assert.equal(state.insertedRow.workspace_id, "5");
155
+ assert.equal(state.insertedRow.light_primary_color, DEFAULT_WORKSPACE_THEME.light.color);
156
+ assert.equal(state.insertedRow.light_secondary_color, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
157
+ assert.equal(state.insertedRow.light_surface_color, DEFAULT_WORKSPACE_THEME.light.surfaceColor);
158
+ assert.equal(
159
+ state.insertedRow.light_surface_variant_color,
160
+ DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor
161
+ );
162
+ assert.equal(state.insertedRow.dark_primary_color, DEFAULT_WORKSPACE_THEME.dark.color);
163
+ assert.equal(state.insertedRow.dark_secondary_color, DEFAULT_WORKSPACE_THEME.dark.secondaryColor);
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);
170
+ assert.equal(record.lightPrimaryColor, DEFAULT_WORKSPACE_THEME.light.color);
171
+ assert.equal(record.lightSecondaryColor, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
172
+ assert.equal(record.lightSurfaceColor, DEFAULT_WORKSPACE_THEME.light.surfaceColor);
173
+ assert.equal(record.lightSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.light.surfaceVariantColor);
174
+ assert.equal(record.darkPrimaryColor, DEFAULT_WORKSPACE_THEME.dark.color);
175
+ assert.equal(record.darkSecondaryColor, DEFAULT_WORKSPACE_THEME.dark.secondaryColor);
176
+ assert.equal(record.darkSurfaceColor, DEFAULT_WORKSPACE_THEME.dark.surfaceColor);
177
+ assert.equal(record.darkSurfaceVariantColor, DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor);
178
+ assert.equal(record.invitesEnabled, false);
179
+ assert.equal(record.workspaceId, "5");
180
+ });
181
+
182
+ test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates workspace settings columns", async () => {
183
+ const { knexStub, state } = createKnexStub();
184
+ const repository = createRepository(knexStub, {
185
+ defaultInvitesEnabled: true
186
+ });
187
+
188
+ const updated = await repository.updateSettingsByWorkspaceId("1", {
189
+ lightPrimaryColor: "#123abc"
190
+ });
191
+
192
+ assert.equal(state.updatePayload.light_primary_color, "#123ABC");
193
+ assert.equal(updated.lightPrimaryColor, "#123ABC");
194
+ });
195
+
196
+ test("workspaceSettingsRepository can be constructed without validating app config shape", () => {
197
+ const { knexStub } = createKnexStub();
198
+
199
+ const repository = createRepository(knexStub);
200
+
201
+ assert.ok(repository);
202
+ });
@@ -0,0 +1,169 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
4
+ import "../test-support/registerDefaultSettingsFields.js";
5
+ import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
6
+ import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
7
+ import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
8
+
9
+ function createRoleCatalog() {
10
+ return createWorkspaceRoleCatalog({
11
+ roleCatalog: {
12
+ workspace: {
13
+ defaultInviteRole: "member"
14
+ },
15
+ roles: {
16
+ owner: {
17
+ assignable: false,
18
+ permissions: ["*"]
19
+ },
20
+ admin: {
21
+ assignable: true,
22
+ permissions: [
23
+ "workspace.roles.view",
24
+ "workspace.settings.view",
25
+ "workspace.settings.update",
26
+ "workspace.members.view",
27
+ "workspace.members.invite",
28
+ "workspace.members.manage",
29
+ "workspace.invites.revoke"
30
+ ]
31
+ },
32
+ member: {
33
+ assignable: true,
34
+ permissions: ["workspace.settings.view"]
35
+ }
36
+ }
37
+ }
38
+ });
39
+ }
40
+
41
+ function parseBody(operation, payload = {}) {
42
+ return validateOperationSection({
43
+ operation,
44
+ section: "bodyValidator",
45
+ value: payload
46
+ });
47
+ }
48
+
49
+ test("workspace settings patch body normalizes valid payload before validation", () => {
50
+ const parsed = parseBody(workspaceSettingsResource.operations.patch, {
51
+ lightPrimaryColor: "#0f6b54",
52
+ lightSecondaryColor: "#0b4d3c",
53
+ lightSurfaceColor: "#eef5f3",
54
+ lightSurfaceVariantColor: "#ddeae7",
55
+ darkPrimaryColor: "#123456",
56
+ darkSecondaryColor: "#234567",
57
+ darkSurfaceColor: "#345678",
58
+ darkSurfaceVariantColor: "#456789",
59
+ invitesEnabled: false
60
+ });
61
+
62
+ assert.equal(parsed.ok, true);
63
+ assert.deepEqual(parsed.fieldErrors, {});
64
+ assert.deepEqual(parsed.value, {
65
+ lightPrimaryColor: "#0F6B54",
66
+ lightSecondaryColor: "#0B4D3C",
67
+ lightSurfaceColor: "#EEF5F3",
68
+ lightSurfaceVariantColor: "#DDEAE7",
69
+ darkPrimaryColor: "#123456",
70
+ darkSecondaryColor: "#234567",
71
+ darkSurfaceColor: "#345678",
72
+ darkSurfaceVariantColor: "#456789",
73
+ invitesEnabled: false
74
+ });
75
+ });
76
+
77
+ test("workspace settings patch body ignores unknown fields after normalization", () => {
78
+ const parsed = parseBody(workspaceSettingsResource.operations.patch, {
79
+ avatarUrl: "https://example.com/avatar.png"
80
+ });
81
+
82
+ assert.equal(parsed.ok, true);
83
+ assert.deepEqual(parsed.fieldErrors, {});
84
+ assert.deepEqual(parsed.value, {});
85
+ });
86
+
87
+ test("workspace settings create body requires full-write fields", () => {
88
+ const parsed = parseBody(workspaceSettingsResource.operations.create, {});
89
+
90
+ assert.equal(parsed.ok, false);
91
+ assert.equal(parsed.fieldErrors.lightPrimaryColor, "Light primary color is required.");
92
+ assert.equal(parsed.fieldErrors.lightSecondaryColor, "Light secondary color is required.");
93
+ assert.equal(parsed.fieldErrors.lightSurfaceColor, "Light surface color is required.");
94
+ assert.equal(parsed.fieldErrors.lightSurfaceVariantColor, "Light surface variant color is required.");
95
+ assert.equal(parsed.fieldErrors.darkPrimaryColor, "Dark primary color is required.");
96
+ assert.equal(parsed.fieldErrors.darkSecondaryColor, "Dark secondary color is required.");
97
+ assert.equal(parsed.fieldErrors.darkSurfaceColor, "Dark surface color is required.");
98
+ assert.equal(parsed.fieldErrors.darkSurfaceVariantColor, "Dark surface variant color is required.");
99
+ assert.equal(parsed.fieldErrors.invitesEnabled, "invitesEnabled is required.");
100
+ });
101
+
102
+ test("workspace settings output normalizes raw service payloads", () => {
103
+ const expectedTheme = resolveWorkspaceThemePalettes({
104
+ lightPrimaryColor: "#0F6B54"
105
+ });
106
+ const normalized = workspaceSettingsResource.operations.view.outputValidator.normalize({
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, {
120
+ workspace: {
121
+ id: "7",
122
+ slug: "mercury",
123
+ ownerUserId: "9"
124
+ },
125
+ settings: {
126
+ lightPrimaryColor: "#0F6B54",
127
+ lightSecondaryColor: expectedTheme.light.secondaryColor,
128
+ lightSurfaceColor: expectedTheme.light.surfaceColor,
129
+ lightSurfaceVariantColor: expectedTheme.light.surfaceVariantColor,
130
+ darkPrimaryColor: expectedTheme.dark.color,
131
+ darkSecondaryColor: expectedTheme.dark.secondaryColor,
132
+ darkSurfaceColor: expectedTheme.dark.surfaceColor,
133
+ darkSurfaceVariantColor: expectedTheme.dark.surfaceVariantColor,
134
+ invitesEnabled: false,
135
+ invitesAvailable: true,
136
+ invitesEffective: false
137
+ },
138
+ roleCatalog: {
139
+ collaborationEnabled: true,
140
+ defaultInviteRole: "member",
141
+ roles: [
142
+ {
143
+ id: "owner",
144
+ assignable: false,
145
+ permissions: ["*"]
146
+ },
147
+ {
148
+ id: "admin",
149
+ assignable: true,
150
+ permissions: [
151
+ "workspace.roles.view",
152
+ "workspace.settings.view",
153
+ "workspace.settings.update",
154
+ "workspace.members.view",
155
+ "workspace.members.invite",
156
+ "workspace.members.manage",
157
+ "workspace.invites.revoke"
158
+ ]
159
+ },
160
+ {
161
+ id: "member",
162
+ assignable: true,
163
+ permissions: ["workspace.settings.view"]
164
+ }
165
+ ],
166
+ assignableRoleIds: ["admin", "member"]
167
+ }
168
+ });
169
+ });
@@ -0,0 +1,140 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import "../test-support/registerDefaultSettingsFields.js";
4
+ import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
5
+ import { createService } from "../src/server/workspaceSettings/workspaceSettingsService.js";
6
+
7
+ function authorizedOptions(permissions = []) {
8
+ return {
9
+ context: {
10
+ actor: {
11
+ id: 1
12
+ },
13
+ permissions
14
+ }
15
+ };
16
+ }
17
+
18
+ function createFixture({ workspaceInvitationsEnabled = true } = {}) {
19
+ const defaultTheme = resolveWorkspaceThemePalettes({
20
+ lightPrimaryColor: "#0F6B54"
21
+ });
22
+ const state = {
23
+ settingsPatch: null,
24
+ workspace: {
25
+ id: 7,
26
+ slug: "tonymobily3",
27
+ name: "TonyMobily3",
28
+ ownerUserId: 9
29
+ },
30
+ settings: {
31
+ lightPrimaryColor: defaultTheme.light.color,
32
+ lightSecondaryColor: defaultTheme.light.secondaryColor,
33
+ lightSurfaceColor: defaultTheme.light.surfaceColor,
34
+ lightSurfaceVariantColor: defaultTheme.light.surfaceVariantColor,
35
+ darkPrimaryColor: defaultTheme.dark.color,
36
+ darkSecondaryColor: defaultTheme.dark.secondaryColor,
37
+ darkSurfaceColor: defaultTheme.dark.surfaceColor,
38
+ darkSurfaceVariantColor: defaultTheme.dark.surfaceVariantColor,
39
+ invitesEnabled: true
40
+ }
41
+ };
42
+
43
+ const service = createService({
44
+ workspaceInvitationsEnabled,
45
+ workspaceSettingsRepository: {
46
+ async ensureForWorkspaceId(workspaceId) {
47
+ assert.equal(Number(workspaceId), 7);
48
+ return { ...state.settings };
49
+ },
50
+ async updateSettingsByWorkspaceId(workspaceId, patch) {
51
+ assert.equal(Number(workspaceId), 7);
52
+ state.settingsPatch = { ...patch };
53
+ state.settings = {
54
+ ...state.settings,
55
+ ...patch
56
+ };
57
+ return state.settings;
58
+ }
59
+ }
60
+ });
61
+
62
+ return { service, state };
63
+ }
64
+
65
+ test("workspaceSettingsService.getWorkspaceSettings returns the stored invitesEnabled flag", async () => {
66
+ const { service, state } = createFixture();
67
+
68
+ const response = await service.getWorkspaceSettings(
69
+ state.workspace,
70
+ authorizedOptions(["workspace.settings.view"])
71
+ );
72
+
73
+ assert.deepEqual(response.settings, {
74
+ lightPrimaryColor: "#0F6B54",
75
+ lightSecondaryColor: "#48A9A6",
76
+ lightSurfaceColor: "#FFFFFF",
77
+ lightSurfaceVariantColor: "#424242",
78
+ darkPrimaryColor: "#2196F3",
79
+ darkSecondaryColor: "#54B6B2",
80
+ darkSurfaceColor: "#212121",
81
+ darkSurfaceVariantColor: "#C8C8C8",
82
+ invitesEnabled: true,
83
+ invitesAvailable: true,
84
+ invitesEffective: true
85
+ });
86
+ });
87
+
88
+ test("workspaceSettingsService.updateWorkspaceSettings writes editable fields through workspaceSettingsRepository only", async () => {
89
+ const { service, state } = createFixture();
90
+
91
+ const response = await service.updateWorkspaceSettings(
92
+ state.workspace,
93
+ {
94
+ invitesEnabled: false
95
+ },
96
+ authorizedOptions(["workspace.settings.update"])
97
+ );
98
+
99
+ assert.deepEqual(state.settingsPatch, {
100
+ invitesEnabled: false
101
+ });
102
+ assert.deepEqual(response.settings, {
103
+ lightPrimaryColor: "#0F6B54",
104
+ lightSecondaryColor: "#48A9A6",
105
+ lightSurfaceColor: "#FFFFFF",
106
+ lightSurfaceVariantColor: "#424242",
107
+ darkPrimaryColor: "#2196F3",
108
+ darkSecondaryColor: "#54B6B2",
109
+ darkSurfaceColor: "#212121",
110
+ darkSurfaceVariantColor: "#C8C8C8",
111
+ invitesEnabled: false,
112
+ invitesAvailable: true,
113
+ invitesEffective: false
114
+ });
115
+ });
116
+
117
+ test("workspaceSettingsService disables invite settings in output when app policy disables invitations", async () => {
118
+ const { service, state } = createFixture({
119
+ workspaceInvitationsEnabled: false
120
+ });
121
+
122
+ const response = await service.getWorkspaceSettings(
123
+ state.workspace,
124
+ authorizedOptions(["workspace.settings.view"])
125
+ );
126
+
127
+ assert.deepEqual(response.settings, {
128
+ lightPrimaryColor: "#0F6B54",
129
+ lightSecondaryColor: "#48A9A6",
130
+ lightSurfaceColor: "#FFFFFF",
131
+ lightSurfaceVariantColor: "#424242",
132
+ darkPrimaryColor: "#2196F3",
133
+ darkSecondaryColor: "#54B6B2",
134
+ darkSurfaceColor: "#212121",
135
+ darkSurfaceVariantColor: "#C8C8C8",
136
+ invitesEnabled: false,
137
+ invitesAvailable: false,
138
+ invitesEffective: false
139
+ });
140
+ });
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { UsersCoreServiceProvider } from "../../users-core/src/server/UsersCoreServiceProvider.js";
4
- import { resolveTenancyProfile } from "../../users-core/src/shared/tenancyProfile.js";
4
+ import { resolveTenancyProfile } from "../src/shared/tenancyProfile.js";
5
5
  import { WorkspacesCoreServiceProvider } from "../src/server/WorkspacesCoreServiceProvider.js";
6
6
 
7
7
  function createReplyDouble() {
@@ -63,10 +63,10 @@ async function registerRoutes({
63
63
  }
64
64
  ],
65
65
  ["actionExecutor", {}],
66
- ["users.workspace.enabled", workspaceEnabled],
67
- ["users.workspace.tenancy.enabled", workspaceTenancyEnabled],
68
- ["users.workspace.invitations.enabled", workspaceInvitationsEnabled],
69
- ["users.workspace.self-create.enabled", workspaceSelfCreateEnabled]
66
+ ["workspaces.enabled", workspaceEnabled],
67
+ ["workspaces.tenancy.enabled", workspaceTenancyEnabled],
68
+ ["workspaces.invitations.enabled", workspaceInvitationsEnabled],
69
+ ["workspaces.self-create.enabled", workspaceSelfCreateEnabled]
70
70
  ]);
71
71
 
72
72
  if (consoleService) {
@@ -0,0 +1 @@
1
+ import "../templates/packages/main/src/shared/resources/workspaceSettingsFields.js";