ElectronicBabylonianLiterature/ebl-frontend

View on GitHub
src/fragmentarium/application/FragmentService.test.ts

Summary

Maintainability
A
0 mins
Test Coverage
import Promise from 'bluebird'
import Folio from 'fragmentarium/domain/Folio'
import { fragment } from 'test-support/test-fragment'
import createLemmatizationTestText from 'test-support/test-text'
import { TestData, testDelegation } from 'test-support/utils'
import Lemma from 'transliteration/domain/Lemma'
import Lemmatization, {
  LemmatizationToken,
} from 'transliteration/domain/Lemmatization'
import FragmentService from './FragmentService'
import { Fragment } from 'fragmentarium/domain/fragment'
import produce, { castDraft, Draft } from 'immer'
import { Genres } from 'fragmentarium/domain/Genres'
import Word from 'dictionary/domain/Word'
import { ManuscriptAttestation } from 'corpus/domain/manuscriptAttestation'
import LemmatizationFactory from './LemmatizationFactory'
import BibliographyService from 'bibliography/application/BibliographyService'
import WordRepository from 'dictionary/infrastructure/WordRepository'
import {
  fragmentFactory,
  manuscriptAttestationFactory,
} from 'test-support/fragment-fixtures'
import { archaeologyFactory } from 'test-support/fragment-data-fixtures'
import {
  bibliographyEntryFactory,
  referenceFactory,
} from 'test-support/bibliography-fixtures'
import BibliographyEntry from 'bibliography/domain/BibliographyEntry'
import { wordFactory } from 'test-support/word-fixtures'
import { silenceConsoleErrors } from 'setupTests'
import { QueryResult } from 'query/QueryResult'
import { MesopotamianDate } from 'chronology/domain/Date'
import { Archaeology } from 'fragmentarium/domain/archaeology'
import { ArchaeologyDto } from 'fragmentarium/domain/archaeologyDtos'
import { toArchaeologyDto } from 'fragmentarium/domain/archaeologyDtos'

jest.mock('./LemmatizationFactory')

jest.mock('bibliography/application/BibliographyService', () => {
  return function () {
    return { find: jest.fn(), findMany: jest.fn(), search: jest.fn() }
  }
})

jest.mock('dictionary/infrastructure/WordRepository', () => {
  return function () {
    return { searchLemma: jest.fn(), find: jest.fn() }
  }
})
const resultStub = {}
const folio = new Folio({ name: 'AKG', number: '375' })
const fileName = 'Babel_Project_01_cropped.svg'
const word: Word = wordFactory.build()
const fragmentRepository = {
  statistics: jest.fn(),
  find: jest.fn(),
  updateTransliteration: jest.fn(),
  updateIntroduction: jest.fn(),
  updateNotes: jest.fn(),
  updateLemmatization: jest.fn(),
  updateGenres: jest.fn(),
  updateScript: jest.fn(),
  updateDate: jest.fn(),
  updateDatesInText: jest.fn(),
  fetchGenres: jest.fn(),
  fetchProvenances: jest.fn(),
  fetchPeriods: jest.fn(),
  fetchColophonNames: jest.fn(),
  updateReferences: jest.fn(),
  updateArchaeology: jest.fn(),
  updateColophon: jest.fn(),
  folioPager: jest.fn(),
  fragmentPager: jest.fn(),
  findLemmas: jest.fn(),
  fetchCdliInfo: jest.fn(),
  findAnnotations: jest.fn(),
  updateAnnotations: jest.fn(),
  lineToVecRanking: jest.fn(),
  findInCorpus: jest.fn(),
  query: jest.fn(),
  queryLatest: jest.fn(),
  listAllFragments: jest.fn(),
  queryByTraditionalReferences: jest.fn(),
}

