mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-22 00:07:52 +01:00
Test BookFinder.js using mocha
This commit is contained in:
parent
d1671f0ddc
commit
e8c14dbb58
6395
package-lock.json
generated
6395
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -16,7 +16,7 @@
|
|||||||
"docker-arm64-local": "docker buildx build --platform linux/arm64 --load . -t advplyr/audiobookshelf-arm64-local",
|
"docker-arm64-local": "docker buildx build --platform linux/arm64 --load . -t advplyr/audiobookshelf-arm64-local",
|
||||||
"docker-armv7-local": "docker buildx build --platform linux/arm/v7 --load . -t advplyr/audiobookshelf-armv7-local",
|
"docker-armv7-local": "docker buildx build --platform linux/arm/v7 --load . -t advplyr/audiobookshelf-armv7-local",
|
||||||
"deploy-linux": "node deploy/linux",
|
"deploy-linux": "node deploy/linux",
|
||||||
"test": "jest"
|
"test": "mocha"
|
||||||
},
|
},
|
||||||
"bin": "prod.js",
|
"bin": "prod.js",
|
||||||
"pkg": {
|
"pkg": {
|
||||||
@ -29,6 +29,9 @@
|
|||||||
"server/**/*.js"
|
"server/**/*.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"mocha": {
|
||||||
|
"recursive": true
|
||||||
|
},
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -45,7 +48,9 @@
|
|||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^29.7.0",
|
"chai": "^4.3.10",
|
||||||
"nodemon": "^2.0.20"
|
"mocha": "^10.2.0",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"sinon": "^17.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,315 +0,0 @@
|
|||||||
const bookFinder = require('./BookFinder')
|
|
||||||
const Audnexus = require('../providers/Audnexus')
|
|
||||||
const { LogLevel } = require('../utils/constants')
|
|
||||||
const Logger = require('../Logger')
|
|
||||||
jest.mock('../providers/Audnexus')
|
|
||||||
|
|
||||||
Logger.setLogLevel(LogLevel.INFO)
|
|
||||||
|
|
||||||
describe('TitleCandidates', () => {
|
|
||||||
describe('cleanAuthor non-empty', () => {
|
|
||||||
let titleCandidates
|
|
||||||
let cleanAuthor = 'leo tolstoy'
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('single add', () => {
|
|
||||||
it.each([
|
|
||||||
['adds a clean title to candidates', 'anna karenina', ['anna karenina']],
|
|
||||||
['lowercases candidate title', 'ANNA KARENINA', ['anna karenina']],
|
|
||||||
['removes author name from title', `anna karenina by ${cleanAuthor}`, ['anna karenina']],
|
|
||||||
['removes author name title', cleanAuthor, []],
|
|
||||||
['cleans subtitle from title', 'anna karenina: subtitle', ['anna karenina']],
|
|
||||||
['removes "by ..." from title', 'anna karenina by arnold schwarzenegger', ['anna karenina', 'anna karenina by arnold schwarzenegger']],
|
|
||||||
['removes bitrate from title', 'anna karenina 64kbps', ['anna karenina', 'anna karenina 64kbps']],
|
|
||||||
['removes edition from title 1', 'anna karenina 2nd edition', ['anna karenina', 'anna karenina 2nd edition']],
|
|
||||||
['removes edition from title 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']],
|
|
||||||
['removes file-type from title', 'anna karenina.mp3', ['anna karenina', 'anna karenina.mp3']],
|
|
||||||
['removes "a novel" from title', 'anna karenina a novel', ['anna karenina', 'anna karenina a novel']],
|
|
||||||
['removes preceding/trailing numbers from title', '1 anna karenina 2', ['anna karenina', '1 anna karenina 2']],
|
|
||||||
['does not add empty title', '', []],
|
|
||||||
['does not add title with only spaces', ' ', []],
|
|
||||||
['adds digit-only title, but not its empty string transformation', '1984', ['1984']],
|
|
||||||
])('%s', (_, title, expected) => {
|
|
||||||
titleCandidates.add(title)
|
|
||||||
expect(titleCandidates.getCandidates()).toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('multi add', () => {
|
|
||||||
it.each([
|
|
||||||
['digits-only candidates get lower priority', ['01', 'anna karenina'], ['anna karenina', '01']],
|
|
||||||
['transformed candidates get higher priority', ['title1 1', 'title2 1'], ['title1', 'title2', 'title1 1', 'title2 1']],
|
|
||||||
['other candidates are ordered by position', ['title1', 'title2'], ['title1', 'title2']],
|
|
||||||
['author candidate is removed', ['title1', cleanAuthor], ['title1']],
|
|
||||||
])('%s', (_, titles, expected) => {
|
|
||||||
for (const title of titles) titleCandidates.add(title)
|
|
||||||
expect(titleCandidates.getCandidates()).toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('cleanAuthor empty', () => {
|
|
||||||
let titleCandidates
|
|
||||||
let cleanAuthor = ''
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('single add', () => {
|
|
||||||
it.each([
|
|
||||||
['does not remove author name', 'leo tolstoy', ['leo tolstoy']],
|
|
||||||
])('%s', (_, title, expected) => {
|
|
||||||
titleCandidates.add(title)
|
|
||||||
expect(titleCandidates.getCandidates()).toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe('AuthorCandidates', () => {
|
|
||||||
let authorCandidates
|
|
||||||
const audnexus = new Audnexus()
|
|
||||||
audnexus.authorASINsRequest.mockResolvedValue([
|
|
||||||
{ name: 'Leo Tolstoy' },
|
|
||||||
{ name: 'Nikolai Gogol' },
|
|
||||||
{ name: 'J. K. Rowling' },
|
|
||||||
])
|
|
||||||
|
|
||||||
describe('cleanAuthor is null', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
authorCandidates = new bookFinder.constructor.AuthorCandidates(null, audnexus)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('no add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns empty author', []],
|
|
||||||
])('%s', async (_, expected) => {
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('single add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns valid author', 'nikolai gogol', ['nikolai gogol']],
|
|
||||||
['does not return invalid author (not in list)', 'fyodor dostoevsky', []],
|
|
||||||
['returns valid author (valid is a substring of added)', 'dr. nikolai gogol', ['nikolai gogol']],
|
|
||||||
['returns added author (added is a substring of valid)', 'gogol', ['gogol']],
|
|
||||||
['returns valid author (added is similar to valid)', 'nicolai gogol', ['nikolai gogol']],
|
|
||||||
['does not return invalid author (added too distant)', 'nikolai google', []],
|
|
||||||
['returns valid author (contains redundant spaces)', 'nikolai gogol', ['nikolai gogol']],
|
|
||||||
['returns valid author (normalized initials)', 'j.k. rowling', ['j. k. rowling']],
|
|
||||||
])('%s', async (_, author, expected) => {
|
|
||||||
authorCandidates.add(author)
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('multi add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns valid authors', ['nikolai gogol', 'leo tolstoy'], ['nikolai gogol', 'leo tolstoy']],
|
|
||||||
['returns deduped valid authors', ['nikolai gogol', 'nikolai gogol'], ['nikolai gogol']],
|
|
||||||
])('%s', async (_, authors, expected) => {
|
|
||||||
for (const author of authors) authorCandidates.add(author)
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('cleanAuthor is valid', () => {
|
|
||||||
const cleanAuthor = 'leo tolstoy'
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('no add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns clean author from constructor', [cleanAuthor]],
|
|
||||||
])('%s', async (_, expected) => {
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('single add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns cleanAuthor + valid author', 'nikolai gogol', [cleanAuthor, 'nikolai gogol']],
|
|
||||||
['returns deduplicated author', cleanAuthor, [cleanAuthor]],
|
|
||||||
])('%s', async (_, author, expected) => {
|
|
||||||
authorCandidates.add(author)
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe('cleanAuthor is invalid', () => {
|
|
||||||
const cleanAuthor = 'fyodor dostoevsky'
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('no add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns invalid clean author from constructor', [cleanAuthor]],
|
|
||||||
])('%s', async (_, expected) => {
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('single add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns only valid author', 'nikolai gogol', ['nikolai gogol']],
|
|
||||||
])('%s', async (_, author, expected) => {
|
|
||||||
authorCandidates.add(author)
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('cleanAuthor is invalid and dirty', () => {
|
|
||||||
describe('no add', () => {
|
|
||||||
it.each([
|
|
||||||
['returns invalid aggressively cleanAuthor from constructor', 'fyodor dostoevsky, translated by jackie chan', ['fyodor dostoevsky']],
|
|
||||||
['returns invalid cleanAuthor from constructor (empty after aggressive ckean)', ', jackie chan', [', jackie chan']],
|
|
||||||
])('%s', async (_, cleanAuthor, expected) => {
|
|
||||||
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus)
|
|
||||||
expect(await authorCandidates.getCandidates()).toEqual([...expected, ''])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('search', () => {
|
|
||||||
const t = 'title'
|
|
||||||
const a = 'author'
|
|
||||||
const u = 'unknown'
|
|
||||||
const r = ['book']
|
|
||||||
|
|
||||||
bookFinder.runSearch = jest.fn((searchTitle, searchAuthor) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolve(searchTitle == t && (searchAuthor == a || searchAuthor == u) ? r : [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const audnexus = new Audnexus()
|
|
||||||
audnexus.authorASINsRequest.mockResolvedValue([
|
|
||||||
{ name: a },
|
|
||||||
])
|
|
||||||
bookFinder.audnexus = audnexus
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
bookFinder.runSearch.mockClear()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('no or empty title', () => {
|
|
||||||
it('returns empty result', async () => {
|
|
||||||
expect(await bookFinder.search('', '', a)).toEqual([])
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('exact valid title and exact valid author', () => {
|
|
||||||
it('returns result (no fuzzy searches)', async () => {
|
|
||||||
expect(await bookFinder.search('', t, a)).toEqual(r)
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('contains valid title and exact valid author', () => {
|
|
||||||
it.each([
|
|
||||||
[`${t} -`],
|
|
||||||
[`${t} - ${a}`],
|
|
||||||
[`${a} - ${t}`],
|
|
||||||
[`${t}- ${a}`],
|
|
||||||
[`${t} -${a}`],
|
|
||||||
[`${t} ${a}`],
|
|
||||||
[`${a} - ${t} (unabridged)`],
|
|
||||||
[`${a} - ${t} (subtitle) - mp3`],
|
|
||||||
[`${t} {narrator} - series-01 64kbps 10:00:00`],
|
|
||||||
[`${a} - ${t} (2006) narrated by narrator [unabridged]`],
|
|
||||||
[`${t} - ${a} 2022 mp3`],
|
|
||||||
[`01 ${t}`],
|
|
||||||
[`2022_${t}_HQ`],
|
|
||||||
])(`returns result ('%s', '${a}') (1 fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, a)).toEqual(r)
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[`s-01 - ${t} (narrator) 64kbps 10:00:00`],
|
|
||||||
[`${a} - series 01 - ${t}`],
|
|
||||||
])(`returns result ('%s', '${a}') (2 fuzzy searches)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, a)).toEqual(r)
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(3)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[`${t}-${a}`],
|
|
||||||
[`${t} junk`],
|
|
||||||
])(`returns empty result ('%s', '${a}')`, async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, a)).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('maxFuzzySearches = 0', () => {
|
|
||||||
it.each([
|
|
||||||
[`${t} - ${a}`],
|
|
||||||
])(`returns empty result ('%s', '${a}') (no fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 0 })).toEqual([])
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('maxFuzzySearches = 1', () => {
|
|
||||||
it.each([
|
|
||||||
[`s-01 - ${t} (narrator) 64kbps 10:00:00`],
|
|
||||||
[`${a} - series 01 - ${t}`],
|
|
||||||
])(`returns empty result ('%s', '${a}') (1 fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 1 })).toEqual([])
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('contains valid title and no author', () => {
|
|
||||||
it.each([
|
|
||||||
[`${t} - ${a}`],
|
|
||||||
[`${a} - ${t}`],
|
|
||||||
])(`returns result ('%s', '') (1 fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, '')).toEqual(r)
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[`${t}`],
|
|
||||||
[`${t} - ${u}`],
|
|
||||||
[`${u} - ${t}`],
|
|
||||||
])(`returns empty result ('%s', '') (no fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, '')).toEqual([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('contains valid title and unknown author', () => {
|
|
||||||
it.each([
|
|
||||||
[`${t} - ${u}`],
|
|
||||||
[`${u} - ${t}`],
|
|
||||||
])(`returns result ('%s', '') (1 fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, u)).toEqual(r)
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[`${t}`],
|
|
||||||
])(`returns result ('%s', '') (no fuzzy search)` , async (searchTitle) => {
|
|
||||||
expect(await bookFinder.search('', searchTitle, u)).toEqual(r)
|
|
||||||
expect(bookFinder.runSearch).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
344
test/server/finders/BookFinder.test.js
Normal file
344
test/server/finders/BookFinder.test.js
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
const sinon = require('sinon');
|
||||||
|
const chai = require('chai');
|
||||||
|
const expect = chai.expect;
|
||||||
|
const bookFinder = require('../../../server/finders/BookFinder');
|
||||||
|
const { LogLevel } = require('../../../server/utils/constants')
|
||||||
|
const Logger = require('../../../server/Logger')
|
||||||
|
Logger.setLogLevel(LogLevel.INFO)
|
||||||
|
|
||||||
|
describe('TitleCandidates', () => {
|
||||||
|
describe('cleanAuthor non-empty', () => {
|
||||||
|
let titleCandidates;
|
||||||
|
const cleanAuthor = 'leo tolstoy';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no adds', () => {
|
||||||
|
it('returns no candidates', () => {
|
||||||
|
expect(titleCandidates.getCandidates()).to.deep.equal([]);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('single add', () => {
|
||||||
|
[
|
||||||
|
['adds candidate', 'anna karenina', ['anna karenina']],
|
||||||
|
['adds lowercased candidate', 'ANNA KARENINA', ['anna karenina']],
|
||||||
|
['adds candidate, removing redundant spaces', 'anna karenina', ['anna karenina']],
|
||||||
|
['adds candidate, removing author', `anna karenina by ${cleanAuthor}`, ['anna karenina']],
|
||||||
|
['does not add empty candidate after removing author', cleanAuthor, []],
|
||||||
|
['adds candidate, removing subtitle', 'anna karenina: subtitle', ['anna karenina']],
|
||||||
|
['adds candidate + variant, removing "by ..."', 'anna karenina by arnold schwarzenegger', ['anna karenina', 'anna karenina by arnold schwarzenegger']],
|
||||||
|
['adds candidate + variant, removing bitrate', 'anna karenina 64kbps', ['anna karenina', 'anna karenina 64kbps']],
|
||||||
|
['adds candidate + variant, removing edition 1', 'anna karenina 2nd edition', ['anna karenina', 'anna karenina 2nd edition']],
|
||||||
|
['adds candidate + variant, removing edition 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']],
|
||||||
|
['adds candidate + variant, removing fie type', 'anna karenina.mp3', ['anna karenina', 'anna karenina.mp3']],
|
||||||
|
['adds candidate + variant, removing "a novel"', 'anna karenina a novel', ['anna karenina', 'anna karenina a novel']],
|
||||||
|
['adds candidate + variant, removing preceding/trailing numbers', '1 anna karenina 2', ['anna karenina', '1 anna karenina 2']],
|
||||||
|
['does not add empty candidate', '', []],
|
||||||
|
['does not add spaces-only candidate', ' ', []],
|
||||||
|
['does not add empty variant', '1984', ['1984']],
|
||||||
|
].forEach(([name, title, expected]) => it(name, () => {
|
||||||
|
titleCandidates.add(title);
|
||||||
|
expect(titleCandidates.getCandidates()).to.deep.equal(expected);
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('multiple adds', () => {
|
||||||
|
[
|
||||||
|
['demotes digits-only candidates', ['01', 'anna karenina'], ['anna karenina', '01']],
|
||||||
|
['promotes transformed variants', ['title1 1', 'title2 1'], ['title1', 'title2', 'title1 1', 'title2 1']],
|
||||||
|
['orders by position', ['title2', 'title1'], ['title2', 'title1']],
|
||||||
|
['dedupes candidates', ['title1', 'title1'], ['title1']],
|
||||||
|
].forEach(([name, titles, expected]) => it(name, () => {
|
||||||
|
for (const title of titles) titleCandidates.add(title)
|
||||||
|
expect(titleCandidates.getCandidates()).to.deep.equal(expected);
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('cleanAuthor empty', () => {
|
||||||
|
let titleCandidates
|
||||||
|
let cleanAuthor = ''
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('single add', () => {
|
||||||
|
[
|
||||||
|
['adds a candidate', 'leo tolstoy', ['leo tolstoy']],
|
||||||
|
].forEach(([name, title, expected]) => it(name, () => {
|
||||||
|
titleCandidates.add(title)
|
||||||
|
expect(titleCandidates.getCandidates()).to.deep.equal(expected);
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('AuthorCandidates', () => {
|
||||||
|
let authorCandidates;
|
||||||
|
const audnexus = {
|
||||||
|
authorASINsRequest: sinon.stub().resolves([
|
||||||
|
{ name: 'Leo Tolstoy' },
|
||||||
|
{ name: 'Nikolai Gogol' },
|
||||||
|
{ name: 'J. K. Rowling' },
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('cleanAuthor is null', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authorCandidates = new bookFinder.constructor.AuthorCandidates(null, audnexus);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no adds', () => {
|
||||||
|
[
|
||||||
|
['returns empty author candidate', []],
|
||||||
|
].forEach(([name, expected]) => it(name, async () => {
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('single add', () => {
|
||||||
|
[
|
||||||
|
['adds recognized candidate', 'nikolai gogol', ['nikolai gogol']],
|
||||||
|
['does not add unrecognized candidate', 'fyodor dostoevsky', []],
|
||||||
|
['adds recognized author if candidate is a superstring', 'dr. nikolai gogol', ['nikolai gogol']],
|
||||||
|
['adds candidate if it is a substring of recognized author', 'gogol', ['gogol']],
|
||||||
|
['adds recognized author if edit distance from candidate is small', 'nicolai gogol', ['nikolai gogol']],
|
||||||
|
['does not add candidate if edit distance from any recognized author is large', 'nikolai google', []],
|
||||||
|
['adds normalized recognized candidate (contains redundant spaces)', 'nikolai gogol', ['nikolai gogol']],
|
||||||
|
['adds normalized recognized candidate (normalized initials)', 'j.k. rowling', ['j. k. rowling']],
|
||||||
|
].forEach(([name, author, expected]) => it(name, async () => {
|
||||||
|
authorCandidates.add(author)
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('multi add', () => {
|
||||||
|
[
|
||||||
|
['adds recognized author candidates', ['nikolai gogol', 'leo tolstoy'], ['nikolai gogol', 'leo tolstoy']],
|
||||||
|
['dedupes author candidates', ['nikolai gogol', 'nikolai gogol'], ['nikolai gogol']],
|
||||||
|
].forEach(([name, authors, expected]) => it(name, async () => {
|
||||||
|
for (const author of authors) authorCandidates.add(author)
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cleanAuthor is a recognized author', () => {
|
||||||
|
const cleanAuthor = 'leo tolstoy';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no adds', () => {
|
||||||
|
[
|
||||||
|
['adds cleanAuthor as candidate', [cleanAuthor]],
|
||||||
|
].forEach(([name, expected]) => it(name, async () => {
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('single add', () => {
|
||||||
|
[
|
||||||
|
['adds recognized candidate', 'nikolai gogol', [cleanAuthor, 'nikolai gogol']],
|
||||||
|
['does not add candidate if it is a dupe of cleanAuthor', cleanAuthor, [cleanAuthor]],
|
||||||
|
].forEach(([name, author, expected]) => it(name, async () => {
|
||||||
|
authorCandidates.add(author)
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cleanAuthor is an unrecognized author', () => {
|
||||||
|
const cleanAuthor = 'Fyodor Dostoevsky';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no adds', () => {
|
||||||
|
[
|
||||||
|
['adds cleanAuthor as candidate', [cleanAuthor]],
|
||||||
|
].forEach(([name, expected]) => it(name, async () => {
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('single add', () => {
|
||||||
|
[
|
||||||
|
['adds recognized candidate and removes cleanAuthor', 'nikolai gogol', ['nikolai gogol']],
|
||||||
|
['does not add unrecognized candidate', 'jackie chan', [cleanAuthor]],
|
||||||
|
].forEach(([name, author, expected]) => it(name, async () => {
|
||||||
|
authorCandidates.add(author)
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cleanAuthor is unrecognized and dirty', () => {
|
||||||
|
describe('no adds', () => {
|
||||||
|
[
|
||||||
|
['adds aggressively cleaned cleanAuthor', 'fyodor dostoevsky, translated by jackie chan', ['fyodor dostoevsky']],
|
||||||
|
['adds cleanAuthor if aggresively cleaned cleanAuthor is empty', ', jackie chan', [', jackie chan']],
|
||||||
|
].forEach(([name, cleanAuthor, expected]) => it(name, async () => {
|
||||||
|
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus)
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('single add', () => {
|
||||||
|
[
|
||||||
|
['adds recognized candidate and removes cleanAuthor', 'fyodor dostoevsky, translated by jackie chan', 'nikolai gogol', ['nikolai gogol']],
|
||||||
|
].forEach(([name, cleanAuthor, author, expected]) => it(name, async () => {
|
||||||
|
authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus)
|
||||||
|
authorCandidates.add(author)
|
||||||
|
expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, ''])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
const t = 'title';
|
||||||
|
const a = 'author';
|
||||||
|
const u = 'unrecognized';
|
||||||
|
const r = ['book'];
|
||||||
|
|
||||||
|
const runSearchStub = sinon.stub(bookFinder, 'runSearch')
|
||||||
|
runSearchStub.resolves([])
|
||||||
|
runSearchStub.withArgs(t, a).resolves(r);
|
||||||
|
runSearchStub.withArgs(t, u).resolves(r);
|
||||||
|
|
||||||
|
const audnexusStub = sinon.stub(bookFinder.audnexus, 'authorASINsRequest')
|
||||||
|
audnexusStub.resolves([ { name: a } ])
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bookFinder.runSearch.resetHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search title is empty', () => {
|
||||||
|
it('returns empty result', async () => {
|
||||||
|
expect(await bookFinder.search('', '', a)).to.deep.equal([]);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search title is a recognized title and search author is a recognized author', () => {
|
||||||
|
it('returns non-empty result (no fuzzy searches)', async () => {
|
||||||
|
expect(await bookFinder.search('', t, a)).to.deep.equal(r);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search title contains recognized title and search author is a recognized author', () => {
|
||||||
|
[
|
||||||
|
[`${t} -`],
|
||||||
|
[`${t} - ${a}`],
|
||||||
|
[`${a} - ${t}`],
|
||||||
|
[`${t}- ${a}`],
|
||||||
|
[`${t} -${a}`],
|
||||||
|
[`${t} ${a}`],
|
||||||
|
[`${a} - ${t} (unabridged)`],
|
||||||
|
[`${a} - ${t} (subtitle) - mp3`],
|
||||||
|
[`${t} {narrator} - series-01 64kbps 10:00:00`],
|
||||||
|
[`${a} - ${t} (2006) narrated by narrator [unabridged]`],
|
||||||
|
[`${t} - ${a} 2022 mp3`],
|
||||||
|
[`01 ${t}`],
|
||||||
|
[`2022_${t}_HQ`],
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${a}') returns non-empty result (with 1 fuzzy search)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
[`s-01 - ${t} (narrator) 64kbps 10:00:00`],
|
||||||
|
[`${a} - series 01 - ${t}`],
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${a}') returns non-empty result (with 2 fuzzy searches)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
[`${t}-${a}`],
|
||||||
|
[`${t} junk`],
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${a}') returns an empty result`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, a)).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxFuzzySearches = 0', () => {
|
||||||
|
[
|
||||||
|
[`${t} - ${a}`],
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${a}') returns an empty result (with no fuzzy searches)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 0 })).to.deep.equal([]);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxFuzzySearches = 1', () => {
|
||||||
|
[
|
||||||
|
[`s-01 - ${t} (narrator) 64kbps 10:00:00`],
|
||||||
|
[`${a} - series 01 - ${t}`],
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${a}') returns an empty result (1 fuzzy search)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 1 })).to.deep.equal([]);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search title contains recognized title and search author is empty', () => {
|
||||||
|
[
|
||||||
|
[`${t} - ${a}`],
|
||||||
|
[`${a} - ${t}`],
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '') returns a non-empty result (1 fuzzy search)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, '')).to.deep.equal(r);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
[`${t}`],
|
||||||
|
[`${t} - ${u}`],
|
||||||
|
[`${u} - ${t}`]
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '') returns an empty result`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, '')).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search title contains recognized title and search author is an unrecognized author', () => {
|
||||||
|
[
|
||||||
|
[`${t} - ${u}`],
|
||||||
|
[`${u} - ${t}`]
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${u}') returns a non-empty result (1 fuzzy search)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
[`${t}`]
|
||||||
|
].forEach(([searchTitle]) => {
|
||||||
|
it(`search('${searchTitle}', '${u}') returns a non-empty result (no fuzzy search)`, async () => {
|
||||||
|
expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r);
|
||||||
|
sinon.assert.callCount(bookFinder.runSearch, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user