mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 04:25:03 +02:00
Reuse default invite settings in link generation (#10651)
* Reuse default invite settings in link generation Signed-off-by: Artem Savchenko <armisav@gmail.com> * Clean up Signed-off-by: Artem Savchenko <armisav@gmail.com> * Capability check Signed-off-by: Artem Savchenko <armisav@gmail.com> * Fix warning Signed-off-by: Artem Savchenko <armisav@gmail.com> --------- Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
//
|
||||
// Copyright © 2026 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { AccountRole } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import type { InviteSettings } from '@hcengineering/setting'
|
||||
import {
|
||||
DEFAULT_INVITE_LINK_GENERATOR_ROLES,
|
||||
getDefaultInviterRoles,
|
||||
getDefaultInviteRole,
|
||||
INVITE_SETTINGS_DEFAULT_EXPIRATION_HOURS,
|
||||
INVITE_SETTINGS_DEFAULT_LIMIT,
|
||||
normalizeInviteRole,
|
||||
normalizeInviteRoles,
|
||||
resolveInviteSettings
|
||||
} from '../inviteSettingsUtils'
|
||||
|
||||
jest.mock('@hcengineering/platform', () => {
|
||||
const actual = jest.requireActual('@hcengineering/platform')
|
||||
return {
|
||||
...actual,
|
||||
getMetadata: jest.fn()
|
||||
}
|
||||
})
|
||||
|
||||
const mockGetMetadata = getMetadata as jest.MockedFunction<typeof getMetadata>
|
||||
|
||||
function inviteDoc (
|
||||
partial: Partial<InviteSettings> & Pick<InviteSettings, 'expirationTime' | 'emailMask' | 'limit'>
|
||||
): InviteSettings {
|
||||
return partial as InviteSettings
|
||||
}
|
||||
|
||||
describe('inviteSettingsUtils', () => {
|
||||
beforeEach(() => {
|
||||
mockGetMetadata.mockReturnValue(undefined)
|
||||
})
|
||||
|
||||
describe('normalizeInviteRole', () => {
|
||||
it('maps string role names case-insensitively', () => {
|
||||
expect(normalizeInviteRole('GUEST', AccountRole.User)).toBe(AccountRole.Guest)
|
||||
expect(normalizeInviteRole('User', AccountRole.Guest)).toBe(AccountRole.User)
|
||||
expect(normalizeInviteRole('MAINTAINER', AccountRole.Guest)).toBe(AccountRole.Maintainer)
|
||||
expect(normalizeInviteRole('owner', AccountRole.Guest)).toBe(AccountRole.Owner)
|
||||
})
|
||||
|
||||
it('returns valid AccountRole numbers as-is', () => {
|
||||
expect(normalizeInviteRole(AccountRole.Maintainer, AccountRole.Guest)).toBe(AccountRole.Maintainer)
|
||||
})
|
||||
|
||||
it('returns fallback for unknown string', () => {
|
||||
expect(normalizeInviteRole('admin', AccountRole.User)).toBe(AccountRole.User)
|
||||
})
|
||||
|
||||
it('returns fallback for undefined', () => {
|
||||
expect(normalizeInviteRole(undefined, AccountRole.Owner)).toBe(AccountRole.Owner)
|
||||
})
|
||||
|
||||
it('returns fallback for invalid number', () => {
|
||||
expect(normalizeInviteRole(999 as unknown as AccountRole, AccountRole.Guest)).toBe(AccountRole.Guest)
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeInviteRoles', () => {
|
||||
it('returns fallback copy when values missing or empty', () => {
|
||||
const fallback = [AccountRole.Guest, AccountRole.User]
|
||||
expect(normalizeInviteRoles(undefined, fallback)).toEqual(fallback)
|
||||
expect(normalizeInviteRoles([], fallback)).toEqual(fallback)
|
||||
expect(normalizeInviteRoles(undefined, fallback)).not.toBe(fallback)
|
||||
})
|
||||
|
||||
it('maps and deduplicates roles', () => {
|
||||
expect(normalizeInviteRoles(['user', 'USER', 'maintainer'], [AccountRole.Guest])).toEqual([
|
||||
AccountRole.User,
|
||||
AccountRole.Maintainer
|
||||
])
|
||||
})
|
||||
|
||||
it('maps unrecognized strings to User (inner fallback)', () => {
|
||||
expect(normalizeInviteRoles(['nope', 'x'], [AccountRole.Guest])).toEqual([AccountRole.User])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDefaultInviteRole / getDefaultInviterRoles', () => {
|
||||
it('uses User and default generator list when metadata unset', () => {
|
||||
mockGetMetadata.mockReturnValue(undefined)
|
||||
expect(getDefaultInviteRole()).toBe(AccountRole.User)
|
||||
expect(getDefaultInviterRoles()).toEqual(DEFAULT_INVITE_LINK_GENERATOR_ROLES)
|
||||
})
|
||||
|
||||
it('reads default invite role from metadata string', () => {
|
||||
mockGetMetadata.mockReturnValue('maintainer')
|
||||
expect(getDefaultInviteRole()).toBe(AccountRole.Maintainer)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveInviteSettings', () => {
|
||||
it('returns defaults when doc is undefined', () => {
|
||||
mockGetMetadata.mockReturnValue(undefined)
|
||||
const r = resolveInviteSettings(undefined)
|
||||
expect(r).toEqual({
|
||||
expirationTime: INVITE_SETTINGS_DEFAULT_EXPIRATION_HOURS,
|
||||
emailMask: '',
|
||||
limit: INVITE_SETTINGS_DEFAULT_LIMIT,
|
||||
defaultInviteRole: AccountRole.User,
|
||||
inviteLinkGeneratorRoles: DEFAULT_INVITE_LINK_GENERATOR_ROLES,
|
||||
noLimit: true
|
||||
})
|
||||
})
|
||||
|
||||
it('uses doc fields and noLimit when limit is not -1', () => {
|
||||
const doc = inviteDoc({
|
||||
expirationTime: 12,
|
||||
emailMask: '*@corp.test',
|
||||
limit: 100,
|
||||
defaultInviteRole: AccountRole.Guest,
|
||||
inviteLinkGeneratorRoles: [AccountRole.Owner]
|
||||
})
|
||||
const r = resolveInviteSettings(doc)
|
||||
expect(r.expirationTime).toBe(12)
|
||||
expect(r.emailMask).toBe('*@corp.test')
|
||||
expect(r.limit).toBe(100)
|
||||
expect(r.defaultInviteRole).toBe(AccountRole.Guest)
|
||||
expect(r.inviteLinkGeneratorRoles).toEqual([AccountRole.Owner])
|
||||
expect(r.noLimit).toBe(false)
|
||||
})
|
||||
|
||||
it('sets noLimit true when doc.limit is -1', () => {
|
||||
const doc = inviteDoc({
|
||||
expirationTime: 48,
|
||||
emailMask: '',
|
||||
limit: -1,
|
||||
defaultInviteRole: AccountRole.User,
|
||||
inviteLinkGeneratorRoles: [AccountRole.User]
|
||||
})
|
||||
expect(resolveInviteSettings(doc).noLimit).toBe(true)
|
||||
})
|
||||
|
||||
it('uses DEFAULT_INVITE_LINK_GENERATOR_ROLES copy when doc list empty', () => {
|
||||
const doc = inviteDoc({
|
||||
expirationTime: 48,
|
||||
emailMask: '',
|
||||
limit: -1,
|
||||
defaultInviteRole: AccountRole.User,
|
||||
inviteLinkGeneratorRoles: []
|
||||
})
|
||||
const r = resolveInviteSettings(doc)
|
||||
expect(r.inviteLinkGeneratorRoles).toEqual(DEFAULT_INVITE_LINK_GENERATOR_ROLES)
|
||||
expect(r.inviteLinkGeneratorRoles).not.toBe(DEFAULT_INVITE_LINK_GENERATOR_ROLES)
|
||||
})
|
||||
|
||||
it('normalizes string defaultInviteRole using metadata fallback', () => {
|
||||
mockGetMetadata.mockReturnValue('user')
|
||||
const doc = inviteDoc({
|
||||
expirationTime: 1,
|
||||
emailMask: '',
|
||||
limit: -1,
|
||||
defaultInviteRole: 'guest' as unknown as AccountRole,
|
||||
inviteLinkGeneratorRoles: [AccountRole.User]
|
||||
})
|
||||
expect(resolveInviteSettings(doc).defaultInviteRole).toBe(AccountRole.Guest)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright © 2026 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { AccountRole, type Account } from '@hcengineering/core'
|
||||
import { RoleCapability } from '@hcengineering/setting'
|
||||
import { DEFAULT_INVITE_LINK_GENERATOR_ROLES } from '../inviteSettingsUtils'
|
||||
import { getRolesForCapability, hasRoleCapability } from '../roleCapability'
|
||||
|
||||
function account (role: AccountRole): Account {
|
||||
return { role } as any
|
||||
}
|
||||
|
||||
describe('roleCapability (Generate invite link permission)', () => {
|
||||
describe('getRolesForCapability', () => {
|
||||
it('uses RoleCapabilitySettings when set', () => {
|
||||
expect(
|
||||
getRolesForCapability(RoleCapability.GenerateInviteLink, {
|
||||
[RoleCapability.GenerateInviteLink]: [AccountRole.Owner]
|
||||
})
|
||||
).toEqual([AccountRole.Owner])
|
||||
})
|
||||
|
||||
it('uses inviteLinkGeneratorRoles when capability map missing', () => {
|
||||
expect(
|
||||
getRolesForCapability(RoleCapability.GenerateInviteLink, undefined, [AccountRole.Guest, AccountRole.User])
|
||||
).toEqual([AccountRole.Guest, AccountRole.User])
|
||||
})
|
||||
|
||||
it('falls back to DEFAULT_INVITE_LINK_GENERATOR_ROLES (shared with invite settings)', () => {
|
||||
expect(getRolesForCapability(RoleCapability.GenerateInviteLink, undefined, undefined)).toBe(
|
||||
DEFAULT_INVITE_LINK_GENERATOR_ROLES
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasRoleCapability', () => {
|
||||
it('allows User when falling back to default generator roles', () => {
|
||||
expect(
|
||||
hasRoleCapability(account(AccountRole.User), RoleCapability.GenerateInviteLink, undefined, undefined)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('denies User when only Owner may generate', () => {
|
||||
expect(
|
||||
hasRoleCapability(account(AccountRole.User), RoleCapability.GenerateInviteLink, undefined, [AccountRole.Owner])
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('allows Owner when only Owner may generate', () => {
|
||||
expect(
|
||||
hasRoleCapability(account(AccountRole.Owner), RoleCapability.GenerateInviteLink, undefined, [AccountRole.Owner])
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user