const imageRepository = {
  find: jest.fn(),
  findFolio: jest.fn(),
  findPhoto: jest.fn(),
  findThumbnail: jest.fn(),
}
const bibliographyService = new (BibliographyService as jest.Mock)()
const wordRepository = new (WordRepository as jest.Mock)()
const fragmentService = new FragmentService(
  fragmentRepository,
  imageRepository,
  wordRepository,
  bibliographyService
)
const lemmas = 'foo I+bar II'
const queryResultStub: QueryResult = { items: [], matchCountTotal: 0 }

const testData: TestData<FragmentService>[] = [
  new TestData('statistics', [], fragmentRepository.statistics, resultStub),
  new TestData(
    'lineToVecRanking',
    ['X.0'],
    fragmentRepository.lineToVecRanking,
    resultStub
  ),
  new TestData('findFolio', [folio], imageRepository.findFolio, resultStub, [
    folio,
  ]),
  new TestData('findImage', [fileName], imageRepository.find, resultStub, [
    fileName,
  ]),
  new TestData('findPhoto', [fragment], imageRepository.findPhoto, resultStub, [
    fragment.number,
  ]),
  new TestData(
    'findThumbnail',
    [fragment, 'small'],
    imageRepository.findThumbnail,
    resultStub,
    [fragment.number, 'small']
  ),
  new TestData(
    'folioPager',
    [folio, 'K.1'],
    fragmentRepository.folioPager,
    resultStub
  ),
  new TestData(
    'fragmentPager',
    ['K.1'],
    fragmentRepository.fragmentPager,
    resultStub
  ),
  new TestData('searchLemma', ['lemma'], wordRepository.searchLemma, [
    resultStub,
  ]),
  new TestData(
    'searchBibliography',
    ['Alba Cecilia 1998 The Qualifications'],
    bibliographyService.search,
    [resultStub]
  ),
  new TestData(
    'fetchCdliInfo',
    [fragment],
    fragmentRepository.fetchCdliInfo,
    resultStub,
    [fragment.getExternalNumber('cdliNumber')]
  ),
  new TestData(
    'findAnnotations',
    [fragment.number, false],
    fragmentRepository.findAnnotations,
    resultStub
  ),
  new TestData(
    'generateAnnotations',
    [fragment.number, true],
    fragmentRepository.findAnnotations,
    resultStub
  ),
  new TestData(
    'updateAnnotations',
    [fragment.number, resultStub],
    fragmentRepository.updateAnnotations,
    resultStub
  ),
  new TestData(
    'findSuggestions',
    ['kur', true],
    fragmentRepository.findLemmas,
    [[new Lemma(word)]],
    ['kur', true],
    Promise.resolve([[word]])
  ),
  new TestData(
    'listAllFragments',
    [],
    fragmentRepository.listAllFragments,
    [],
    [],
    Promise.resolve([])
  ),
]

testDelegation(fragmentService, testData)

describe.each(['searchLemma'])('%s', (method) => {
  test('Resolves to empty array on zero length query', async () => {
    await expect(fragmentService[method]('')).resolves.toEqual([])
  })
})

