From 974e17ee3e84a6d4a9dd577455f33961c58d02e8 Mon Sep 17 00:00:00 2001 From: Jason Axley Date: Thu, 21 Aug 2025 10:36:04 -0700 Subject: [PATCH] Added additional unit tests for construction of objects containing deviceId property --- server/objects/files/AudioFile.js | 5 +- server/objects/files/EBookFile.js | 8 +- server/objects/files/LibraryFile.js | 3 + server/scanner/LibraryItemScanData.js | 10 +- server/scanner/LibraryItemScanner.js | 11 +- test/server/MockDatabase.js | 122 ++++++++++++++++++ .../objects/SimilarLibraryFileObjects.test.js | 112 +++++++++++++++- .../server/scanner/LibraryItemScanner.test.js | 71 +++++++++- test/server/scanner/LibraryScanner.test.js | 115 +---------------- 9 files changed, 333 insertions(+), 124 deletions(-) create mode 100644 test/server/MockDatabase.js diff --git a/server/objects/files/AudioFile.js b/server/objects/files/AudioFile.js index 17e4ffcb7..b5e3e2fd0 100644 --- a/server/objects/files/AudioFile.js +++ b/server/objects/files/AudioFile.js @@ -71,10 +71,13 @@ class AudioFile { } } + /** + * @param {{ index: any; ino: any; deviceId: any; metadata: any; addedAt: any; updatedAt: any; manuallyVerified: any; exclude: any; error: null; trackNumFromMeta: any; discNumFromMeta: any; trackNumFromFilename: any; cdNumFromFilename: undefined; discNumFromFilename: any; format: any; duration: any; bitRate: any; language: any; codec: null; timeBase: any; channels: any; channelLayout: any; chapters: any[]; embeddedCoverArt: null; metaTags: any; }} data + */ construct(data) { this.index = data.index this.ino = data.ino - this.deviceId = data.dev + this.deviceId = data.deviceId this.metadata = new FileMetadata(data.metadata || {}) this.addedAt = data.addedAt this.updatedAt = data.updatedAt diff --git a/server/objects/files/EBookFile.js b/server/objects/files/EBookFile.js index 37138bb5b..28ac5c16b 100644 --- a/server/objects/files/EBookFile.js +++ b/server/objects/files/EBookFile.js @@ -1,6 +1,9 @@ const FileMetadata = require('../metadata/FileMetadata') class EBookFile { + /** + * @param {{ ino: any; deviceId: any; isSupplementary?: boolean; addedAt?: number; updatedAt?: number; metadata?: { filename: string; ext: string; path: string; relPath: string; size: number; mtimeMs: number; ctimeMs: number; birthtimeMs: number; }; libraryFolderId?: any; libraryId?: any; mediaType?: any; mtimeMs?: any; ctimeMs?: any; birthtimeMs?: any; path?: any; relPath?: any; isFile?: any; mediaMetadata?: any; libraryFiles?: any; }} file + */ constructor(file) { this.ino = null this.deviceId = null @@ -14,9 +17,12 @@ class EBookFile { } } + /** + * @param {{ ino: any; deviceId: any; isSupplementary?: boolean | undefined; addedAt: any; updatedAt: any; metadata: any; libraryFolderId?: any; libraryId?: any; mediaType?: any; mtimeMs?: any; ctimeMs?: any; birthtimeMs?: any; path?: any; relPath?: any; isFile?: any; mediaMetadata?: any; libraryFiles?: any; ebookFormat?: any; }} file + */ construct(file) { this.ino = file.ino - this.deviceId = file.dev + this.deviceId = file.deviceId this.metadata = new FileMetadata(file.metadata) this.ebookFormat = file.ebookFormat || this.metadata.format this.addedAt = file.addedAt diff --git a/server/objects/files/LibraryFile.js b/server/objects/files/LibraryFile.js index 946f31516..f58da0914 100644 --- a/server/objects/files/LibraryFile.js +++ b/server/objects/files/LibraryFile.js @@ -4,6 +4,9 @@ const globals = require('../../utils/globals') const FileMetadata = require('../metadata/FileMetadata') class LibraryFile { + /** + * @param {{ ino: any; deviceId: any; metadata?: { filename: any; ext: any; path: any; relPath: any; size: any; mtimeMs: any; ctimeMs: any; birthtimeMs: any; } | { filename: string; ext: string; path: string; relPath: string; size: number; mtimeMs: number; ctimeMs: number; birthtimeMs: number; } | null; isSupplementary?: any; addedAt?: any; updatedAt?: any; fileType?: string; libraryFolderId?: any; libraryId?: any; mediaType?: any; mtimeMs?: any; ctimeMs?: any; birthtimeMs?: any; path?: any; relPath?: any; isFile?: any; mediaMetadata?: any; libraryFiles?: any; } | undefined} [file] + */ constructor(file) { this.ino = null this.deviceId = null diff --git a/server/scanner/LibraryItemScanData.js b/server/scanner/LibraryItemScanData.js index 2778495ca..f583b8278 100644 --- a/server/scanner/LibraryItemScanData.js +++ b/server/scanner/LibraryItemScanData.js @@ -5,11 +5,11 @@ const globals = require('../utils/globals') class LibraryItemScanData { /** - * @typedef LibraryFileModifiedObject + * @typedef {Object} LibraryFileModifiedObject * @property {LibraryItem.LibraryFileObject} old * @property {LibraryItem.LibraryFileObject} new + * @param {{ libraryFolderId: any; libraryId: any; mediaType: any; ino: any; deviceId: any; mtimeMs: any; ctimeMs: any; birthtimeMs: any; path: any; relPath: any; isFile: any; mediaMetadata: any; libraryFiles: any; }} data */ - constructor(data) { /** @type {string} */ this.libraryFolderId = data.libraryFolderId @@ -20,7 +20,7 @@ class LibraryItemScanData { /** @type {string} */ this.ino = data.ino /** @type {string} */ - this.deviceId = data.dev + this.deviceId = data.deviceId /** @type {number} */ this.mtimeMs = data.mtimeMs /** @type {number} */ @@ -179,8 +179,8 @@ class LibraryItemScanData { /** * * @param {LibraryItem} existingLibraryItem - * @param {import('./LibraryScan')} libraryScan - * @returns {boolean} true if changes found + * @param {import('./LibraryScan') | import('./ScanLogger')} libraryScan + * @returns {Promise} true if changes found */ async checkLibraryItemData(existingLibraryItem, libraryScan) { const keysToCompare = ['libraryFolderId', 'ino', 'deviceId', 'path', 'relPath', 'isFile'] diff --git a/server/scanner/LibraryItemScanner.js b/server/scanner/LibraryItemScanner.js index 4d55b4301..f39069480 100644 --- a/server/scanner/LibraryItemScanner.js +++ b/server/scanner/LibraryItemScanner.js @@ -23,7 +23,7 @@ class LibraryItemScanner { * Scan single library item * * @param {string} libraryItemId - * @param {{relPath:string, path:string}} [updateLibraryItemDetails] used by watcher when item folder was renamed + * @param {{relPath:string, path:string, isFile: boolean}} [updateLibraryItemDetails] used by watcher when item folder was renamed * @returns {number} ScanResult */ async scanLibraryItem(libraryItemId, updateLibraryItemDetails = null) { @@ -190,7 +190,7 @@ class LibraryItemScanner { * @param {import('../models/Library')} library * @param {import('../models/LibraryFolder')} folder * @param {boolean} isSingleMediaItem - * @returns {Promise} ScanResult + * @returns {Promise} ScanResult */ async scanPotentialNewLibraryItem(libraryItemPath, library, folder, isSingleMediaItem) { const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, isSingleMediaItem) @@ -209,6 +209,13 @@ class LibraryItemScanner { } module.exports = new LibraryItemScanner() +/** + * @param {{ path?: any; relPath?: any; mediaMetadata?: any; }} libraryItemData + * @param {import("../models/LibraryFolder")} folder + * @param {import("../models/Library")} library + * @param {boolean} isSingleMediaItem + * @param {LibraryFile[]} libraryFiles + */ async function buildLibraryItemScanData(libraryItemData, folder, library, isSingleMediaItem, libraryFiles) { const libraryItemStats = await fileUtils.getFileTimestampsWithIno(libraryItemData.path) return new LibraryItemScanData({ diff --git a/test/server/MockDatabase.js b/test/server/MockDatabase.js new file mode 100644 index 000000000..880eded69 --- /dev/null +++ b/test/server/MockDatabase.js @@ -0,0 +1,122 @@ +const Database = require('../../server/Database') +const { Sequelize } = require('sequelize') +const LibraryFile = require('../../server/objects/files/LibraryFile') +const fileUtils = require('../../server/utils/fileUtils') +const sinon = require('sinon') + +async function loadTestDatabase(mockFileInfo) { + let libraryItem1Id, libraryItem2Id + + let fileInfo = mockFileInfo || getMockFileInfo() + let bookLibraryFiles = fileInfo.keys().reduce((acc, key) => { + let bookfile = new LibraryFile() + bookfile.setDataFromPath(key, key) + acc.push(bookfile) + return acc + }, []) + + global.ServerSettings = {} + Database.sequelize = new Sequelize({ + dialect: 'sqlite', + storage: ':memory:', + // Choose one of the logging options + logging: (...msg) => console.log(msg), + logQueryParameters: true + }) + Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '') + await Database.buildModels() + + const newLibrary = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' }) + const newLibraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: newLibrary.id }) + const newLibraryFolder2 = await Database.libraryFolderModel.create({ path: '/mnt/drive', libraryId: newLibrary.id }) + + const newBook = await Database.bookModel.create({ title: 'Test Book', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const newLibraryItem = await Database.libraryItemModel.create(buildBookLibraryItemParams(bookLibraryFiles[0], newBook.id, newLibrary.id, newLibraryFolder.id)) + libraryItem1Id = newLibraryItem.id + + const newBook2 = await Database.bookModel.create({ title: 'Test Book 2', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const newLibraryItem2 = await Database.libraryItemModel.create(buildBookLibraryItemParams(bookLibraryFiles[1], newBook2.id, newLibrary.id, newLibraryFolder2.id)) + libraryItem2Id = newLibraryItem2.id + + return newLibrary +} +exports.loadTestDatabase = loadTestDatabase + +/** @returns {Map} */ +function getMockFileInfo() { + // @ts-ignore + return new Map([ + ['/test/file.pdf', { path: '/test/file.pdf', isDirectory: () => false, size: 1024, mtimeMs: Date.now(), ino: '1', dev: '100' }], + ['/mnt/drive/file-same-ino-different-dev.pdf', { path: '/mnt/drive/file-same-ino-different-dev.pdf', isDirectory: () => false, size: 42, mtimeMs: Date.now(), ino: '1', dev: '200' }] + ]) +} + +exports.getMockFileInfo = getMockFileInfo +/** @returns {Map} */ +// this has the same data as above except one file has been renamed +function getRenamedMockFileInfo() { + // @ts-ignore + return new Map([ + ['/test/file-renamed.pdf', { path: '/test/file-renamed.pdf', isDirectory: () => false, size: 1024, mtimeMs: Date.now(), ino: '1', dev: '100' }], + ['/mnt/drive/file-same-ino-different-dev.pdf', { path: '/mnt/drive/file-same-ino-different-dev.pdf', isDirectory: () => false, size: 42, mtimeMs: Date.now(), ino: '1', dev: '200' }] + ]) +} +exports.getRenamedMockFileInfo = getRenamedMockFileInfo + +/** + * @param {LibraryFile} libraryFile + * @param {any} bookId + * @param {string} libraryId + * @param {any} libraryFolderId + */ +function buildBookLibraryItemParams(libraryFile, bookId, libraryId, libraryFolderId) { + return { + path: libraryFile.metadata?.path, + isFile: true, + ino: libraryFile.ino, + deviceId: libraryFile.deviceId, + libraryFiles: [libraryFile.toJSON()], + mediaId: bookId, + mediaType: 'book', + libraryId: libraryId, + libraryFolderId: libraryFolderId + } +} +exports.buildBookLibraryItemParams = buildBookLibraryItemParams + +function stubFileUtils() { + let getInoStub, getDeviceIdStub, getFileTimestampsWithInoStub + getInoStub = sinon.stub(fileUtils, 'getIno') + getInoStub.callsFake((path) => { + const normalizedPath = fileUtils.filePathToPOSIX(path).replace(/\/$/, '') + const stats = getMockFileInfo().get(normalizedPath) + if (stats) { + return stats.ino + } else { + return null + } + }) + + getDeviceIdStub = sinon.stub(fileUtils, 'getDeviceId') + getDeviceIdStub.callsFake(async (path) => { + const normalizedPath = fileUtils.filePathToPOSIX(path).replace(/\/$/, '') + const stats = getMockFileInfo().get(normalizedPath) + if (stats) { + return stats.dev + } else { + return null + } + }) + + getFileTimestampsWithInoStub = sinon.stub(fileUtils, 'getFileTimestampsWithIno') + getFileTimestampsWithInoStub.callsFake(async (path) => { + const normalizedPath = fileUtils.filePathToPOSIX(path).replace(/\/$/, '') + const stats = getMockFileInfo().get(normalizedPath) + if (stats) { + return stats + } else { + return null + } + }) +} +exports.stubFileUtils = stubFileUtils diff --git a/test/server/objects/SimilarLibraryFileObjects.test.js b/test/server/objects/SimilarLibraryFileObjects.test.js index aa10465cd..0b9308fb0 100644 --- a/test/server/objects/SimilarLibraryFileObjects.test.js +++ b/test/server/objects/SimilarLibraryFileObjects.test.js @@ -1,9 +1,115 @@ +const chai = require('chai') +const expect = chai.expect +const sinon = require('sinon') + +const Path = require('path') +const Database = require('../../../server/Database') +const { loadTestDatabase, stubFileUtils, getMockFileInfo } = require('../MockDatabase') + +// TODO: all of these classes duplicate each other. const LibraryFile = require('../../../server/objects/files/LibraryFile') const EBookFile = require('../../../server/objects/files/EBookFile') const AudioFile = require('../../../server/objects/files/AudioFile') -const LibraryItem = require('../../../server/models/LibraryItem') const LibraryItemScanData = require('../../../server/scanner/LibraryItemScanData') +const FileMetadata = require('../../../server/objects/metadata/FileMetadata') -// TODO: all of these duplicate each other. Need to verify that deviceId is set on each when constructing. And that deviceId is populated when using toJSON() +const fileProperties = buildFileProperties() +const lf = new LibraryFile(fileProperties) +const ebf = new EBookFile(fileProperties) +const af = new AudioFile(fileProperties) -// TODO: check that any libraryFiles properties set to JSON contain a LibraryFile which has a deviceId property +describe('ObjectSetsDeviceIdWhenConstructed', function () { + this.timeout(0) + beforeEach(async () => { + stubFileUtils() + await loadTestDatabase() + }) + + afterEach(() => { + sinon.restore() + }) + + const lisd = new LibraryItemScanData(fileProperties) + + const objects = [lf, ebf, af, lisd] + + objects.forEach((obj) => { + it(`${obj.constructor.name}SetsDeviceIdWhenConstructed`, () => { + expect(obj.ino).to.equal(fileProperties.ino) + expect(obj.deviceId).to.equal(fileProperties.deviceId) + }) + }) + + it('LibraryItemSetsDeviceIdWhenConstructed', async () => { + const mockFileInfo = getMockFileInfo().get('/test/file.pdf') + + /** @type {import('../../../server/models/LibraryItem') | null} */ + const li = await Database.libraryItemModel.findOneExpanded({ + path: '/test/file.pdf' + }) + + expect(li?.ino).to.equal(mockFileInfo?.ino) + expect(li?.deviceId).to.equal(mockFileInfo?.dev) + }) + + it('LibraryFileJSONHasDeviceId', async () => { + const mockFileInfo = getMockFileInfo().get('/test/file.pdf') + + /** @type {import('../../../server/models/LibraryItem') | null} */ + const li = await Database.libraryItemModel.findOneExpanded({ + path: '/test/file.pdf' + }) + + const lf_json = li?.libraryFiles[0] + expect(lf_json).to.not.be.null + expect(lf_json?.deviceId).to.equal(mockFileInfo?.dev) + }) +}) + +describe('ObjectSetsDeviceIdWhenSerialized', () => { + const objects = [lf, ebf, af] + objects.forEach((obj) => { + it(`${obj.constructor.name}SetsDeviceIdWhenSerialized`, () => { + const obj_json = obj.toJSON() + expect(obj_json.ino).to.equal(fileProperties.ino) + expect(obj_json.deviceId).to.equal(fileProperties.deviceId) + }) + }) +}) + +/** @returns {{ libraryFolderId: any; libraryId: any; mediaType: any; ino: any; deviceId: any; mtimeMs: any; ctimeMs: any; birthtimeMs: any; path: any; relPath: any; isFile: any; mediaMetadata: any; libraryFiles: any; }} */ +function buildFileProperties() { + const path = '/tmp/foo.epub' + const metadata = new FileMetadata() + metadata.filename = Path.basename(path) + metadata.path = path + metadata.relPath = path + metadata.ext = Path.extname(path) + + return { + ino: '12345', + deviceId: '9876', + metadata: metadata, + isSupplementary: false, + addedAt: Date.now(), + updatedAt: Date.now() + } +} + +function buildLibraryItemProperties(fileProperties) { + return { + id: '7792E90F-D526-4636-8A38-EA8342E71FEE', + path: fileProperties.path, + relPath: fileProperties.path, + isFile: true, + ino: fileProperties.ino, + deviceId: fileProperties.dev, + libraryFiles: [], + mediaId: '7195803A-9974-46E4-A7D1-7A6E1AD7FD4B', + mediaType: 'book', + libraryId: '907DA361-67E4-47CF-9C67-C8E2E5CA1B15', + libraryFolderId: 'E2216F60-8ABF-4E55-BA83-AD077EB907F3', + createdAt: Date.now(), + updatedAt: Date.now() + } +} diff --git a/test/server/scanner/LibraryItemScanner.test.js b/test/server/scanner/LibraryItemScanner.test.js index 28c370b24..1fe2c8354 100644 --- a/test/server/scanner/LibraryItemScanner.test.js +++ b/test/server/scanner/LibraryItemScanner.test.js @@ -1 +1,70 @@ -// TODO: test buildLibraryItemScanData +const chai = require('chai') +const expect = chai.expect +const sinon = require('sinon') +const rewire = require('rewire') +const Path = require('path') + +const { stubFileUtils, getMockFileInfo, loadTestDatabase } = require('../MockDatabase') + +const LibraryFile = require('../../../server/objects/files/LibraryFile') +const FileMetadata = require('../../../server/objects/metadata/FileMetadata') +const LibraryFolder = require('../../../server/models/LibraryFolder') + +describe('buildLibraryItemScanData', () => { + let testLibrary = null + beforeEach(async () => { + stubFileUtils() + testLibrary = await loadTestDatabase() + }) + + afterEach(() => { + sinon.restore() + }) + + it('setsDeviceId', async () => { + const libraryItemScanner = rewire('../../../server/scanner/LibraryItemScanner') + + /** + * @param {{ path?: any; relPath?: any; mediaMetadata?: any; }} libraryItemData + * @param {import("../../../server/models/LibraryFolder")} folder + * @param {import("../../../server/models/Library")} library + * @param {boolean} isSingleMediaItem + * @param {LibraryFile[]} libraryFiles + * @return {import('../../../server/scanner/LibraryItemScanData') | null} + * */ + const buildLibraryItemScanData = libraryItemScanner.__get__('buildLibraryItemScanData') + + const mockFileInfo = getMockFileInfo().get('/test/file.pdf') + const lf = new LibraryFile() + var fileMetadata = new FileMetadata() + fileMetadata.setData(mockFileInfo) + fileMetadata.filename = Path.basename(mockFileInfo?.path) + fileMetadata.path = mockFileInfo?.path + fileMetadata.relPath = mockFileInfo?.path + fileMetadata.ext = Path.extname(mockFileInfo?.path) + lf.ino = mockFileInfo?.ino + lf.deviceId = mockFileInfo?.dev + lf.metadata = fileMetadata + lf.addedAt = Date.now() + lf.updatedAt = Date.now() + lf.metadata = fileMetadata + + const libraryItemData = { + path: mockFileInfo?.path, // full path + relPath: mockFileInfo?.path, // only filename + mediaMetadata: { + title: Path.basename(mockFileInfo?.path, Path.extname(mockFileInfo?.path)) + } + } + + const scanData = await buildLibraryItemScanData(libraryItemData, buildLibraryFolder(), testLibrary, true, [lf.toJSON()]) + + expect(scanData).to.not.be.null + expect(scanData.deviceId).to.equal(mockFileInfo?.dev) + }) +}) + +/** @return {import("../../../server/models/LibraryFolder")} folder */ +function buildLibraryFolder() { + return new LibraryFolder() +} diff --git a/test/server/scanner/LibraryScanner.test.js b/test/server/scanner/LibraryScanner.test.js index 06b7203a6..251f2b52d 100644 --- a/test/server/scanner/LibraryScanner.test.js +++ b/test/server/scanner/LibraryScanner.test.js @@ -3,49 +3,18 @@ const expect = chai.expect const sinon = require('sinon') const rewire = require('rewire') const fileUtils = require('../../../server/utils/fileUtils') -const Database = require('../../../server/Database') -const { Sequelize } = require('sequelize') const LibraryFile = require('../../../server/objects/files/LibraryFile') const LibraryItem = require('../../../server/models/LibraryItem') const FileMetadata = require('../../../server/objects/metadata/FileMetadata') const Path = require('path') +const Database = require('../../../server/Database') +const { stubFileUtils, loadTestDatabase, getMockFileInfo, getRenamedMockFileInfo, buildBookLibraryItemParams } = require('../MockDatabase') describe('LibraryScanner', () => { - let getInoStub, getDeviceIdStub, getFileTimestampsWithInoStub, LibraryScanner, testLibrary + let LibraryScanner, testLibrary beforeEach(async () => { - getInoStub = sinon.stub(fileUtils, 'getIno') - getInoStub.callsFake((path) => { - const normalizedPath = fileUtils.filePathToPOSIX(path).replace(/\/$/, '') - const stats = getMockFileInfo().get(normalizedPath) - if (stats) { - return stats.ino - } else { - return null - } - }) - - getDeviceIdStub = sinon.stub(fileUtils, 'getDeviceId') - getDeviceIdStub.callsFake(async (path) => { - const normalizedPath = fileUtils.filePathToPOSIX(path).replace(/\/$/, '') - const stats = getMockFileInfo().get(normalizedPath) - if (stats) { - return stats.dev - } else { - return null - } - }) - - getFileTimestampsWithInoStub = sinon.stub(fileUtils, 'getFileTimestampsWithIno') - getFileTimestampsWithInoStub.callsFake(async (path) => { - const normalizedPath = fileUtils.filePathToPOSIX(path).replace(/\/$/, '') - const stats = getMockFileInfo().get(normalizedPath) - if (stats) { - return stats - } else { - return null - } - }) + stubFileUtils() LibraryScanner = rewire('../../../server/scanner/LibraryScanner') }) @@ -245,79 +214,3 @@ describe('LibraryScanner', () => { let ItemToItemInoMatch = LibraryScanner.__get__('ItemToItemInoMatch') }) }) - -async function loadTestDatabase(mockFileInfo) { - let libraryItem1Id, libraryItem2Id - - let fileInfo = mockFileInfo || getMockFileInfo() - let bookLibraryFiles = fileInfo.keys().reduce((acc, key) => { - let bookfile = new LibraryFile() - bookfile.setDataFromPath(key, key) - acc.push(bookfile) - return acc - }, []) - - global.ServerSettings = {} - Database.sequelize = new Sequelize({ - dialect: 'sqlite', - storage: ':memory:', - // Choose one of the logging options - logging: (...msg) => console.log(msg), - logQueryParameters: true - }) - Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '') - await Database.buildModels() - - const newLibrary = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' }) - const newLibraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: newLibrary.id }) - const newLibraryFolder2 = await Database.libraryFolderModel.create({ path: '/mnt/drive', libraryId: newLibrary.id }) - - const newBook = await Database.bookModel.create({ title: 'Test Book', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) - const newLibraryItem = await Database.libraryItemModel.create(buildBookLibraryItemParams(bookLibraryFiles[0], newBook.id, newLibrary.id, newLibraryFolder.id)) - libraryItem1Id = newLibraryItem.id - - const newBook2 = await Database.bookModel.create({ title: 'Test Book 2', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) - const newLibraryItem2 = await Database.libraryItemModel.create(buildBookLibraryItemParams(bookLibraryFiles[1], newBook2.id, newLibrary.id, newLibraryFolder2.id)) - libraryItem2Id = newLibraryItem2.id - - return newLibrary -} - -/** - * @param {LibraryFile} libraryFile - * @param {any} bookId - * @param {string} libraryId - * @param {any} libraryFolderId - */ -function buildBookLibraryItemParams(libraryFile, bookId, libraryId, libraryFolderId) { - return { - path: libraryFile.metadata.path, - isFile: true, - ino: libraryFile.ino, - deviceId: libraryFile.deviceId, - libraryFiles: [libraryFile.toJSON()], - mediaId: bookId, - mediaType: 'book', - libraryId: libraryId, - libraryFolderId: libraryFolderId - } -} - -/** @returns {Map} */ -function getMockFileInfo() { - // @ts-ignore - return new Map([ - ['/test/file.pdf', { path: '/test/file.pdf', isDirectory: () => false, size: 1024, mtimeMs: Date.now(), ino: '1', dev: '100' }], - ['/mnt/drive/file-same-ino-different-dev.pdf', { path: '/mnt/drive/file-same-ino-different-dev.pdf', isDirectory: () => false, size: 42, mtimeMs: Date.now(), ino: '1', dev: '200' }] - ]) -} - -/** @returns {Map} */ -// this has the same data as above except one file has been renamed -function getRenamedMockFileInfo() { - // @ts-ignore - return new Map([ - ['/test/file-renamed.pdf', { path: '/test/file-renamed.pdf', isDirectory: () => false, size: 1024, mtimeMs: Date.now(), ino: '1', dev: '100' }], - ['/mnt/drive/file-same-ino-different-dev.pdf', { path: '/mnt/drive/file-same-ino-different-dev.pdf', isDirectory: () => false, size: 42, mtimeMs: Date.now(), ino: '1', dev: '200' }] - ]) -}