Files
standardnotes-server/packages/syncing-server/src/Infra/MySQL/MySQLItemRepository.spec.ts
T

402 lines
15 KiB
TypeScript

import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { ContentType } from '@standardnotes/common'
import { Item } from '../../Domain/Item/Item'
import { MySQLItemRepository } from './MySQLItemRepository'
import { TimerInterface } from '@standardnotes/time'
import { ReadStream } from 'fs'
describe('MySQLItemRepository', () => {
let queryBuilder: SelectQueryBuilder<Item>
let ormRepository: Repository<Item>
let item: Item
let timer: TimerInterface
const createRepository = () => new MySQLItemRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Item>>
item = {} as jest.Mocked<Item>
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn(() => 1616161616161616)
ormRepository = {} as jest.Mocked<Repository<Item>>
ormRepository.save = jest.fn()
ormRepository.remove = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(item)
expect(ormRepository.save).toHaveBeenCalledWith(item)
})
it('should remove', async () => {
await createRepository().remove(item)
expect(ormRepository.remove).toHaveBeenCalledWith(item)
})
it('should delete all items for a given user', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.delete = jest.fn().mockReturnThis()
queryBuilder.from = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().deleteByUserUuid('123')
expect(queryBuilder.delete).toHaveBeenCalled()
expect(queryBuilder.from).toHaveBeenCalledWith('items')
expect(queryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: '123' })
expect(queryBuilder.execute).toHaveBeenCalled()
})
it('should find one item by uuid and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(item)
const result = await createRepository().findByUuidAndUserUuid('1-2-3', '2-3-4')
expect(queryBuilder.where).toHaveBeenCalledWith('item.uuid = :uuid AND item.user_uuid = :userUuid', {
uuid: '1-2-3',
userUuid: '2-3-4',
})
expect(result).toEqual(item)
})
it('should find one item by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(item)
const result = await createRepository().findByUuid('1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('item.uuid = :uuid', {
uuid: '1-2-3',
})
expect(result).toEqual(item)
})
it('should find items by all query criteria filled in', async () => {
queryBuilder.getMany = jest.fn().mockReturnValue([item])
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().findAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should stream items by all query criteria filled in', async () => {
const stream = {} as jest.Mocked<ReadStream>
queryBuilder.stream = jest.fn().mockReturnValue(stream)
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().streamAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual(stream)
})
it('should find items content sizes by all query criteria filled in', async () => {
queryBuilder.getRawMany = jest.fn().mockReturnValue([{ uuid: item.uuid, contentSize: item.contentSize }])
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.select = jest.fn()
queryBuilder.addSelect = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().findContentSizeForComputingTransferLimit({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.select).toHaveBeenCalledWith('item.uuid', 'uuid')
expect(queryBuilder.addSelect).toHaveBeenCalledWith('item.content_size', 'contentSize')
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should find items by all query criteria filled in', async () => {
queryBuilder.getMany = jest.fn().mockReturnValue([item])
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().findAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should count items by all query criteria filled in', async () => {
queryBuilder.getCount = jest.fn().mockReturnValue(1)
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().countAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual(1)
})
it('should find items by only mandatory query criteria', async () => {
queryBuilder.getMany = jest.fn().mockReturnValue([item])
queryBuilder.where = jest.fn()
queryBuilder.orderBy = jest.fn()
const result = await createRepository().findAll({
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
})
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should find dates for computing integrity hash', async () => {
queryBuilder.getRawMany = jest
.fn()
.mockReturnValue([{ updated_at_timestamp: 1616164633241312 }, { updated_at_timestamp: 1616164633242313 }])
queryBuilder.select = jest.fn()
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
const result = await createRepository().findDatesForComputingIntegrityHash('1-2-3')
expect(queryBuilder.select).toHaveBeenCalledWith('item.updated_at_timestamp')
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.deleted = :deleted', { deleted: false })
expect(result.length).toEqual(2)
expect(result[0]).toEqual({ updated_at_timestamp: 1616164633242313 })
expect(result[1]).toEqual({ updated_at_timestamp: 1616164633241312 })
})
it('should find items for computing integrity payloads', async () => {
queryBuilder.getRawMany = jest.fn().mockReturnValue([
{ uuid: '1-2-3', updated_at_timestamp: 1616164633241312, content_type: ContentType.Note },
{ uuid: '2-3-4', updated_at_timestamp: 1616164633242313, content_type: ContentType.ItemsKey },
])
queryBuilder.select = jest.fn()
queryBuilder.addSelect = jest.fn()
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
const result = await createRepository().findItemsForComputingIntegrityPayloads('1-2-3')
expect(queryBuilder.select).toHaveBeenCalledWith('item.uuid', 'uuid')
expect(queryBuilder.addSelect).toHaveBeenNthCalledWith(1, 'item.updated_at_timestamp', 'updated_at_timestamp')
expect(queryBuilder.addSelect).toHaveBeenNthCalledWith(2, 'item.content_type', 'content_type')
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.deleted = :deleted', { deleted: false })
expect(result.length).toEqual(2)
expect(result[0]).toEqual({
uuid: '2-3-4',
updated_at_timestamp: 1616164633242313,
content_type: ContentType.ItemsKey,
})
expect(result[1]).toEqual({ uuid: '1-2-3', updated_at_timestamp: 1616164633241312, content_type: ContentType.Note })
})
it('should find item by uuid and mark it for deletion', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.update = jest.fn().mockReturnThis()
queryBuilder.update().set = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
const item = { uuid: 'e-1-2-3' } as jest.Mocked<Item>
const updatedAtTimestamp = timer.getTimestampInMicroseconds()
await createRepository().markItemsAsDeleted([item.uuid], updatedAtTimestamp)
expect(queryBuilder.update).toHaveBeenCalled()
expect(queryBuilder.update().set).toHaveBeenCalledWith(
expect.objectContaining({
deleted: true,
content: null,
encItemKey: null,
authHash: null,
updatedAtTimestamp: expect.anything(),
}),
)
expect(queryBuilder.where).toHaveBeenCalledWith('uuid IN (:...uuids)', {
uuids: ['e-1-2-3'],
})
expect(queryBuilder.execute).toHaveBeenCalled()
})
it('should update item content size', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.update = jest.fn().mockReturnThis()
queryBuilder.update().set = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().updateContentSize('1-2-3', 345)
expect(queryBuilder.update).toHaveBeenCalled()
expect(queryBuilder.update().set).toHaveBeenCalledWith(
expect.objectContaining({
contentSize: 345,
}),
)
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :itemUuid', {
itemUuid: '1-2-3',
})
expect(queryBuilder.execute).toHaveBeenCalled()
})
})