describe('methods returning fragment', () => {
  const number = 'K.1'
  let fragment: Fragment
  let result: Fragment
  let genreResult: string[][]
  let colophonNamesResult: string[]
  const genreOptions = [['ARCHIVE', 'Administrative']]
  const genres: Genres = Genres.fromJson([
    {
      category: ['ARCHIVE', 'Administrative'],
      uncertain: false,
    },
  ])
  const colophonNamesOptions = [['Humbaba', 'Enkidu']]
  const date: MesopotamianDate = MesopotamianDate.fromJson({
    year: { value: '1' },
    month: { value: '1' },
    day: { value: '1' },
    isSeleucidEra: true,
  })
  const datesInText: MesopotamianDate[] = [date]
  let archaeology: Archaeology
  let archaeologyDto: ArchaeologyDto

  beforeEach(() => {
    const references = bibliographyEntryFactory
      .buildList(2)
      .map((entry: BibliographyEntry) =>
        referenceFactory.build({}, { associations: { document: entry } })
      )
    fragment = fragmentFactory.build(
      {
        number: number,
      },
      {
        associations: {
          references: references,
          genres: new Genres([]),
        },
      }
    )
    bibliographyService.find.mockImplementation((id: string) =>
      Promise.reject(new Error(`${id} not found.`))
    )
    bibliographyService.findMany.mockImplementation((ids: string[]) =>
      Promise.reject(new Error(`${ids} not found.`))
    )
    silenceConsoleErrors()
  })

  describe('find', () => {
    beforeEach(async () => {
      fragmentRepository.find.mockReturnValue(Promise.resolve(fragment))
      result = await fragmentService.find(number)
    })

    test('Returns fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () => {
      expect(fragmentRepository.find).toHaveBeenCalledWith(
        number,
        undefined,
        undefined
      )
    })
  })

  describe('Reject with permission denied', () => {
    test('Throws permission error', async () => {
      fragmentRepository.find.mockReturnValueOnce(
        Promise.reject(new Error('403 Forbidden'))
      )
      expect(fragmentRepository.find('X.1')).rejects.toThrowError(
        "You don't have permissions to view this fragment."
      )
    })
  })

  describe('update transliteration', () => {
    const transliteration = '1. kur'

    beforeEach(async () => {
      fragmentRepository.updateTransliteration.mockReturnValue(
        Promise.resolve(fragment)
      )
      result = await fragmentService.updateTransliteration(
        fragment.number,
        transliteration
      )
    })

    test('Returns updated fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () =>
      expect(fragmentRepository.updateTransliteration).toHaveBeenCalledWith(
        fragment.number,
        transliteration
      ))
  })
  describe('update introduction', () => {
    const introduction = 'Introductory @i{text}'

    beforeEach(async () => {
      fragmentRepository.updateIntroduction.mockReturnValue(
        Promise.resolve(fragment)
      )
      result = await fragmentService.updateIntroduction(
        fragment.number,
        introduction
      )
    })

    test('Returns updated fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () =>
      expect(fragmentRepository.updateIntroduction).toHaveBeenCalledWith(
        fragment.number,
        introduction
      ))
  })
  describe('update notes', () => {
    const notes = 'Notes @i{text}'

    beforeEach(async () => {
      fragmentRepository.updateNotes.mockReturnValue(Promise.resolve(fragment))
      result = await fragmentService.updateNotes(fragment.number, notes)
    })

    test('Returns updated fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () =>
      expect(fragmentRepository.updateNotes).toHaveBeenCalledWith(
        fragment.number,
        notes
      ))
  })
  describe('update edition', () => {
    const transliteration = '1. kur'
    const notes = 'notes'
    const introduction = 'Introductory @i{text}'

    beforeEach(async () => {
      fragmentRepository.updateIntroduction.mockReturnValue(
        Promise.resolve(fragment)
      )
      fragmentRepository.updateNotes.mockReturnValue(Promise.resolve(fragment))
      fragmentRepository.updateTransliteration.mockReturnValue(
        Promise.resolve(fragment)
      )
      result = await fragmentService.updateEdition(
        fragment.number,
        transliteration,
        notes,
        introduction
      )
    })

    test('Returns updated fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () => {
      expect(fragmentRepository.updateTransliteration).toHaveBeenCalledWith(
        fragment.number,
        transliteration
      )
      expect(fragmentRepository.updateIntroduction).toHaveBeenCalledWith(
        fragment.number,
        introduction
      )
      expect(fragmentRepository.updateNotes).toHaveBeenCalledWith(
        fragment.number,
        notes
      )
    })
  })
  describe('fetch genres', () => {
    beforeEach(async () => {
      fragmentRepository.fetchGenres.mockReturnValue(
        Promise.resolve(genreOptions)
      )
      genreResult = await fragmentService.fetchGenres()
    })
    test('returns genres', () => expect(genreResult).toEqual(genreOptions))
    test('calls repository with correct parameters', () =>
      expect(fragmentRepository.fetchGenres).toHaveBeenCalled())
  })

  describe('fetch colophon names', () => {
    beforeEach(async () => {
      fragmentRepository.fetchColophonNames.mockReturnValue(
        Promise.resolve(colophonNamesOptions)
      )
      colophonNamesResult = await fragmentService.fetchColophonNames('u')
    })
    test('returns names', () =>
      expect(colophonNamesResult).toEqual(colophonNamesOptions))
    test('calls repository with correct parameters', () =>
      expect(fragmentRepository.fetchColophonNames).toHaveBeenCalled())
  })

  describe('update genre', () => {
    let expectedFragment: Fragment

    beforeEach(async () => {
      expectedFragment = produce(fragment, (draft: Draft<Fragment>) => {
        draft.genres = castDraft(genres)
      })
      fragmentRepository.updateGenres.mockReturnValue(
        Promise.resolve(expectedFragment)
      )
      result = await fragmentService.updateGenres(fragment.number, genres)
    })
    test('returns updated fragment', () =>
      expect(result).toEqual(expectedFragment))
    test('calls repository with correct parameters', () =>
      expect(fragmentRepository.updateGenres).toHaveBeenCalledWith(
        fragment.number,
        genres
      ))
  })

  describe('update date', () => {
    let expectedFragment: Fragment

    beforeEach(async () => {
      expectedFragment = produce(fragment, (draft: Draft<Fragment>) => {
        draft.date = castDraft(date)
      })
      fragmentRepository.updateDate.mockReturnValue(
        Promise.resolve(expectedFragment)
      )
      result = await fragmentService.updateDate(fragment.number, date)
    })
    test('returns updated fragment', () =>
      expect(result).toEqual(expectedFragment))
    test('calls repository with correct parameters', () =>
      expect(fragmentRepository.updateDate).toHaveBeenCalledWith(
        fragment.number,
        date
      ))
  })

  describe('update dates in text', () => {
    let expectedFragment: Fragment

    beforeEach(async () => {
      expectedFragment = produce(fragment, (draft: Draft<Fragment>) => {
        draft.datesInText = castDraft(datesInText)
      })
      fragmentRepository.updateDatesInText.mockReturnValue(
        Promise.resolve(expectedFragment)
      )
      result = await fragmentService.updateDatesInText(
        fragment.number,
        datesInText
      )
    })
    test('returns updated fragment', () =>
      expect(result).toEqual(expectedFragment))
    test('calls repository with correct parameters', () =>
      expect(fragmentRepository.updateDatesInText).toHaveBeenCalledWith(
        fragment.number,
        datesInText
      ))
  })

  describe('update archaeology', () => {
    let expectedFragment: Fragment

    beforeEach(async () => {
      archaeology = archaeologyFactory.build()
      archaeologyDto = toArchaeologyDto(archaeology)
      expectedFragment = produce(fragment, (draft: Draft<Fragment>) => {
        draft.archaeology = castDraft(archaeology)
      })
      fragmentRepository.updateArchaeology.mockReturnValue(
        Promise.resolve(expectedFragment)
      )
      result = await fragmentService.updateArchaeology(
        fragment.number,
        archaeologyDto
      )
    })
    test('returns updated fragment', () =>
      expect(result).toEqual(expectedFragment))
    test('calls repository with correct parameters', () =>
      expect(fragmentRepository.updateArchaeology).toHaveBeenCalledWith(
        fragment.number,
        archaeologyDto
      ))
  })

  describe('update lemmatization', () => {
    const lemmatization: Lemmatization = new Lemmatization(
      ['1.'],
      [[new LemmatizationToken('kur', true, [])]]
    )

    beforeEach(async () => {
      fragmentRepository.updateLemmatization.mockReturnValue(
        Promise.resolve(fragment)
      )
      result = await fragmentService.updateLemmatization(
        fragment.number,
        lemmatization.toDto()
      )
    })

    test('Returns updated fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () =>
      expect(fragmentRepository.updateLemmatization).toHaveBeenCalledWith(
        fragment.number,
        lemmatization.toDto()
      ))
  })

  describe('update references', () => {
    beforeEach(async () => {
      fragmentRepository.updateReferences.mockReturnValue(
        Promise.resolve(fragment)
      )
      result = await fragmentService.updateReferences(
        fragment.number,
        fragment.references
      )
    })

    test('Returns updated fragment', () => expect(result).toEqual(fragment))
    test('Finds correct fragment', () =>
      expect(fragmentRepository.updateReferences).toHaveBeenCalledWith(
        fragment.number,
        fragment.references
      ))
  })
})

