mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
UBERF-13433: Add tests
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Generated
+14
-10
@@ -591,7 +591,7 @@ importers:
|
||||
version: file:projects/model-card.tgz
|
||||
'@rush-temp/model-chat':
|
||||
specifier: file:./projects/model-chat.tgz
|
||||
version: file:projects/model-chat.tgz
|
||||
version: file:projects/model-chat.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3))
|
||||
'@rush-temp/model-chunter':
|
||||
specifier: file:./projects/model-chunter.tgz
|
||||
version: file:projects/model-chunter.tgz
|
||||
@@ -1678,9 +1678,6 @@ importers:
|
||||
'@types/htmlparser2':
|
||||
specifier: ^3.10.7
|
||||
version: 3.10.7
|
||||
'@types/jest':
|
||||
specifier: ^29.5.5
|
||||
version: 29.5.12
|
||||
'@types/js-yaml':
|
||||
specifier: ^4.0.9
|
||||
version: 4.0.9
|
||||
@@ -2302,9 +2299,6 @@ importers:
|
||||
toposort:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
ts-jest:
|
||||
specifier: ^29.1.1
|
||||
version: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3)))(typescript@5.8.3)
|
||||
ts-loader:
|
||||
specifier: ^9.2.5
|
||||
version: 9.5.1(typescript@5.8.3)(webpack@5.97.1)
|
||||
@@ -4890,7 +4884,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/love-resources@file:projects/love-resources.tgz':
|
||||
resolution: {integrity: sha512-KzBDtWbDzztfPmsNo9D7m7wf5tebLLMK1cDJP/o5MDJYsS7exjWimlGKAhKKb/iQ4CCjQLT9HwyEhhEPIlbi9g==, tarball: file:projects/love-resources.tgz}
|
||||
resolution: {integrity: sha512-FOzneXg+JMRX2/EeEWd4w/lPJMBkSbDespBkMM479bLjcOwCrjswt/hI2uGHO7Pi0gvVVczrWwzp2eVVpCU81A==, tarball: file:projects/love-resources.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/love@file:projects/love.tgz':
|
||||
@@ -4982,7 +4976,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-chat@file:projects/model-chat.tgz':
|
||||
resolution: {integrity: sha512-64oSh/Vi61UW9byPXcxGfeV1lPT0GtZ9FSlu2p+FdkLqptO4tUmlHE7GMveKoL+OjM64njjOJjqkR0zvRNgy0w==, tarball: file:projects/model-chat.tgz}
|
||||
resolution: {integrity: sha512-dVgwzzmaQjHprWR3mEu1EO5J5azTvzNOyOYBI+AYRtqGHaPLAUGOVFKrebcCdbyJgUhq7wp7aDzq3+r75/fQ+w==, tarball: file:projects/model-chat.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-chunter@file:projects/model-chunter.tgz':
|
||||
@@ -22093,8 +22087,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rush-temp/model-chat@file:projects/model-chat.tgz':
|
||||
'@rush-temp/model-chat@file:projects/model-chat.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 22.15.29
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3)
|
||||
@@ -22103,10 +22098,19 @@ snapshots:
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3))
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3)))(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@rush-temp/model-chunter@file:projects/model-chunter.tgz':
|
||||
dependencies:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -11,9 +11,11 @@
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent --forceExit",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
"_phase:validate": "compile validate",
|
||||
"_phase:test": "jest --passWithNoTests --silent --forceExit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
@@ -26,7 +28,10 @@
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.8.3",
|
||||
"@types/node": "^22.15.29"
|
||||
"@types/node": "^22.15.29",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"ts-jest": "^29.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// 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 type { Card, MasterTag, ParentInfo } from '@hcengineering/card'
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
|
||||
// Import after mocking
|
||||
import { performParentInfoMigration } from '../migration'
|
||||
|
||||
// Mock the chat plugin
|
||||
jest.mock('../plugin', () => ({
|
||||
masterTag: {
|
||||
Thread: 'chat:masterTag:Thread' as Ref<MasterTag>
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock card plugin
|
||||
jest.mock('@hcengineering/card', () => ({
|
||||
class: {
|
||||
Card: 'card:class:Card'
|
||||
},
|
||||
DOMAIN_CARD: 'card'
|
||||
}))
|
||||
|
||||
// Helper function to create mock cards
|
||||
function createMockCard (id: string, parentInfo?: ParentInfo[]): Card {
|
||||
const card: Card = {
|
||||
_id: id as any,
|
||||
_class: 'card:class:Card' as any,
|
||||
title: `Card ${id}`,
|
||||
content: { __ref: 'blob1' } as any,
|
||||
blobs: {} as any,
|
||||
rank: '1',
|
||||
space: 'space1' as any,
|
||||
modifiedBy: 'user1' as any,
|
||||
modifiedOn: Date.now(),
|
||||
createdBy: 'user1' as any,
|
||||
createdOn: Date.now(),
|
||||
parentInfo: parentInfo ?? []
|
||||
}
|
||||
return card
|
||||
}
|
||||
|
||||
describe('migrateParentInfo', () => {
|
||||
let mockClient: any
|
||||
let mockIterator: any
|
||||
let mockLogger: any
|
||||
|
||||
const channelMasterTag = 'chat:masterTag:Channel' as Ref<MasterTag>
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
log: jest.fn(),
|
||||
error: jest.fn()
|
||||
}
|
||||
|
||||
mockIterator = {
|
||||
next: jest.fn(),
|
||||
close: jest.fn()
|
||||
}
|
||||
|
||||
mockClient = {
|
||||
traverse: jest.fn().mockResolvedValue(mockIterator),
|
||||
bulk: jest.fn(),
|
||||
logger: mockLogger
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should migrate cards with channel master tag in parentInfo using custom bulk size', async () => {
|
||||
const cardsWithChannelParent: Card[] = [
|
||||
createMockCard('card1', [
|
||||
{
|
||||
_class: channelMasterTag,
|
||||
title: 'Channel 1',
|
||||
_id: 'channel1' as Ref<Card>
|
||||
}
|
||||
]),
|
||||
createMockCard('card2', [
|
||||
{
|
||||
_id: 'channel2' as Ref<Card>,
|
||||
_class: channelMasterTag,
|
||||
title: 'Channel 2'
|
||||
}
|
||||
])
|
||||
]
|
||||
|
||||
// Mock iterator to return cards in first call, then empty
|
||||
mockIterator.next.mockResolvedValueOnce(cardsWithChannelParent).mockResolvedValueOnce([])
|
||||
|
||||
// Use smaller bulk size for testing
|
||||
await performParentInfoMigration(mockClient, 2)
|
||||
|
||||
// Verify iterator.next was called with the correct bulk size
|
||||
expect(mockIterator.next).toHaveBeenCalledWith(2)
|
||||
|
||||
const mockChatMasterTag = 'chat:masterTag:Thread' as Ref<MasterTag>
|
||||
|
||||
// Verify bulk update was called with correct operations
|
||||
expect(mockClient.bulk).toHaveBeenCalledWith('card', [
|
||||
{
|
||||
filter: { _id: 'card1' },
|
||||
update: {
|
||||
parentInfo: [
|
||||
{
|
||||
_class: mockChatMasterTag,
|
||||
title: 'Channel 1',
|
||||
_id: 'channel1' as Ref<Card>
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
filter: { _id: 'card2' },
|
||||
update: {
|
||||
parentInfo: [
|
||||
{
|
||||
_class: mockChatMasterTag,
|
||||
title: 'Channel 2',
|
||||
_id: 'channel2' as Ref<Card>
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// Verify iterator was closed
|
||||
expect(mockIterator.close).toHaveBeenCalled()
|
||||
|
||||
// Verify logging
|
||||
expect(mockLogger.log).toHaveBeenCalledWith('Migrated cards', { count: 2 })
|
||||
})
|
||||
|
||||
it('should process multiple small batches with custom bulk size', async () => {
|
||||
const batch1: Card[] = [
|
||||
createMockCard('card1', [{ _class: channelMasterTag, title: 'Channel 1', _id: 'channel1' as any }]),
|
||||
createMockCard('card2', [{ _class: channelMasterTag, title: 'Channel 2', _id: 'channel2' as any }])
|
||||
]
|
||||
|
||||
const batch2: Card[] = [
|
||||
createMockCard('card3', [{ _class: channelMasterTag, title: 'Channel 3', _id: 'channel3' as any }])
|
||||
]
|
||||
|
||||
mockIterator.next.mockResolvedValueOnce(batch1).mockResolvedValueOnce(batch2).mockResolvedValueOnce([])
|
||||
|
||||
// Use small bulk size for testing
|
||||
await performParentInfoMigration(mockClient, 2)
|
||||
|
||||
// Verify iterator.next was called with correct bulk size
|
||||
expect(mockIterator.next).toHaveBeenCalledWith(2)
|
||||
|
||||
// Verify bulk was called twice
|
||||
expect(mockClient.bulk).toHaveBeenCalledTimes(2)
|
||||
|
||||
// Verify logging for both batches
|
||||
expect(mockLogger.log).toHaveBeenNthCalledWith(1, 'Migrated cards', { count: 2 })
|
||||
expect(mockLogger.log).toHaveBeenNthCalledWith(2, 'Migrated cards', { count: 3 })
|
||||
|
||||
// Verify iterator was closed
|
||||
expect(mockIterator.close).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should skip cards without parentInfo', async () => {
|
||||
const cardsWithoutParentInfo: Card[] = [createMockCard('card1', undefined), createMockCard('card2', [])]
|
||||
|
||||
mockIterator.next.mockResolvedValueOnce(cardsWithoutParentInfo).mockResolvedValueOnce([])
|
||||
|
||||
await performParentInfoMigration(mockClient, 5)
|
||||
|
||||
// Verify no bulk operations were performed
|
||||
expect(mockClient.bulk).not.toHaveBeenCalled()
|
||||
|
||||
// Verify logging shows processed cards but no updates
|
||||
expect(mockLogger.log).toHaveBeenCalledWith('Migrated cards', { count: 2 })
|
||||
})
|
||||
|
||||
it('should ensure iterator is closed even if an error occurs', async () => {
|
||||
mockIterator.next.mockRejectedValue(new Error('Database error'))
|
||||
|
||||
await expect(performParentInfoMigration(mockClient, 10)).rejects.toThrow('Database error')
|
||||
|
||||
// Verify iterator was still closed
|
||||
expect(mockIterator.close).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should use default bulk size of 1000 when not specified', async () => {
|
||||
const cards: Card[] = [createMockCard('card1', [])]
|
||||
|
||||
mockIterator.next.mockResolvedValueOnce(cards).mockResolvedValueOnce([])
|
||||
|
||||
await performParentInfoMigration(mockClient)
|
||||
|
||||
// Verify iterator.next was called with default bulk size
|
||||
expect(mockIterator.next).toHaveBeenCalledWith(1000)
|
||||
})
|
||||
})
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type MigrationUpgradeClient,
|
||||
type MigrateUpdate,
|
||||
type MigrationDocumentQuery,
|
||||
type MigrateMode,
|
||||
tryMigrate
|
||||
} from '@hcengineering/model'
|
||||
import chat from './plugin'
|
||||
@@ -57,12 +58,16 @@ async function migrateChannelsToThreads (client: MigrationClient): Promise<void>
|
||||
)
|
||||
}
|
||||
|
||||
async function migrateParentInfo (client: MigrationClient): Promise<void> {
|
||||
async function migrateParentInfo (client: MigrationClient, mode: MigrateMode): Promise<void> {
|
||||
await performParentInfoMigration(client, 1000)
|
||||
}
|
||||
|
||||
export async function performParentInfoMigration (client: MigrationClient, bulkSize: number = 1000): Promise<void> {
|
||||
let processedCards = 0
|
||||
const iterator = await client.traverse<Card>(DOMAIN_CARD, { _class: card.class.Card })
|
||||
try {
|
||||
while (true) {
|
||||
const cards = await iterator.next(1000)
|
||||
const cards = await iterator.next(bulkSize)
|
||||
if (cards === null || cards.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
|
||||
Reference in New Issue
Block a user