Files
huly-platform/services/gmail/pod-gmail/src/__tests__/syncState.test.ts
T
Artyom SavchenkoandGitHub a98b83d1e4 UBERF-10408: New gmail integration (#8869)
* UBERF-10375: Fix full message sync

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10375: Clean up

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Init new gmail integration

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Rename package

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Add mail channel and thread master tags

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Rename

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Create messages in cards

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Use new messages

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Use mail tags and add mail-common

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Use tags for mails

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Clean up tests

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Fix tests

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Clean up

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Clean up

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Apply client fixes

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Fix communication version

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Use adapter for message manager

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Remove gmail-next and improve performance

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Fix dep mismatch

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Add prefix for new history

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Fix failed tests

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-10408: Fix composite emails and add channel cache

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
2025-05-12 11:26:46 +07:00

254 lines
8.8 KiB
TypeScript

//
// Copyright © 2025 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 { PersonId } from '@hcengineering/core'
import { KeyValueClient } from '@hcengineering/kvs-client'
import { SyncStateManager } from '../message/syncState'
import { IntegrationVersion } from '../types'
import { History } from '../message/types'
describe('SyncStateManager', () => {
const workspace = 'test-workspace'
const userId = 'test-user-id' as PersonId
const historyId = 'test-history-id'
const pageToken = 'test-page-token'
let mockKeyValueClient: jest.Mocked<KeyValueClient>
let v1StateManager: SyncStateManager
let v2StateManager: SyncStateManager
beforeEach(() => {
// Reset mocks before each test
mockKeyValueClient = {
getValue: jest.fn(),
setValue: jest.fn().mockResolvedValue(undefined),
deleteKey: jest.fn().mockResolvedValue(undefined)
} as unknown as jest.Mocked<KeyValueClient>
// Create state managers for both versions
v1StateManager = new SyncStateManager(mockKeyValueClient, workspace, IntegrationVersion.V1)
v2StateManager = new SyncStateManager(mockKeyValueClient, workspace, IntegrationVersion.V2)
})
describe('getHistory', () => {
it('should call getValue with correct key for V1', async () => {
const expectedHistory: History = {
historyId,
userId,
workspace
}
mockKeyValueClient.getValue.mockResolvedValue(expectedHistory)
const result = await v1StateManager.getHistory(userId)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`history:${workspace}:${userId}`)
expect(result).toEqual(expectedHistory)
})
it('should call getValue with correct key for V2', async () => {
const expectedHistory: History = {
historyId,
userId,
workspace
}
mockKeyValueClient.getValue.mockResolvedValue(expectedHistory)
const result = await v2StateManager.getHistory(userId)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`history-v2:${workspace}:${userId}`)
expect(result).toEqual(expectedHistory)
})
it('should return null when no history exists', async () => {
mockKeyValueClient.getValue.mockResolvedValue(null)
const result = await v1StateManager.getHistory(userId)
expect(result).toBeNull()
})
it('should propagate errors from KeyValueClient', async () => {
const error = new Error('Database error')
mockKeyValueClient.getValue.mockRejectedValue(error)
await expect(v1StateManager.getHistory(userId)).rejects.toThrow(error)
})
})
describe('clearHistory', () => {
it('should call deleteKey with correct key for V1', async () => {
await v1StateManager.clearHistory(userId)
expect(mockKeyValueClient.deleteKey).toHaveBeenCalledWith(`history:${workspace}:${userId}`)
})
it('should call deleteKey with correct key for V2', async () => {
await v2StateManager.clearHistory(userId)
expect(mockKeyValueClient.deleteKey).toHaveBeenCalledWith(`history-v2:${workspace}:${userId}`)
})
it('should propagate errors from KeyValueClient', async () => {
const error = new Error('Database error')
mockKeyValueClient.deleteKey.mockRejectedValue(error)
await expect(v1StateManager.clearHistory(userId)).rejects.toThrow(error)
})
})
describe('setHistoryId', () => {
it('should call setValue with correct key and value for V1', async () => {
await v1StateManager.setHistoryId(userId, historyId)
expect(mockKeyValueClient.setValue).toHaveBeenCalledWith(`history:${workspace}:${userId}`, {
historyId,
userId,
workspace
})
})
it('should call setValue with correct key and value for V2', async () => {
await v2StateManager.setHistoryId(userId, historyId)
expect(mockKeyValueClient.setValue).toHaveBeenCalledWith(`history-v2:${workspace}:${userId}`, {
historyId,
userId,
workspace
})
})
it('should propagate errors from KeyValueClient', async () => {
const error = new Error('Database error')
mockKeyValueClient.setValue.mockRejectedValue(error)
await expect(v1StateManager.setHistoryId(userId, historyId)).rejects.toThrow(error)
})
})
describe('getPageToken', () => {
it('should call getValue with correct key for V1', async () => {
mockKeyValueClient.getValue.mockResolvedValue(pageToken)
const result = await v1StateManager.getPageToken(userId)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`page-token:${workspace}:${userId}`)
expect(result).toEqual(pageToken)
})
it('should call getValue with correct key for V2', async () => {
mockKeyValueClient.getValue.mockResolvedValue(pageToken)
const result = await v2StateManager.getPageToken(userId)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`page-token-v2:${workspace}:${userId}`)
expect(result).toEqual(pageToken)
})
it('should return null when no page token exists', async () => {
mockKeyValueClient.getValue.mockResolvedValue(null)
const result = await v1StateManager.getPageToken(userId)
expect(result).toBeNull()
})
it('should propagate errors from KeyValueClient', async () => {
const error = new Error('Database error')
mockKeyValueClient.getValue.mockRejectedValue(error)
await expect(v1StateManager.getPageToken(userId)).rejects.toThrow(error)
})
})
describe('setPageToken', () => {
it('should call setValue with correct key and value for V1', async () => {
await v1StateManager.setPageToken(userId, pageToken)
expect(mockKeyValueClient.setValue).toHaveBeenCalledWith(`page-token:${workspace}:${userId}`, pageToken)
})
it('should call setValue with correct key and value for V2', async () => {
await v2StateManager.setPageToken(userId, pageToken)
expect(mockKeyValueClient.setValue).toHaveBeenCalledWith(`page-token-v2:${workspace}:${userId}`, pageToken)
})
it('should propagate errors from KeyValueClient', async () => {
const error = new Error('Database error')
mockKeyValueClient.setValue.mockRejectedValue(error)
await expect(v1StateManager.setPageToken(userId, pageToken)).rejects.toThrow(error)
})
})
describe('key generation', () => {
it('should generate different keys for V1 and V2', async () => {
// Test through the public methods to verify the keys are different
mockKeyValueClient.getValue.mockResolvedValue(null)
await v1StateManager.getHistory(userId)
await v2StateManager.getHistory(userId)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`history:${workspace}:${userId}`)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`history-v2:${workspace}:${userId}`)
mockKeyValueClient.getValue.mockClear()
await v1StateManager.getPageToken(userId)
await v2StateManager.getPageToken(userId)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`page-token:${workspace}:${userId}`)
expect(mockKeyValueClient.getValue).toHaveBeenCalledWith(`page-token-v2:${workspace}:${userId}`)
})
})
describe('version migration scenario', () => {
it('should allow migrating from V1 to V2', async () => {
// Simulate V1 data existing
const v1History: History = { historyId: 'v1-history', userId, workspace }
mockKeyValueClient.getValue.mockImplementation((key: string) => {
if (key === `history:${workspace}:${userId}`) {
return Promise.resolve(v1History)
}
if (key === `history-v2:${workspace}:${userId}`) {
return Promise.resolve(null)
}
return Promise.resolve(null)
})
// V1 manager should find history
const historyFromV1 = await v1StateManager.getHistory(userId)
expect(historyFromV1).toEqual(v1History)
// V2 manager should not find history yet
const historyFromV2 = await v2StateManager.getHistory(userId)
expect(historyFromV2).toBeNull()
// Migrate by setting V2 history
await v2StateManager.setHistoryId(userId, 'v2-history')
// Verify the call to set V2 history
expect(mockKeyValueClient.setValue).toHaveBeenCalledWith(`history-v2:${workspace}:${userId}`, {
historyId: 'v2-history',
userId,
workspace
})
})
})
})