test('createLemmatization', async () => {
  const [text] = await createLemmatizationTestText()
  const lemmatization = new Lemmatization([], [])

  const createLemmatization = jest.fn<Promise<Lemmatization>, [Text]>()
  createLemmatization.mockReturnValue(Promise.resolve(lemmatization))
  const MockLemmatizationFactory = LemmatizationFactory as jest.Mock
  MockLemmatizationFactory.mockImplementation(() => ({
    createLemmatization,
  }))

  const result = await fragmentService.createLemmatization(text)
  expect(MockLemmatizationFactory).toHaveBeenCalledWith(
    fragmentService,
    wordRepository
  )
  expect(createLemmatization).toBeCalledWith(text)
  expect(result).toEqual(lemmatization)
})

describe('search for fragment in corpus', () => {
  const number = 'K.1'
  const manuscriptAttestation = [
    manuscriptAttestationFactory.build(
      {},
      {
        transient: { museumNumber: number },
      }
    ),
  ]
  let result: ManuscriptAttestation[]
  beforeEach(async () => {
    fragmentRepository.findInCorpus.mockReturnValue(
      Promise.resolve(manuscriptAttestation)
    )
    result = [...(await fragmentService.findInCorpus(number))]
  })
  test('returns attestation data', () => {
    expect(result).toEqual(manuscriptAttestation)
  })
  test('calls repository with correct parameters', () =>
    expect(fragmentRepository.findInCorpus).toHaveBeenCalled())
})

