@jskit-ai/users-core 0.1.4
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 +464 -0
- package/package.json +35 -0
- package/src/server/UsersCoreServiceProvider.js +74 -0
- package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
- package/src/server/accountNotifications/accountNotificationsService.js +41 -0
- package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
- package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
- package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
- package/src/server/accountPreferences/accountPreferencesService.js +41 -0
- package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
- package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
- package/src/server/accountProfile/accountProfileActions.js +137 -0
- package/src/server/accountProfile/accountProfileService.js +124 -0
- package/src/server/accountProfile/avatarService.js +141 -0
- package/src/server/accountProfile/avatarStorageService.js +132 -0
- package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
- package/src/server/accountProfile/registerAccountProfile.js +62 -0
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
- package/src/server/accountSecurity/accountSecurityActions.js +144 -0
- package/src/server/accountSecurity/accountSecurityService.js +103 -0
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
- package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
- package/src/server/common/README.md +21 -0
- package/src/server/common/contributors/README.md +11 -0
- package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
- package/src/server/common/diTokens.js +21 -0
- package/src/server/common/formatters/README.md +11 -0
- package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
- package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
- package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
- package/src/server/common/formatters/workspaceFormatter.js +46 -0
- package/src/server/common/registerCommonRepositories.js +45 -0
- package/src/server/common/registerSharedApi.js +9 -0
- package/src/server/common/repositories/README.md +24 -0
- package/src/server/common/repositories/repositoryUtils.js +50 -0
- package/src/server/common/repositories/userProfilesRepository.js +251 -0
- package/src/server/common/repositories/userSettingsRepository.js +179 -0
- package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
- package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
- package/src/server/common/repositories/workspacesRepository.js +183 -0
- package/src/server/common/routes/README.md +11 -0
- package/src/server/common/services/README.md +12 -0
- package/src/server/common/services/accountContextService.js +31 -0
- package/src/server/common/services/authProfileSyncService.js +128 -0
- package/src/server/common/services/workspaceContextService.js +270 -0
- package/src/server/common/support/deepFreeze.js +17 -0
- package/src/server/common/support/realtimeServiceEvents.js +94 -0
- package/src/server/common/support/resolveActionUser.js +11 -0
- package/src/server/common/support/workspaceRoutePaths.js +17 -0
- package/src/server/common/validators/README.md +11 -0
- package/src/server/common/validators/authenticatedUserValidator.js +42 -0
- package/src/server/common/validators/routeParamsValidator.js +62 -0
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
- package/src/server/consoleSettings/consoleService.js +36 -0
- package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
- package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
- package/src/server/consoleSettings/consoleSettingsService.js +40 -0
- package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
- package/src/server/registerWorkspaceBootstrap.js +36 -0
- package/src/server/registerWorkspaceCore.js +95 -0
- package/src/server/support/resolveWorkspace.js +16 -0
- package/src/server/support/workspaceActionSurfaces.js +135 -0
- package/src/server/support/workspaceInvitationsPolicy.js +45 -0
- package/src/server/support/workspaceRouteInput.js +22 -0
- package/src/server/workspaceBootstrapContributor.js +401 -0
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
- package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
- package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
- package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
- package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
- package/src/shared/events/usersEvents.js +19 -0
- package/src/shared/index.js +91 -0
- package/src/shared/operationMessages.js +16 -0
- package/src/shared/resources/consoleSettingsFields.js +55 -0
- package/src/shared/resources/consoleSettingsResource.js +139 -0
- package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
- package/src/shared/resources/userProfileResource.js +148 -0
- package/src/shared/resources/userSettingsFields.js +71 -0
- package/src/shared/resources/userSettingsResource.js +416 -0
- package/src/shared/resources/workspaceMembersResource.js +352 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
- package/src/shared/resources/workspaceResource.js +149 -0
- package/src/shared/resources/workspaceSettingsFields.js +60 -0
- package/src/shared/resources/workspaceSettingsResource.js +178 -0
- package/src/shared/roles.js +136 -0
- package/src/shared/settings.js +31 -0
- package/src/shared/support/usersApiPaths.js +34 -0
- package/src/shared/support/usersVisibility.js +45 -0
- package/src/shared/support/workspacePathModel.js +145 -0
- package/src/shared/tenancyMode.js +35 -0
- package/src/shared/tenancyProfile.js +73 -0
- package/templates/config/workspaceRoles.js +30 -0
- package/templates/migrations/users_core_console_owner.cjs +39 -0
- package/templates/migrations/users_core_initial.cjs +118 -0
- package/templates/migrations/users_core_profile_username.cjs +98 -0
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
- package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
- package/test/authProfileSyncService.test.js +119 -0
- package/test/avatarService.test.js +114 -0
- package/test/avatarStorageService.test.js +61 -0
- package/test/consoleService.test.js +57 -0
- package/test/consoleSettingsService.test.js +86 -0
- package/test/exportsContract.test.js +38 -0
- package/test/registerAvatarMultipartSupport.test.js +64 -0
- package/test/registerServiceRealtimeEvents.test.js +160 -0
- package/test/registerWorkspaceDirectory.test.js +26 -0
- package/test/registerWorkspaceSettings.test.js +44 -0
- package/test/resourcesCanonical.test.js +90 -0
- package/test/roles.test.js +74 -0
- package/test/settingsFieldRegistriesSingleton.test.js +24 -0
- package/test/tenancyProfile.test.js +67 -0
- package/test/userSettingsResource.test.js +31 -0
- package/test/usersApiPaths.test.js +31 -0
- package/test/usersRouteRequestInputValidator.test.js +556 -0
- package/test/usersRouteResources.test.js +113 -0
- package/test/usersRouteValidators.test.js +49 -0
- package/test/usersVisibility.test.js +22 -0
- package/test/workspaceActionContextContributor.test.js +251 -0
- package/test/workspaceActionSurfaces.test.js +105 -0
- package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
- package/test/workspaceBootstrapContributor.test.js +466 -0
- package/test/workspaceInvitationsPolicy.test.js +71 -0
- package/test/workspaceInvitesRepository.test.js +111 -0
- package/test/workspaceMembersService.test.js +400 -0
- package/test/workspacePathModel.test.js +93 -0
- package/test/workspacePendingInvitationsResource.test.js +38 -0
- package/test/workspacePendingInvitationsService.test.js +151 -0
- package/test/workspaceRouteVisibilityResolver.test.js +83 -0
- package/test/workspaceService.test.js +480 -0
- package/test/workspaceSettingsActions.test.js +42 -0
- package/test/workspaceSettingsRepository.test.js +156 -0
- package/test/workspaceSettingsResource.test.js +156 -0
- package/test/workspaceSettingsService.test.js +120 -0
- package/test-support/registerDefaultSettingsFields.js +3 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
|
+
import { createRepository } from "../src/server/workspaceSettings/workspaceSettingsRepository.js";
|
|
5
|
+
|
|
6
|
+
function createDefaultWorkspaceSettings() {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createKnexStub(rowOverrides = {}) {
|
|
11
|
+
const state = {
|
|
12
|
+
insertedRow: null,
|
|
13
|
+
updatePayload: null,
|
|
14
|
+
row: {
|
|
15
|
+
workspace_id: 1,
|
|
16
|
+
name: "Workspace",
|
|
17
|
+
avatar_url: "",
|
|
18
|
+
color: "#0F6B54",
|
|
19
|
+
invites_enabled: 1,
|
|
20
|
+
created_at: "2026-03-09 00:26:35.710",
|
|
21
|
+
updated_at: "2026-03-09 00:26:35.710",
|
|
22
|
+
...rowOverrides
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function tableBuilder(tableName) {
|
|
27
|
+
assert.equal(tableName, "workspace_settings");
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
insert(payload) {
|
|
31
|
+
state.insertedRow = { ...payload };
|
|
32
|
+
state.row = {
|
|
33
|
+
workspace_id: payload.workspace_id,
|
|
34
|
+
name: payload.name,
|
|
35
|
+
avatar_url: payload.avatar_url,
|
|
36
|
+
color: payload.color,
|
|
37
|
+
invites_enabled: payload.invites_enabled,
|
|
38
|
+
created_at: "2026-03-10 00:00:00.000",
|
|
39
|
+
updated_at: "2026-03-10 00:00:00.000"
|
|
40
|
+
};
|
|
41
|
+
return Promise.resolve([1]);
|
|
42
|
+
},
|
|
43
|
+
where(criteria) {
|
|
44
|
+
assert.equal(typeof criteria, "object");
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
first() {
|
|
48
|
+
return Promise.resolve(state.row ? { ...state.row } : null);
|
|
49
|
+
},
|
|
50
|
+
update(payload) {
|
|
51
|
+
state.updatePayload = payload;
|
|
52
|
+
if (Object.hasOwn(payload, "invites_enabled")) {
|
|
53
|
+
state.row.invites_enabled = payload.invites_enabled;
|
|
54
|
+
}
|
|
55
|
+
if (Object.hasOwn(payload, "name")) {
|
|
56
|
+
state.row.name = payload.name;
|
|
57
|
+
}
|
|
58
|
+
if (Object.hasOwn(payload, "avatar_url")) {
|
|
59
|
+
state.row.avatar_url = payload.avatar_url;
|
|
60
|
+
}
|
|
61
|
+
if (Object.hasOwn(payload, "color")) {
|
|
62
|
+
state.row.color = payload.color;
|
|
63
|
+
}
|
|
64
|
+
if (Object.hasOwn(payload, "updated_at")) {
|
|
65
|
+
state.row.updated_at = payload.updated_at;
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve(1);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { knexStub: tableBuilder, state };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("workspaceSettingsRepository.findByWorkspaceId maps the stored row", async () => {
|
|
78
|
+
const { knexStub } = createKnexStub();
|
|
79
|
+
const repository = createRepository(knexStub, {
|
|
80
|
+
defaultInvitesEnabled: createDefaultWorkspaceSettings()
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const record = await repository.findByWorkspaceId(1);
|
|
84
|
+
|
|
85
|
+
assert.deepEqual(record, {
|
|
86
|
+
workspaceId: 1,
|
|
87
|
+
name: "Workspace",
|
|
88
|
+
avatarUrl: "",
|
|
89
|
+
color: "#0F6B54",
|
|
90
|
+
invitesEnabled: true,
|
|
91
|
+
createdAt: "2026-03-08T16:26:35.710Z",
|
|
92
|
+
updatedAt: "2026-03-08T16:26:35.710Z"
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates invitesEnabled only", async () => {
|
|
97
|
+
const { knexStub, state } = createKnexStub();
|
|
98
|
+
const repository = createRepository(knexStub, {
|
|
99
|
+
defaultInvitesEnabled: createDefaultWorkspaceSettings()
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const updated = await repository.updateSettingsByWorkspaceId(1, {
|
|
103
|
+
invitesEnabled: false
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
assert.equal(state.updatePayload.invites_enabled, false);
|
|
107
|
+
assert.equal(updated.invitesEnabled, false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defaults exactly", async () => {
|
|
111
|
+
const { knexStub, state } = createKnexStub();
|
|
112
|
+
state.row = null;
|
|
113
|
+
const repository = createRepository(knexStub, {
|
|
114
|
+
defaultInvitesEnabled: false
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const record = await repository.ensureForWorkspaceId(5);
|
|
118
|
+
|
|
119
|
+
assert.equal(state.insertedRow.workspace_id, 5);
|
|
120
|
+
assert.equal(state.insertedRow.name, "Workspace");
|
|
121
|
+
assert.equal(state.insertedRow.avatar_url, "");
|
|
122
|
+
assert.equal(state.insertedRow.color, "#0F6B54");
|
|
123
|
+
assert.equal(state.insertedRow.invites_enabled, false);
|
|
124
|
+
assert.equal(record.name, "Workspace");
|
|
125
|
+
assert.equal(record.avatarUrl, "");
|
|
126
|
+
assert.equal(record.color, "#0F6B54");
|
|
127
|
+
assert.equal(record.invitesEnabled, false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates name/avatar/color columns", async () => {
|
|
131
|
+
const { knexStub, state } = createKnexStub();
|
|
132
|
+
const repository = createRepository(knexStub, {
|
|
133
|
+
defaultInvitesEnabled: true
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const updated = await repository.updateSettingsByWorkspaceId(1, {
|
|
137
|
+
name: "New name",
|
|
138
|
+
avatarUrl: "https://example.com/avatar.png",
|
|
139
|
+
color: "#123abc"
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
assert.equal(state.updatePayload.name, "New name");
|
|
143
|
+
assert.equal(state.updatePayload.avatar_url, "https://example.com/avatar.png");
|
|
144
|
+
assert.equal(state.updatePayload.color, "#123ABC");
|
|
145
|
+
assert.equal(updated.name, "New name");
|
|
146
|
+
assert.equal(updated.avatarUrl, "https://example.com/avatar.png");
|
|
147
|
+
assert.equal(updated.color, "#123ABC");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("workspaceSettingsRepository can be constructed without validating app config shape", () => {
|
|
151
|
+
const { knexStub } = createKnexStub();
|
|
152
|
+
|
|
153
|
+
const repository = createRepository(knexStub);
|
|
154
|
+
|
|
155
|
+
assert.ok(repository);
|
|
156
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
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 { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
6
|
+
import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
|
|
7
|
+
|
|
8
|
+
function createRoleCatalog() {
|
|
9
|
+
return createWorkspaceRoleCatalog({
|
|
10
|
+
workspaceRoles: {
|
|
11
|
+
defaultInviteRole: "member",
|
|
12
|
+
roles: {
|
|
13
|
+
owner: {
|
|
14
|
+
assignable: false,
|
|
15
|
+
permissions: ["*"]
|
|
16
|
+
},
|
|
17
|
+
admin: {
|
|
18
|
+
assignable: true,
|
|
19
|
+
permissions: [
|
|
20
|
+
"workspace.roles.view",
|
|
21
|
+
"workspace.settings.view",
|
|
22
|
+
"workspace.settings.update",
|
|
23
|
+
"workspace.members.view",
|
|
24
|
+
"workspace.members.invite",
|
|
25
|
+
"workspace.members.manage",
|
|
26
|
+
"workspace.invites.revoke"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
member: {
|
|
30
|
+
assignable: true,
|
|
31
|
+
permissions: ["workspace.settings.view"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseBody(operation, payload = {}) {
|
|
39
|
+
return validateOperationSection({
|
|
40
|
+
operation,
|
|
41
|
+
section: "bodyValidator",
|
|
42
|
+
value: payload
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test("workspace settings patch body normalizes valid payload before validation", () => {
|
|
47
|
+
const parsed = parseBody(workspaceSettingsResource.operations.patch, {
|
|
48
|
+
name: " Team Mercury ",
|
|
49
|
+
avatarUrl: "https://example.com/avatar.png",
|
|
50
|
+
color: "#0f6b54",
|
|
51
|
+
invitesEnabled: false
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
assert.equal(parsed.ok, true);
|
|
55
|
+
assert.deepEqual(parsed.fieldErrors, {});
|
|
56
|
+
assert.deepEqual(parsed.value, {
|
|
57
|
+
name: "Team Mercury",
|
|
58
|
+
avatarUrl: "https://example.com/avatar.png",
|
|
59
|
+
color: "#0F6B54",
|
|
60
|
+
invitesEnabled: false
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("workspace settings patch body validates avatar URL protocol", () => {
|
|
65
|
+
const parsed = parseBody(workspaceSettingsResource.operations.patch, {
|
|
66
|
+
avatarUrl: "ftp://example.com/avatar.png"
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.equal(parsed.ok, false);
|
|
70
|
+
assert.equal(
|
|
71
|
+
parsed.fieldErrors.avatarUrl,
|
|
72
|
+
"Workspace avatar URL must be a valid absolute URL (http:// or https://)."
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("workspace settings patch body keeps max-length name rule", () => {
|
|
77
|
+
const parsed = parseBody(workspaceSettingsResource.operations.patch, {
|
|
78
|
+
name: "x".repeat(161)
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
assert.equal(parsed.ok, false);
|
|
82
|
+
assert.equal(parsed.fieldErrors.name, "Workspace name must be at most 160 characters.");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("workspace settings create body requires full-write fields", () => {
|
|
86
|
+
const parsed = parseBody(workspaceSettingsResource.operations.create, {
|
|
87
|
+
name: "Mercury Workspace"
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
assert.equal(parsed.ok, false);
|
|
91
|
+
assert.equal(parsed.fieldErrors.color, "Workspace color is required.");
|
|
92
|
+
assert.equal(parsed.fieldErrors.invitesEnabled, "invitesEnabled is required.");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("workspace settings output normalizes raw service payloads", () => {
|
|
96
|
+
const normalized = workspaceSettingsResource.operations.view.outputValidator.normalize({
|
|
97
|
+
workspace: {
|
|
98
|
+
id: "7",
|
|
99
|
+
slug: " mercury ",
|
|
100
|
+
ownerUserId: "9"
|
|
101
|
+
},
|
|
102
|
+
settings: {
|
|
103
|
+
name: " Mercury Workspace ",
|
|
104
|
+
avatarUrl: " https://example.com/avatar.png ",
|
|
105
|
+
color: "#0f6b54",
|
|
106
|
+
invitesEnabled: false
|
|
107
|
+
},
|
|
108
|
+
roleCatalog: createRoleCatalog()
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assert.deepEqual(normalized, {
|
|
112
|
+
workspace: {
|
|
113
|
+
id: 7,
|
|
114
|
+
slug: "mercury",
|
|
115
|
+
ownerUserId: 9
|
|
116
|
+
},
|
|
117
|
+
settings: {
|
|
118
|
+
name: "Mercury Workspace",
|
|
119
|
+
avatarUrl: "https://example.com/avatar.png",
|
|
120
|
+
color: "#0F6B54",
|
|
121
|
+
invitesEnabled: false,
|
|
122
|
+
invitesAvailable: true,
|
|
123
|
+
invitesEffective: false
|
|
124
|
+
},
|
|
125
|
+
roleCatalog: {
|
|
126
|
+
collaborationEnabled: true,
|
|
127
|
+
defaultInviteRole: "member",
|
|
128
|
+
roles: [
|
|
129
|
+
{
|
|
130
|
+
id: "owner",
|
|
131
|
+
assignable: false,
|
|
132
|
+
permissions: ["*"]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "admin",
|
|
136
|
+
assignable: true,
|
|
137
|
+
permissions: [
|
|
138
|
+
"workspace.roles.view",
|
|
139
|
+
"workspace.settings.view",
|
|
140
|
+
"workspace.settings.update",
|
|
141
|
+
"workspace.members.view",
|
|
142
|
+
"workspace.members.invite",
|
|
143
|
+
"workspace.members.manage",
|
|
144
|
+
"workspace.invites.revoke"
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "member",
|
|
149
|
+
assignable: true,
|
|
150
|
+
permissions: ["workspace.settings.view"]
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
assignableRoleIds: ["admin", "member"]
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import "../test-support/registerDefaultSettingsFields.js";
|
|
4
|
+
import { createService } from "../src/server/workspaceSettings/workspaceSettingsService.js";
|
|
5
|
+
|
|
6
|
+
function authorizedOptions(permissions = []) {
|
|
7
|
+
return {
|
|
8
|
+
context: {
|
|
9
|
+
actor: {
|
|
10
|
+
id: 1
|
|
11
|
+
},
|
|
12
|
+
permissions
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createFixture({ workspaceInvitationsEnabled = true } = {}) {
|
|
18
|
+
const state = {
|
|
19
|
+
settingsPatch: null,
|
|
20
|
+
workspace: {
|
|
21
|
+
id: 7,
|
|
22
|
+
slug: "tonymobily3",
|
|
23
|
+
name: "TonyMobily3",
|
|
24
|
+
ownerUserId: 9,
|
|
25
|
+
avatarUrl: "",
|
|
26
|
+
color: "#0F6B54"
|
|
27
|
+
},
|
|
28
|
+
settings: {
|
|
29
|
+
name: "TonyMobily3",
|
|
30
|
+
avatarUrl: "",
|
|
31
|
+
color: "#0F6B54",
|
|
32
|
+
invitesEnabled: true
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const service = createService({
|
|
37
|
+
workspaceInvitationsEnabled,
|
|
38
|
+
workspaceSettingsRepository: {
|
|
39
|
+
async ensureForWorkspaceId(workspaceId) {
|
|
40
|
+
assert.equal(Number(workspaceId), 7);
|
|
41
|
+
return { ...state.settings };
|
|
42
|
+
},
|
|
43
|
+
async updateSettingsByWorkspaceId(workspaceId, patch) {
|
|
44
|
+
assert.equal(Number(workspaceId), 7);
|
|
45
|
+
state.settingsPatch = { ...patch };
|
|
46
|
+
state.settings = {
|
|
47
|
+
...state.settings,
|
|
48
|
+
...patch
|
|
49
|
+
};
|
|
50
|
+
return state.settings;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return { service, state };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test("workspaceSettingsService.getWorkspaceSettings returns the stored invitesEnabled flag", async () => {
|
|
59
|
+
const { service, state } = createFixture();
|
|
60
|
+
|
|
61
|
+
const response = await service.getWorkspaceSettings(
|
|
62
|
+
state.workspace,
|
|
63
|
+
authorizedOptions(["workspace.settings.view"])
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(response.settings, {
|
|
67
|
+
name: "TonyMobily3",
|
|
68
|
+
avatarUrl: "",
|
|
69
|
+
color: "#0F6B54",
|
|
70
|
+
invitesEnabled: true,
|
|
71
|
+
invitesAvailable: true,
|
|
72
|
+
invitesEffective: true
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("workspaceSettingsService.updateWorkspaceSettings writes editable fields through workspaceSettingsRepository only", async () => {
|
|
77
|
+
const { service, state } = createFixture();
|
|
78
|
+
|
|
79
|
+
const response = await service.updateWorkspaceSettings(
|
|
80
|
+
state.workspace,
|
|
81
|
+
{
|
|
82
|
+
name: "New Name",
|
|
83
|
+
invitesEnabled: false
|
|
84
|
+
},
|
|
85
|
+
authorizedOptions(["workspace.settings.update"])
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
assert.deepEqual(state.settingsPatch, {
|
|
89
|
+
name: "New Name",
|
|
90
|
+
invitesEnabled: false
|
|
91
|
+
});
|
|
92
|
+
assert.deepEqual(response.settings, {
|
|
93
|
+
name: "New Name",
|
|
94
|
+
avatarUrl: "",
|
|
95
|
+
color: "#0F6B54",
|
|
96
|
+
invitesEnabled: false,
|
|
97
|
+
invitesAvailable: true,
|
|
98
|
+
invitesEffective: false
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("workspaceSettingsService disables invite settings in output when app policy disables invitations", async () => {
|
|
103
|
+
const { service, state } = createFixture({
|
|
104
|
+
workspaceInvitationsEnabled: false
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const response = await service.getWorkspaceSettings(
|
|
108
|
+
state.workspace,
|
|
109
|
+
authorizedOptions(["workspace.settings.view"])
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
assert.deepEqual(response.settings, {
|
|
113
|
+
name: "TonyMobily3",
|
|
114
|
+
avatarUrl: "",
|
|
115
|
+
color: "#0F6B54",
|
|
116
|
+
invitesEnabled: false,
|
|
117
|
+
invitesAvailable: false,
|
|
118
|
+
invitesEffective: false
|
|
119
|
+
});
|
|
120
|
+
});
|