123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- import { Logger } from 'winston'
- import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
- import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
- import { Revision } from '../../../Revision/Revision'
- import { ContentType, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
- import { TimerInterface } from '@standardnotes/time'
- describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
- let primaryRevisionRepository: RevisionRepositoryInterface
- let secondaryRevisionRepository: RevisionRepositoryInterface | null
- let logger: Logger
- let primaryRevision1: Revision
- let primaryRevision2: Revision
- let secondaryRevision1: Revision
- let secondaryRevision2: Revision
- let timer: TimerInterface
- const createUseCase = () =>
- new TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser(
- primaryRevisionRepository,
- secondaryRevisionRepository,
- timer,
- logger,
- )
- beforeEach(() => {
- primaryRevision1 = Revision.create(
- {
- itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
- userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
- content: 'test',
- contentType: ContentType.create('Note').getValue(),
- itemsKeyId: 'test',
- encItemKey: 'test',
- authHash: 'test',
- creationDate: new Date(1),
- dates: Dates.create(new Date(1), new Date(2)).getValue(),
- },
- new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
- ).getValue()
- primaryRevision2 = Revision.create(
- {
- itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc2d').getValue(),
- userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
- content: 'test',
- contentType: ContentType.create('Note').getValue(),
- itemsKeyId: 'test',
- encItemKey: 'test',
- authHash: 'test',
- creationDate: new Date(1),
- dates: Dates.create(new Date(1), new Date(2)).getValue(),
- },
- new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
- ).getValue()
- secondaryRevision1 = Revision.create(
- {
- itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
- userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
- content: 'test',
- contentType: ContentType.create('Note').getValue(),
- itemsKeyId: 'test',
- encItemKey: 'test',
- authHash: 'test',
- creationDate: new Date(1),
- dates: Dates.create(new Date(1), new Date(2)).getValue(),
- },
- new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
- ).getValue()
- secondaryRevision2 = Revision.create(
- {
- itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc2d').getValue(),
- userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
- content: 'test',
- contentType: ContentType.create('Note').getValue(),
- itemsKeyId: 'test',
- encItemKey: 'test',
- authHash: 'test',
- creationDate: new Date(1),
- dates: Dates.create(new Date(1), new Date(2)).getValue(),
- },
- new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
- ).getValue()
- primaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
- primaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
- primaryRevisionRepository.findByUserUuid = jest
- .fn()
- .mockResolvedValueOnce([primaryRevision1])
- .mockResolvedValueOnce([primaryRevision2])
- .mockResolvedValueOnce([primaryRevision1])
- .mockResolvedValueOnce([primaryRevision2])
- primaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
- secondaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
- secondaryRevisionRepository.insert = jest.fn().mockResolvedValue(true)
- secondaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
- secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
- secondaryRevisionRepository.findOneByUuid = jest
- .fn()
- .mockResolvedValueOnce(secondaryRevision1)
- .mockResolvedValueOnce(secondaryRevision2)
- logger = {} as jest.Mocked<Logger>
- logger.error = jest.fn()
- logger.info = jest.fn()
- timer = {} as jest.Mocked<TimerInterface>
- timer.sleep = jest.fn()
- timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
- timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
- days: 0,
- hours: 0,
- minutes: 0,
- seconds: 0,
- milliseconds: 0,
- })
- })
- describe('successfull transition', () => {
- it('should transition Revisions from primary to secondary database', async () => {
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeFalsy()
- expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(2)
- expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
- Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- )
- expect(primaryRevisionRepository.findByUserUuid).toHaveBeenCalledTimes(4)
- expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(1, {
- userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- limit: 1,
- offset: 0,
- })
- expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(2, {
- userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- limit: 1,
- offset: 1,
- })
- expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(3, {
- userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- limit: 1,
- offset: 0,
- })
- expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(4, {
- userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- limit: 1,
- offset: 1,
- })
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledTimes(2)
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision1)
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision2)
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
- expect(primaryRevisionRepository.removeByUserUuid).toHaveBeenCalledTimes(1)
- })
- it('should log an error if deleting Revisions from primary database fails', async () => {
- primaryRevisionRepository.removeByUserUuid = jest.fn().mockRejectedValue(new Error('error'))
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeFalsy()
- expect(logger.error).toHaveBeenCalledTimes(1)
- expect(logger.error).toHaveBeenCalledWith(
- 'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
- )
- })
- })
- describe('failed transition', () => {
- it('should remove Revisions from secondary database if integrity check fails', async () => {
- const secondaryRevision2WithDifferentContent = Revision.create({
- ...secondaryRevision2.props,
- content: 'different-content',
- }).getValue()
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
- .fn()
- .mockResolvedValueOnce(secondaryRevision1)
- .mockResolvedValueOnce(secondaryRevision2WithDifferentContent)
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual(
- 'Revision 00000000-0000-0000-0000-000000000001 is not identical in primary and secondary database',
- )
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- })
- it('should remove Revisions from secondary database if migrating Revisions fails', async () => {
- primaryRevisionRepository.findByUserUuid = jest
- .fn()
- .mockResolvedValueOnce([primaryRevision1])
- .mockRejectedValueOnce(new Error('error'))
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual(
- 'Errored when migrating revisions for user 00000000-0000-0000-0000-000000000000: error',
- )
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- })
- it('should return an error for a specific revision if it errors when saving to secondary database', async () => {
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest
- .fn()
- .mockResolvedValueOnce(true)
- .mockRejectedValueOnce(new Error('error'))
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual(
- 'Errored when saving revision 00000000-0000-0000-0000-000000000001 to secondary database: error',
- )
- })
- it('should log an error if deleting Revisions from secondary database fails upon migration failure', async () => {
- primaryRevisionRepository.findByUserUuid = jest
- .fn()
- .mockResolvedValueOnce([primaryRevision1])
- .mockRejectedValueOnce(new Error('error'))
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid = jest
- .fn()
- .mockRejectedValue(new Error('error'))
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(logger.error).toHaveBeenCalledTimes(1)
- expect(logger.error).toHaveBeenCalledWith(
- 'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
- )
- })
- it('should log an error if deleting Revisions from secondary database fails upon integrity check failure', async () => {
- const secondaryRevision2WithDifferentContent = Revision.create({
- ...secondaryRevision2.props,
- content: 'different-content',
- }).getValue()
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
- .fn()
- .mockResolvedValueOnce(secondaryRevision1)
- .mockResolvedValueOnce(secondaryRevision2WithDifferentContent)
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid = jest
- .fn()
- .mockRejectedValue(new Error('error'))
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(logger.error).toHaveBeenCalledTimes(1)
- expect(logger.error).toHaveBeenCalledWith(
- 'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
- )
- })
- it('should not perform the transition if secondary Revision repository is not set', async () => {
- secondaryRevisionRepository = null
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual('Secondary revision repository is not set')
- expect(primaryRevisionRepository.countByUserUuid).not.toHaveBeenCalled()
- expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- })
- it('should not perform the transition if the user uuid is invalid', async () => {
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: 'invalid-uuid',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual('Given value is not a valid uuid: invalid-uuid')
- expect(primaryRevisionRepository.countByUserUuid).not.toHaveBeenCalled()
- expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- })
- it('should fail integrity check if the Revision count is not the same in both databases', async () => {
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest.fn().mockResolvedValue(1)
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual(
- 'Total revisions count for user 00000000-0000-0000-0000-000000000000 in primary database (2) does not match total revisions count in secondary database (1)',
- )
- expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(2)
- expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
- Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- )
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
- })
- it('should fail if one Revision is not found in the secondary database', async () => {
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
- .fn()
- .mockResolvedValueOnce(secondaryRevision1)
- .mockResolvedValueOnce(null)
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual('Revision 00000000-0000-0000-0000-000000000001 not found in secondary database')
- expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(2)
- expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
- Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
- )
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).not.toHaveBeenCalled()
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
- })
- it('should fail if an error is thrown during integrity check between primary and secondary database', async () => {
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest
- .fn()
- .mockRejectedValue(new Error('error'))
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual('Errored when checking integrity between primary and secondary database: error')
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
- })
- it('should fail if a revisions did not save in the secondary database', async () => {
- ;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest.fn().mockResolvedValue(false)
- const useCase = createUseCase()
- const result = await useCase.execute({
- userUuid: '00000000-0000-0000-0000-000000000000',
- })
- expect(result.isFailed()).toBeTruthy()
- expect(result.getError()).toEqual(
- 'Failed to save revision 00000000-0000-0000-0000-000000000000 to secondary database',
- )
- expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
- expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
- })
- })
- })
|