uberf-12170: support merging person in addSocialIdToPerson (#9470)

This commit is contained in:
Alexey Zinoviev
2025-07-04 22:55:33 +05:00
committed by GitHub
parent ad61c7a87f
commit 8b5bfc35d1
3 changed files with 190 additions and 20 deletions
@@ -21,7 +21,15 @@
setCurrentAccount,
SocialId
} from '@hcengineering/core'
import { Button, Icon, Label, showPopup } from '@hcengineering/ui'
import {
Button,
getPlatformColorDef,
Icon,
Label,
PaletteColorIndexes,
showPopup,
themeStore
} from '@hcengineering/ui'
import contact, { SocialIdentityProvider, SocialIdentityRef } from '@hcengineering/contact'
import { getClient, MessageBox } from '@hcengineering/presentation'
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
@@ -121,13 +129,15 @@
<div class="flex-grow" />
{#if isLogin}
<div class="tag flex-center">
{@const color = getPlatformColorDef(PaletteColorIndexes.Turquoise, $themeStore.dark)}
<div class="tag flex-center" style:background={color.background} style:border-color={color.color}>
<Label label={setting.string.Login} />
</div>
{/if}
{#if isPrimary}
<div class="tag flex-center">
{@const color = getPlatformColorDef(PaletteColorIndexes.Ocean, $themeStore.dark)}
<div class="tag flex-center" style:background={color.background} style:border-color={color.color}>
<Label label={setting.string.Primary} />
</div>
{/if}
+140 -16
View File
@@ -1956,14 +1956,20 @@ describe('account utils', () => {
})
})
describe('addSocialId', () => {
describe('addSocialIdBase', () => {
const mockDb = {
person: {
findOne: jest.fn()
findOne: jest.fn(),
update: jest.fn()
},
socialId: {
findOne: jest.fn(),
insertOne: jest.fn()
find: jest.fn(),
insertOne: jest.fn(),
update: jest.fn()
},
account: {
findOne: jest.fn()
}
} as unknown as AccountDB
@@ -2033,22 +2039,140 @@ describe('account utils', () => {
)
})
test('should throw error if social id already exists', async () => {
const value = 'test@example.com'
const type = SocialIdType.EMAIL
describe('existing socialId cases', () => {
const person = 'test-person-uuid' as PersonUuid
const existingSocialId = {
type,
value,
personUuid: 'other-person-uuid' as PersonUuid
}
const otherPerson = 'other-person-uuid' as PersonUuid
const existingSocialIdId = 'existing-id' as PersonId
;(mockDb.person.findOne as jest.Mock).mockResolvedValue({})
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
beforeEach(() => {
;(mockDb.person.findOne as jest.Mock).mockResolvedValue({})
})
await expect(addSocialIdBase(mockDb, person, type, value, false)).rejects.toThrow(
new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdAlreadyExists, {}))
)
test('should throw error if socialId exists with different person and is verified', async () => {
const existingSocialId = {
_id: existingSocialIdId,
type: SocialIdType.EMAIL,
value: 'test@example.com',
personUuid: otherPerson,
verifiedOn: Date.now()
}
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
await expect(addSocialIdBase(mockDb, person, SocialIdType.EMAIL, 'test@example.com', true)).rejects.toThrow(
new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdAlreadyExists, {}))
)
})
test('should update personUuid if socialId exists with different person and is not verified', async () => {
const existingSocialId = {
_id: existingSocialIdId,
type: SocialIdType.EMAIL,
value: 'test@example.com',
personUuid: otherPerson,
verifiedOn: null
}
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
;(mockDb.account.findOne as jest.Mock).mockResolvedValue({ uuid: otherPerson })
const result = await addSocialIdBase(mockDb, person, SocialIdType.EMAIL, 'test@example.com', true)
expect(result).toBe(existingSocialIdId)
expect(mockDb.socialId.update).toHaveBeenCalledWith(
{ _id: existingSocialIdId },
{
personUuid: person,
verifiedOn: expect.any(Number)
}
)
})
test('should merge persons if socialId exists with different person, no account, and confirmed=true', async () => {
const existingSocialId = {
_id: existingSocialIdId,
type: SocialIdType.EMAIL,
value: 'test@example.com',
personUuid: otherPerson,
verifiedOn: null
}
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
;(mockDb.account.findOne as jest.Mock).mockResolvedValue(null)
;(mockDb.socialId.find as jest.Mock).mockResolvedValue([existingSocialId])
const result = await addSocialIdBase(mockDb, person, SocialIdType.EMAIL, 'test@example.com', true)
expect(result).toBe(existingSocialIdId)
expect(mockDb.socialId.update).toHaveBeenCalledWith(
{ _id: existingSocialIdId },
{ verifiedOn: expect.any(Number) }
)
expect(mockDb.socialId.update).toHaveBeenCalledWith(
{ _id: existingSocialIdId, personUuid: otherPerson },
{ personUuid: person }
)
expect(mockDb.person.update).toHaveBeenCalledWith({ uuid: otherPerson }, { migratedTo: person })
})
test('should update verifiedOn if socialId exists for same person and confirmed=true', async () => {
const existingSocialId = {
_id: existingSocialIdId,
type: SocialIdType.EMAIL,
value: 'test@example.com',
personUuid: person,
verifiedOn: null
}
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
const result = await addSocialIdBase(mockDb, person, SocialIdType.EMAIL, 'test@example.com', true)
expect(result).toBe(existingSocialIdId)
expect(mockDb.socialId.update).toHaveBeenCalledWith(
{ _id: existingSocialIdId },
{ verifiedOn: expect.any(Number) }
)
})
test('should update displayValue if different from existing', async () => {
const existingSocialId = {
_id: existingSocialIdId,
type: SocialIdType.EMAIL,
value: 'test@example.com',
personUuid: person,
displayValue: 'old display'
}
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
const result = await addSocialIdBase(
mockDb,
person,
SocialIdType.EMAIL,
'test@example.com',
false,
'new display'
)
expect(result).toBe(existingSocialIdId)
expect(mockDb.socialId.update).toHaveBeenCalledWith(
{ _id: existingSocialIdId },
{ displayValue: 'new display' }
)
})
test('should not update anything if no changes needed', async () => {
const existingSocialId = {
_id: existingSocialIdId,
type: SocialIdType.EMAIL,
value: 'test@example.com',
personUuid: person,
verifiedOn: Date.now(),
displayValue: 'display'
}
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(existingSocialId)
const result = await addSocialIdBase(mockDb, person, SocialIdType.EMAIL, 'test@example.com', false, 'display')
expect(result).toBe(existingSocialIdId)
expect(mockDb.socialId.update).not.toHaveBeenCalled()
})
})
test('should normalize value', async () => {
+37 -1
View File
@@ -52,6 +52,7 @@ import {
type Integration,
type LoginInfo,
type Meta,
type Operations,
type OtpInfo,
type RegionInfo,
type SocialId,
@@ -1513,7 +1514,42 @@ export async function addSocialIdBase (
const socialId = await db.socialId.findOne({ type, value: normalizedValue })
if (socialId != null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdAlreadyExists, {}))
const update: Operations<SocialId> = {}
let needUpdate = false
if (socialId.personUuid !== personUuid) {
if (socialId.verifiedOn != null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdAlreadyExists, {}))
} else {
// Was not verified.
const accountFromSocialId = await db.account.findOne({ uuid: socialId.personUuid as AccountUuid })
if (accountFromSocialId == null && confirmed) {
// If attached to a person w/o account and adding verifiedOn - merge this person into the current account.
await doMergePersons(db, personUuid, socialId.personUuid)
} else {
// Re-wire this social id into the current account
update.personUuid = personUuid
needUpdate = true
}
}
}
if (confirmed && socialId.verifiedOn == null) {
update.verifiedOn = Date.now()
needUpdate = true
}
if (displayValue !== socialId.displayValue) {
update.displayValue = displayValue
needUpdate = true
}
if (needUpdate) {
await db.socialId.update({ _id: socialId._id }, update)
}
return socialId._id
}
const newSocialId: Omit<SocialId, '_id' | 'key'> = {