import 'jest' jest.mock('@feedyou/utils/dist/azurestorage') import { getEntityGenerator, Query, QueryTable, TranslateToObject, UpsertTable, CreateTableIfNotExists, DeleteEntity, GetBlobAsText, DeleteBlob, CreateContainerIfNotExists, CreateBlockBlobFromText, ListBlobsSegmentedWithPrefix } from '@feedyou/utils/dist/azurestorage' import { getBotsFromStorage, saveBotToStorage, getBotVersions, getBotById, getBotComments, saveBotComment, Comment, deleteBotWithId } from '../storageService' import { Bot } from '../../../../../schema' const tableVersions = [ { PartitionKey: 'pk', Version: 'v', RowKey: 'rk', AuthorName: 'an', AuthorId: 'ai', Title: 't', Timestamp: 'ts' }, { PartitionKey: 'pk1', Version: 'v1', RowKey: 'rk1', AuthorName: 'an1', AuthorId: 'ai1', Title: 't1', Timestamp: 'ts1' } ] const tableBotObject = { PartitionKey: 'pk1', Version: 'v1', RowKey: 'rk1', Name: 'testtt', AuthorName: 'an1', AuthorId: 'ai1', Title: 't1', Timestamp: 'ts1' } const tableCommentObject: any = { PartitionKey: 'pk', RowKey: 'rk', StepId: 'stepid', Text: 'text', InReplyTo: 'somebody1', Assignee: { id: 'somebody3' }, Resolved: { by: { id: 'resolvedid', name: 'somebody2' }, timestamp: 12345 }, Status: 'resolved', Author: { name: 'somebody', id: 'some' }, Timestamp: '1234567890', ReversedTimestamp: 'emit', DialogId: 'dialogid' } const commentObject: any = { author: { id: 'some', name: 'somebody' }, dialogId: 'dialogid', id: 'rk', inReplyTo: 'somebody1', resolved: { by: { name: 'somebody2', id: 'resolvedid' }, timestamp: 12345 }, assignee: { id: 'somebody3' }, status: 'resolved', stepId: 'stepid', text: 'text', timestamp: NaN } const testBot: Bot = { id: 'testtt', version: '1.2.2', dialogs: [ { id: 'dialogid', steps: [ { id: 'stepid', type: 'message' } ] } ] } const testBotFull: Bot = { id: 'testtt', name: 'testbot', versionTitle: 'super awesome release', author: { name: 'Jon', id: 'Snow' }, version: '1.2.2', dialogs: [ { id: 'dialogid', steps: [ { id: 'stepid', type: 'message' } ] } ] } const tableBot = { PaddedVersion: { _: '001002002' }, PartitionKey: { _: 'testtt' }, RowKey: { _: '9007199253738989' }, Version: { _: '1.2.2' }, Name: { _: 'testtt' } } const tableBotFull = { AuthorId: { _: 'Snow' }, AuthorName: { _: 'Jon' }, Name: { _: 'testbot' }, PaddedVersion: { _: '001002002' }, PartitionKey: { _: 'testtt' }, RowKey: { _: '9007199253738989' }, Title: { _: 'super awesome release' }, Version: { _: '1.2.2' } } const testComment: Comment = { id: 'id', stepId: 'stepId', author: { name: 'authorName', id: 'authorId' }, inReplyTo: 'inReplyTo', dialogId: 'dialogId', assignee: { id: 'assigneeId' }, status: 'status', resolved: { by: { id: 'resolvedById', name: 'resolvedByName' }, timestamp: 12345 }, text: 'text', timestamp: 12344 } const tableComment = { Assignee: { _: '{"id":"assigneeId"}' }, Author: { _: '{"name":"authorName","id":"authorId"}' }, DialogId: { _: 'dialogId' }, InReplyTo: { _: 'inReplyTo' }, PartitionKey: { _: 'testtt' }, Resolved: { _: '{"by":{"id":"resolvedById","name":"resolvedByName"},"timestamp":12345}' }, ReversedTimestamp: { _: 9999999987656 }, RowKey: { _: 'id' }, Status: { _: 'status' }, StepId: { _: 'stepId' }, Text: { _: 'text' }, Timestamp: { _: new Date('1970-01-01T00:00:12.344Z') } } const setFunctionsMock = (fns: any[], result: any) => { fns.map(fn => setFunctionMock(fn, result)) } const setFunctionMock = (fn: any, result: any) => { fn.mockImplementation(jest.fn(result)) } let queryWhere = jest.fn() beforeEach(() => { jest.resetAllMocks() setFunctionMock(getEntityGenerator, () => { return { String: (x: any) => { return x ? { _: x } : undefined }, DateTime: (x: any) => { return x ? { _: x } : undefined }, Int32: (x: any) => { return x ? { _: x } : undefined } } }) queryWhere = jest.fn() setFunctionMock(Query, () => { const where = { where: queryWhere } return { ...where, top: () => where } }) setFunctionsMock( [ QueryTable, TranslateToObject, UpsertTable, CreateTableIfNotExists, DeleteEntity, GetBlobAsText, DeleteBlob, CreateContainerIfNotExists, CreateBlockBlobFromText ], () => {} ) }) describe('data/bots:', () => { describe('storageService.ts:', () => { describe('getBotVersions():', () => { it('returns bots', async done => { setFunctionMock(TranslateToObject, () => tableVersions) const res = await getBotVersions('testtt') expect(queryWhere).toBeCalledWith('PartitionKey eq ?', 'testtt') expect(res).toEqual([ { author: { id: 'ai', name: 'an' }, id: 'pk', rowKey: 'rk', timestamp: 'ts', title: 't', version: 'v' }, { author: { id: 'ai1', name: 'an1' }, id: 'pk1', rowKey: 'rk1', timestamp: 'ts1', title: 't1', version: 'v1' } ]) done() }) it('throws Error because no bots with id were found', async done => { setFunctionMock(TranslateToObject, () => Array(0)) try { const res = await getBotVersions('testtt') expect(true).toBe(false) } catch (err) { expect(queryWhere).toBeCalledWith('PartitionKey eq ?', 'testtt') expect(err).toMatchObject(new Error('could not find bot testtt')) done() } }) }) describe('saveBotToStorage():', () => { it('saves bot to storage', async done => { setFunctionMock(QueryTable, () => Array(0)) //so entityExists returned false const res = await saveBotToStorage(testBot) expect(res).toBeUndefined() expect(CreateContainerIfNotExists).toBeCalledWith('bot-versions') expect(CreateTableIfNotExists).toBeCalledWith('BotVersion') expect(UpsertTable).toBeCalledWith('BotVersion', tableBot, 'replace') expect(CreateBlockBlobFromText).toBeCalledWith( 'bot-versions', 'testtt-9007199253738989.json', JSON.stringify(testBot) ) done() }) it('saves bot to conflict storage', async done => { setFunctionMock(QueryTable, () => { return { entries: [{ test: 'hi' }] } }) //so entityExists returned true try { //@ts-ignore const res = await saveBotToStorage({ ...testBotFull, version: 122 }) expect(true).toBe(false) } catch (err) { expect(err).toMatchObject( new Error( 'Version 122 of bot testbot testtt already exists - created probably by someone else. Backup file of your version was just created, please contact administrator to resolve this conflict. Send him screenshot of this message.' ) ) expect(CreateContainerIfNotExists).toBeCalledWith('conflict-bot-versions') expect(CreateTableIfNotExists).toBeCalledWith('BotVersionConflict') expect(UpsertTable).toBeCalledWith( 'BotVersionConflict', { ...tableBotFull, Version: { _: 122 }, PaddedVersion: { _: '122000000' }, RowKey: { _: '9007199132740991' } }, 'replace' ) expect(CreateBlockBlobFromText).toBeCalledWith( 'conflict-bot-versions', 'testtt-9007199132740991.json', JSON.stringify({ ...testBotFull, version: 122 }) ) } done() }) }) describe('getBotsFromStorage():', () => { it('doesnt find anything', async done => { setFunctionMock(TranslateToObject, () => Array(0)) setFunctionMock(GetBlobAsText, () => '') const res = await getBotsFromStorage() expect(res).toEqual([]) expect(GetBlobAsText).not.toBeCalled() done() }) it('returns bots', async done => { setFunctionMock(TranslateToObject, () => { const arr = [] arr.push(tableBotObject) arr.push(tableBotObject) return arr }) setFunctionMock(GetBlobAsText, () => '{"environments": {"others":"envOthers"}}') const res = await getBotsFromStorage() expect(res).toEqual([{ environments: 'envOthers', id: 'pk1', name: 'testtt' }]) expect(GetBlobAsText).toBeCalledWith('bot-versions', 'pk1-rk1.json') done() }) }) describe('getBotById():', () => { it('throws error because no bots were found', async done => { setFunctionMock(TranslateToObject, () => Array(0)) setFunctionMock(GetBlobAsText, () => '') try { const res = await getBotById('testtt') expect(true).toBe(false) } catch (err) { expect(err).toMatchObject(new Error('could not find bot testtt')) expect(GetBlobAsText).not.toBeCalled() } done() }) it('returns bot', async done => { setFunctionMock(TranslateToObject, () => { const bots = [] bots.push(tableBotObject) return bots }) setFunctionMock(GetBlobAsText, () => '{"id": "super awesoem bot"}') try { const res = await getBotById('testtt') expect(res).toEqual({ bot: { id: 'super awesoem bot' } }) expect(GetBlobAsText).toBeCalledWith('bot-versions', 'testtt-rk1.json') } catch (err) { expect(true).toBe(false) } done() }) }) describe('getBotComments():', () => { it('returns [] because undefined', async done => { setFunctionMock(TranslateToObject, (): undefined => undefined) const res = await getBotComments('testtt') expect(res).toEqual([]) done() }) it('returns [] because no comments were found', async done => { setFunctionMock(TranslateToObject, () => [{}, {}]) const res = await getBotComments('testtt') expect(res).toEqual([]) done() }) it('returns comments', async done => { setFunctionMock(TranslateToObject, () => { const comments = [] comments.push(tableCommentObject) comments.push({}) comments.push(tableCommentObject) return comments }) const res = await getBotComments('testtt') const comments = [] comments.push(commentObject) comments.push(commentObject) expect(res).toEqual(comments) done() }) }) describe('saveBotComment():', () => { it('throws error because no comment', async done => { setFunctionMock(UpsertTable, () => {}) try { const res = await saveBotComment('testtt', undefined) expect(saveBotComment).toThrowError() } catch (err) { expect(err).toMatchObject(new Error('comment body is empty')) expect(UpsertTable).not.toBeCalled() } done() }) it('throws error because failed to save', async done => { setFunctionMock(UpsertTable, () => { throw new Error('test error') }) try { const res = await saveBotComment('testtt', testComment) expect(saveBotComment).toThrowError() } catch (err) { expect(err).toMatchObject(new Error('test error')) expect(UpsertTable).toBeCalledWith('Comments', tableComment, 'replace') } done() }) it('saves bot', async done => { setFunctionMock(UpsertTable, () => {}) try { const res = await saveBotComment('testtt', testComment) expect(UpsertTable).toBeCalledWith('Comments', tableComment, 'replace') expect(res).toBeUndefined() } catch (err) { expect(saveBotComment).not.toThrowError() } done() }) }) describe('deleteBotWithId():', () => { it('returns undefined because bot doesnt exist', async done => { setFunctionMock(TranslateToObject, () => { throw new Error('test error') }) setFunctionMock(ListBlobsSegmentedWithPrefix, () => { return { entries: [ { name: 'test-blob-name.json' } ] } }) try { const res = await deleteBotWithId('testtt') expect(res).toBeUndefined() expect(DeleteBlob).not.toBeCalled() expect(DeleteEntity).not.toBeCalled() } catch (err) { expect(deleteBotWithId).not.toThrowError() } done() }) it('returns error because failed to delete something', async done => { setFunctionMock(TranslateToObject, () => [{ ...tableBotObject }]) setFunctionMock(ListBlobsSegmentedWithPrefix, () => { return { entries: [ { name: 'test-blob-name.json' } ] } }) setFunctionMock(DeleteBlob, () => { throw new Error('test error') }) setFunctionMock(DeleteEntity, () => {}) try { const res = await deleteBotWithId('') expect(deleteBotWithId).toThrowError() } catch (err) { expect(err).toMatchObject(new Error('test error')) expect(DeleteBlob).toBeCalledTimes(1) expect(DeleteBlob).toBeCalledWith('bot-versions', 'test-blob-name.json') expect(DeleteEntity).toBeCalledWith('BotVersion', 'pk1', 'rk1') } done() }) it('deletes entity and blob', async done => { setFunctionMock(TranslateToObject, () => tableVersions) setFunctionMock(ListBlobsSegmentedWithPrefix, () => { return { entries: [ { name: 'test-blob-name.json' } ] } }) setFunctionsMock([DeleteBlob, DeleteEntity], () => {}) try { const res = await deleteBotWithId('pk') expect(res).toBeUndefined() } catch (err) { expect(deleteBotWithId).not.toThrowError() } expect(DeleteBlob).toBeCalledTimes(1) expect(DeleteEntity).toBeCalledTimes(2) expect(DeleteBlob).toBeCalledWith('bot-versions', 'test-blob-name.json') expect(DeleteEntity).toHaveBeenNthCalledWith(1, 'BotVersion', 'pk', 'rk') expect(DeleteEntity).toHaveBeenNthCalledWith(2, 'BotVersion', 'pk1', 'rk1') expect(ListBlobsSegmentedWithPrefix).toBeCalledWith('bot-versions', 'pk') done() }) }) }) })