const queryTestCases = [
  { lemmas: 'ina I' },
  { lemmas: lemmas, queryOperator: 'and' },
  { lemmas: lemmas, queryOperator: 'or' },
  { lemmas: lemmas, queryOperator: 'line' },
  { lemmas: lemmas, queryOperator: 'phrase' },
  { bibId: 'id' },
  { bibId: 'id', pages: '42' },
  { transliteration: 'me lik\nkur kur' },
  { number: 'X.1' },
  {
    number: 'M.2',
    bibId: 'id',
    pages: '123',
    transliteration: 'ana',
    lemmas: 'šumma I+ina I+qanû I',
    lemmaOperator: 'line',
  },
]

const queryTestData: TestData<FragmentService>[] = queryTestCases.map(
  (parameters) =>
    new TestData(
      'query',
      [parameters],
      fragmentRepository.query,
      queryResultStub,
      [parameters],
      Promise.resolve(queryResultStub)
    )
)

describe('Query FragmentService', () =>
  testDelegation(fragmentService, queryTestData))

describe('Query by traditional references', () => {
  const fragment = fragmentFactory.build({
    traditionalReferences: ['text 1'],
  })
  const returnData = {
    items: [
      {
        traditionalReference: 'text 1',
        fragmentNumbers: [fragment.number],
      },
    ],
  }
  const expected = Promise.resolve(returnData)
  let result
  beforeEach(async () => {
    fragmentRepository.queryByTraditionalReferences.mockReturnValue(
      Promise.resolve(returnData)
    )
    result = fragmentService.queryByTraditionalReferences(['text 1'])
  })
  test('returns traditional reference to fragment numbers mapping data', () => {
    expect(result).toEqual(expected)
  })
  test('calls repository with correct parameters', () =>
    expect(
      fragmentRepository.queryByTraditionalReferences
    ).toHaveBeenCalledWith(['text 1']))
})