@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,90 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import "../test-support/registerDefaultSettingsFields.js";
|
|
7
|
+
import { workspaceMembersResource } from "../src/shared/resources/workspaceMembersResource.js";
|
|
8
|
+
import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
|
|
9
|
+
import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
10
|
+
import { userProfileResource } from "../src/shared/resources/userProfileResource.js";
|
|
11
|
+
import { userSettingsResource } from "../src/shared/resources/userSettingsResource.js";
|
|
12
|
+
import { consoleSettingsResource } from "../src/shared/resources/consoleSettingsResource.js";
|
|
13
|
+
|
|
14
|
+
function assertResourceOperationMessages(resource, operationName, label) {
|
|
15
|
+
const operation = resource?.operations?.[operationName];
|
|
16
|
+
assert.equal(typeof operation, "object", `${label}.operations.${operationName} must exist.`);
|
|
17
|
+
|
|
18
|
+
const operationMessages = operation?.messages;
|
|
19
|
+
const resourceMessages = resource?.messages || resource?.operationMessages;
|
|
20
|
+
const resolvedMessages =
|
|
21
|
+
operationMessages && typeof operationMessages === "object"
|
|
22
|
+
? operationMessages
|
|
23
|
+
: resourceMessages;
|
|
24
|
+
|
|
25
|
+
assert.equal(
|
|
26
|
+
typeof resolvedMessages,
|
|
27
|
+
"object",
|
|
28
|
+
`${label}.operations.${operationName} must resolve operation messages from operation.messages or resource.messages.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test("users-core resources expose messages for all operations", () => {
|
|
33
|
+
const resources = {
|
|
34
|
+
workspace: workspaceResource,
|
|
35
|
+
workspaceSettings: workspaceSettingsResource,
|
|
36
|
+
userProfile: userProfileResource,
|
|
37
|
+
userSettings: userSettingsResource,
|
|
38
|
+
consoleSettings: consoleSettingsResource
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (const [label, resource] of Object.entries(resources)) {
|
|
42
|
+
for (const operationName of ["view", "list", "create", "replace", "patch"]) {
|
|
43
|
+
assertResourceOperationMessages(resource, operationName, label);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("users-core specialized resource operations expose messages and validators", () => {
|
|
49
|
+
const workspaceMembersOperationSpecs = [
|
|
50
|
+
{ label: "workspaceMembers.rolesList", operation: workspaceMembersResource.operations.rolesList },
|
|
51
|
+
{ label: "workspaceMembers.membersList", operation: workspaceMembersResource.operations.membersList },
|
|
52
|
+
{ label: "workspaceMembers.updateMemberRole", operation: workspaceMembersResource.operations.updateMemberRole },
|
|
53
|
+
{ label: "workspaceMembers.removeMember", operation: workspaceMembersResource.operations.removeMember },
|
|
54
|
+
{ label: "workspaceMembers.invitesList", operation: workspaceMembersResource.operations.invitesList },
|
|
55
|
+
{ label: "workspaceMembers.createInvite", operation: workspaceMembersResource.operations.createInvite },
|
|
56
|
+
{ label: "workspaceMembers.revokeInvite", operation: workspaceMembersResource.operations.revokeInvite },
|
|
57
|
+
{ label: "workspaceMembers.redeemInvite", operation: workspaceMembersResource.operations.redeemInvite }
|
|
58
|
+
];
|
|
59
|
+
const operationSpecs = [
|
|
60
|
+
...workspaceMembersOperationSpecs,
|
|
61
|
+
{ label: "userProfile.avatarUpload", operation: userProfileResource.operations.avatarUpload },
|
|
62
|
+
{ label: "userProfile.avatarDelete", operation: userProfileResource.operations.avatarDelete },
|
|
63
|
+
{ label: "userSettings.passwordChange", operation: userSettingsResource.operations.passwordChange },
|
|
64
|
+
{ label: "userSettings.passwordMethodToggle", operation: userSettingsResource.operations.passwordMethodToggle },
|
|
65
|
+
{ label: "userSettings.oauthLinkStart", operation: userSettingsResource.operations.oauthLinkStart },
|
|
66
|
+
{ label: "userSettings.oauthUnlink", operation: userSettingsResource.operations.oauthUnlink },
|
|
67
|
+
{ label: "userSettings.logoutOtherSessions", operation: userSettingsResource.operations.logoutOtherSessions }
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const { label, operation } of operationSpecs) {
|
|
71
|
+
assert.equal(typeof operation?.messages, "object", `${label}.messages must be an object.`);
|
|
72
|
+
assert.equal(typeof operation?.outputValidator?.schema, "object", `${label}.outputValidator.schema must exist.`);
|
|
73
|
+
if (operation?.bodyValidator) {
|
|
74
|
+
assert.equal(typeof operation.bodyValidator.schema, "object", `${label}.bodyValidator.schema must exist.`);
|
|
75
|
+
}
|
|
76
|
+
if (operation?.paramsValidator) {
|
|
77
|
+
assert.equal(typeof operation.paramsValidator.schema, "object", `${label}.paramsValidator.schema must exist.`);
|
|
78
|
+
}
|
|
79
|
+
if (operation?.queryValidator) {
|
|
80
|
+
assert.equal(typeof operation.queryValidator.schema, "object", `${label}.queryValidator.schema must exist.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("users-core no longer contains legacy shared/schema directory", () => {
|
|
86
|
+
const testFilePath = fileURLToPath(import.meta.url);
|
|
87
|
+
const packageRoot = path.resolve(path.dirname(testFilePath), "..");
|
|
88
|
+
const legacySchemaDir = path.join(packageRoot, "src", "shared", "schema");
|
|
89
|
+
assert.equal(existsSync(legacySchemaDir), false, "src/shared/schema must not exist.");
|
|
90
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
createWorkspaceRoleCatalog,
|
|
5
|
+
cloneWorkspaceRoleCatalog,
|
|
6
|
+
resolveRolePermissions,
|
|
7
|
+
hasPermission
|
|
8
|
+
} from "../src/shared/roles.js";
|
|
9
|
+
|
|
10
|
+
test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.workspaceRoles", () => {
|
|
11
|
+
const emptyCatalog = createWorkspaceRoleCatalog();
|
|
12
|
+
assert.deepEqual(emptyCatalog.roles, []);
|
|
13
|
+
assert.deepEqual(emptyCatalog.assignableRoleIds, []);
|
|
14
|
+
assert.equal(emptyCatalog.defaultInviteRole, "");
|
|
15
|
+
assert.equal(emptyCatalog.collaborationEnabled, false);
|
|
16
|
+
|
|
17
|
+
const appConfig = {
|
|
18
|
+
workspaceRoles: {
|
|
19
|
+
defaultInviteRole: "editor",
|
|
20
|
+
roles: {
|
|
21
|
+
owner: {
|
|
22
|
+
assignable: false,
|
|
23
|
+
permissions: ["workspace.settings.update"]
|
|
24
|
+
},
|
|
25
|
+
editor: {
|
|
26
|
+
assignable: true,
|
|
27
|
+
permissions: ["crud_contacts.*"]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
33
|
+
const editorRole = roleCatalog.roles.find((role) => role.id === "editor");
|
|
34
|
+
|
|
35
|
+
assert.equal(roleCatalog.defaultInviteRole, "editor");
|
|
36
|
+
assert.equal(roleCatalog.assignableRoleIds.includes("editor"), true);
|
|
37
|
+
assert.deepEqual(resolveRolePermissions("owner", appConfig), ["workspace.settings.update"]);
|
|
38
|
+
assert.equal(hasPermission(editorRole?.permissions, "crud_contacts.update"), true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("cloneWorkspaceRoleCatalog normalizes role ids and returns detached arrays", () => {
|
|
42
|
+
const source = {
|
|
43
|
+
collaborationEnabled: true,
|
|
44
|
+
defaultInviteRole: "member",
|
|
45
|
+
roles: [
|
|
46
|
+
{
|
|
47
|
+
id: " MEMBER ",
|
|
48
|
+
assignable: true,
|
|
49
|
+
permissions: ["workspace.members.view"]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
assignableRoleIds: ["member"]
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const cloned = cloneWorkspaceRoleCatalog(source);
|
|
56
|
+
assert.deepEqual(cloned, {
|
|
57
|
+
collaborationEnabled: true,
|
|
58
|
+
defaultInviteRole: "member",
|
|
59
|
+
roles: [
|
|
60
|
+
{
|
|
61
|
+
id: "member",
|
|
62
|
+
assignable: true,
|
|
63
|
+
permissions: ["workspace.members.view"]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
assignableRoleIds: ["member"]
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
cloned.roles[0].permissions.push("workspace.members.manage");
|
|
70
|
+
cloned.assignableRoleIds.push("admin");
|
|
71
|
+
|
|
72
|
+
assert.deepEqual(source.roles[0].permissions, ["workspace.members.view"]);
|
|
73
|
+
assert.deepEqual(source.assignableRoleIds, ["member"]);
|
|
74
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
async function importWithIdentity(url, identity) {
|
|
5
|
+
return import(`${url.href}?identity=${identity}`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
test("settings field registries stay shared across module identities", async () => {
|
|
9
|
+
const workspaceModuleUrl = new URL("../src/shared/resources/workspaceSettingsFields.js", import.meta.url);
|
|
10
|
+
const userModuleUrl = new URL("../src/shared/resources/userSettingsFields.js", import.meta.url);
|
|
11
|
+
const consoleModuleUrl = new URL("../src/shared/resources/consoleSettingsFields.js", import.meta.url);
|
|
12
|
+
|
|
13
|
+
const workspaceA = await importWithIdentity(workspaceModuleUrl, "workspace-a");
|
|
14
|
+
const workspaceB = await importWithIdentity(workspaceModuleUrl, "workspace-b");
|
|
15
|
+
assert.equal(workspaceA.workspaceSettingsFields, workspaceB.workspaceSettingsFields);
|
|
16
|
+
|
|
17
|
+
const userA = await importWithIdentity(userModuleUrl, "user-a");
|
|
18
|
+
const userB = await importWithIdentity(userModuleUrl, "user-b");
|
|
19
|
+
assert.equal(userA.userSettingsFields, userB.userSettingsFields);
|
|
20
|
+
|
|
21
|
+
const consoleA = await importWithIdentity(consoleModuleUrl, "console-a");
|
|
22
|
+
const consoleB = await importWithIdentity(consoleModuleUrl, "console-b");
|
|
23
|
+
assert.equal(consoleA.consoleSettingsFields, consoleB.consoleSettingsFields);
|
|
24
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
TENANCY_MODE_NONE,
|
|
5
|
+
TENANCY_MODE_PERSONAL,
|
|
6
|
+
TENANCY_MODE_WORKSPACE,
|
|
7
|
+
WORKSPACE_SLUG_POLICY_NONE,
|
|
8
|
+
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
9
|
+
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
10
|
+
resolveTenancyProfile,
|
|
11
|
+
isWorkspaceTenancyMode
|
|
12
|
+
} from "../src/shared/tenancyProfile.js";
|
|
13
|
+
|
|
14
|
+
test("resolveTenancyProfile returns mode-specific workspace policy matrix", () => {
|
|
15
|
+
const noneProfile = resolveTenancyProfile({ tenancyMode: TENANCY_MODE_NONE });
|
|
16
|
+
assert.deepEqual(noneProfile, {
|
|
17
|
+
mode: TENANCY_MODE_NONE,
|
|
18
|
+
workspace: {
|
|
19
|
+
enabled: false,
|
|
20
|
+
autoProvision: false,
|
|
21
|
+
allowSelfCreate: false,
|
|
22
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_NONE
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const personalProfile = resolveTenancyProfile({ tenancyMode: TENANCY_MODE_PERSONAL });
|
|
27
|
+
assert.deepEqual(personalProfile, {
|
|
28
|
+
mode: TENANCY_MODE_PERSONAL,
|
|
29
|
+
workspace: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
autoProvision: true,
|
|
32
|
+
allowSelfCreate: false,
|
|
33
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const workspaceProfile = resolveTenancyProfile({ tenancyMode: TENANCY_MODE_WORKSPACE });
|
|
38
|
+
assert.deepEqual(workspaceProfile, {
|
|
39
|
+
mode: TENANCY_MODE_WORKSPACE,
|
|
40
|
+
workspace: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
autoProvision: false,
|
|
43
|
+
allowSelfCreate: false,
|
|
44
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_USER_SELECTED
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("isWorkspaceTenancyMode is true only for workspace mode", () => {
|
|
50
|
+
assert.equal(isWorkspaceTenancyMode(TENANCY_MODE_WORKSPACE), true);
|
|
51
|
+
assert.equal(isWorkspaceTenancyMode(TENANCY_MODE_PERSONAL), false);
|
|
52
|
+
assert.equal(isWorkspaceTenancyMode(TENANCY_MODE_NONE), false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("resolveTenancyProfile allows explicit workspace self-create policy override", () => {
|
|
56
|
+
const workspaceProfile = resolveTenancyProfile({
|
|
57
|
+
tenancyMode: TENANCY_MODE_WORKSPACE,
|
|
58
|
+
tenancyPolicy: {
|
|
59
|
+
workspace: {
|
|
60
|
+
allowSelfCreate: true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
assert.equal(workspaceProfile.mode, TENANCY_MODE_WORKSPACE);
|
|
66
|
+
assert.equal(workspaceProfile.workspace.allowSelfCreate, true);
|
|
67
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { userSettingsResource } from "../src/shared/resources/userSettingsResource.js";
|
|
6
|
+
|
|
7
|
+
function parseBody(operation, payload = {}) {
|
|
8
|
+
return validateOperationSection({
|
|
9
|
+
operation,
|
|
10
|
+
section: "bodyValidator",
|
|
11
|
+
value: payload
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("user settings preferences update keeps required string validation after normalization", () => {
|
|
16
|
+
const parsed = parseBody(userSettingsResource.operations.preferencesUpdate, {
|
|
17
|
+
theme: " "
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
assert.equal(parsed.ok, false);
|
|
21
|
+
assert.equal(typeof parsed.fieldErrors.theme, "string");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("user settings notifications update rejects non-boolean values", () => {
|
|
25
|
+
const parsed = parseBody(userSettingsResource.operations.notificationsUpdate, {
|
|
26
|
+
productUpdates: "yes"
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
assert.equal(parsed.ok, false);
|
|
30
|
+
assert.equal(typeof parsed.fieldErrors.productUpdates, "string");
|
|
31
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
normalizeSurfaceWorkspaceRequirement,
|
|
5
|
+
resolveApiBasePath
|
|
6
|
+
} from "../src/shared/support/usersApiPaths.js";
|
|
7
|
+
|
|
8
|
+
test("normalizeSurfaceWorkspaceRequirement only accepts explicit true", () => {
|
|
9
|
+
assert.equal(normalizeSurfaceWorkspaceRequirement(true), true);
|
|
10
|
+
assert.equal(normalizeSurfaceWorkspaceRequirement(false), false);
|
|
11
|
+
assert.equal(normalizeSurfaceWorkspaceRequirement("true"), false);
|
|
12
|
+
assert.equal(normalizeSurfaceWorkspaceRequirement(1), false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("resolveApiBasePath resolves workspace and non-workspace API base paths", () => {
|
|
16
|
+
assert.equal(
|
|
17
|
+
resolveApiBasePath({
|
|
18
|
+
surfaceRequiresWorkspace: true,
|
|
19
|
+
relativePath: "/customers"
|
|
20
|
+
}),
|
|
21
|
+
"/api/w/:workspaceSlug/workspace/customers"
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
assert.equal(
|
|
25
|
+
resolveApiBasePath({
|
|
26
|
+
surfaceRequiresWorkspace: false,
|
|
27
|
+
relativePath: "/customers"
|
|
28
|
+
}),
|
|
29
|
+
"/api/customers"
|
|
30
|
+
);
|
|
31
|
+
});
|