mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-09-10 17:58:02 +02:00
Added additional unit tests for construction of objects containing deviceId property
This commit is contained in:
parent
3a4aacb7bf
commit
974e17ee3e
@ -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) {
|
construct(data) {
|
||||||
this.index = data.index
|
this.index = data.index
|
||||||
this.ino = data.ino
|
this.ino = data.ino
|
||||||
this.deviceId = data.dev
|
this.deviceId = data.deviceId
|
||||||
this.metadata = new FileMetadata(data.metadata || {})
|
this.metadata = new FileMetadata(data.metadata || {})
|
||||||
this.addedAt = data.addedAt
|
this.addedAt = data.addedAt
|
||||||
this.updatedAt = data.updatedAt
|
this.updatedAt = data.updatedAt
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
const FileMetadata = require('../metadata/FileMetadata')
|
const FileMetadata = require('../metadata/FileMetadata')
|
||||||
|
|
||||||
class EBookFile {
|
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) {
|
constructor(file) {
|
||||||
this.ino = null
|
this.ino = null
|
||||||
this.deviceId = 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) {
|
construct(file) {
|
||||||
this.ino = file.ino
|
this.ino = file.ino
|
||||||
this.deviceId = file.dev
|
this.deviceId = file.deviceId
|
||||||
this.metadata = new FileMetadata(file.metadata)
|
this.metadata = new FileMetadata(file.metadata)
|
||||||
this.ebookFormat = file.ebookFormat || this.metadata.format
|
this.ebookFormat = file.ebookFormat || this.metadata.format
|
||||||
this.addedAt = file.addedAt
|
this.addedAt = file.addedAt
|
||||||
|
@ -4,6 +4,9 @@ const globals = require('../../utils/globals')
|
|||||||
const FileMetadata = require('../metadata/FileMetadata')
|
const FileMetadata = require('../metadata/FileMetadata')
|
||||||
|
|
||||||
class LibraryFile {
|
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) {
|
constructor(file) {
|
||||||
this.ino = null
|
this.ino = null
|
||||||
this.deviceId = null
|
this.deviceId = null
|
||||||
|
@ -5,11 +5,11 @@ const globals = require('../utils/globals')
|
|||||||
|
|
||||||
class LibraryItemScanData {
|
class LibraryItemScanData {
|
||||||
/**
|
/**
|
||||||
* @typedef LibraryFileModifiedObject
|
* @typedef {Object} LibraryFileModifiedObject
|
||||||
* @property {LibraryItem.LibraryFileObject} old
|
* @property {LibraryItem.LibraryFileObject} old
|
||||||
* @property {LibraryItem.LibraryFileObject} new
|
* @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) {
|
constructor(data) {
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.libraryFolderId = data.libraryFolderId
|
this.libraryFolderId = data.libraryFolderId
|
||||||
@ -20,7 +20,7 @@ class LibraryItemScanData {
|
|||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.ino = data.ino
|
this.ino = data.ino
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.deviceId = data.dev
|
this.deviceId = data.deviceId
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
this.mtimeMs = data.mtimeMs
|
this.mtimeMs = data.mtimeMs
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
@ -179,8 +179,8 @@ class LibraryItemScanData {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {LibraryItem} existingLibraryItem
|
* @param {LibraryItem} existingLibraryItem
|
||||||
* @param {import('./LibraryScan')} libraryScan
|
* @param {import('./LibraryScan') | import('./ScanLogger')} libraryScan
|
||||||
* @returns {boolean} true if changes found
|
* @returns {Promise<boolean>} true if changes found
|
||||||
*/
|
*/
|
||||||
async checkLibraryItemData(existingLibraryItem, libraryScan) {
|
async checkLibraryItemData(existingLibraryItem, libraryScan) {
|
||||||
const keysToCompare = ['libraryFolderId', 'ino', 'deviceId', 'path', 'relPath', 'isFile']
|
const keysToCompare = ['libraryFolderId', 'ino', 'deviceId', 'path', 'relPath', 'isFile']
|
||||||
|
@ -23,7 +23,7 @@ class LibraryItemScanner {
|
|||||||
* Scan single library item
|
* Scan single library item
|
||||||
*
|
*
|
||||||
* @param {string} libraryItemId
|
* @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
|
* @returns {number} ScanResult
|
||||||
*/
|
*/
|
||||||
async scanLibraryItem(libraryItemId, updateLibraryItemDetails = null) {
|
async scanLibraryItem(libraryItemId, updateLibraryItemDetails = null) {
|
||||||
@ -190,7 +190,7 @@ class LibraryItemScanner {
|
|||||||
* @param {import('../models/Library')} library
|
* @param {import('../models/Library')} library
|
||||||
* @param {import('../models/LibraryFolder')} folder
|
* @param {import('../models/LibraryFolder')} folder
|
||||||
* @param {boolean} isSingleMediaItem
|
* @param {boolean} isSingleMediaItem
|
||||||
* @returns {Promise<LibraryItem>} ScanResult
|
* @returns {Promise<LibraryItem | null>} ScanResult
|
||||||
*/
|
*/
|
||||||
async scanPotentialNewLibraryItem(libraryItemPath, library, folder, isSingleMediaItem) {
|
async scanPotentialNewLibraryItem(libraryItemPath, library, folder, isSingleMediaItem) {
|
||||||
const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, isSingleMediaItem)
|
const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, isSingleMediaItem)
|
||||||
@ -209,6 +209,13 @@ class LibraryItemScanner {
|
|||||||
}
|
}
|
||||||
module.exports = new 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) {
|
async function buildLibraryItemScanData(libraryItemData, folder, library, isSingleMediaItem, libraryFiles) {
|
||||||
const libraryItemStats = await fileUtils.getFileTimestampsWithIno(libraryItemData.path)
|
const libraryItemStats = await fileUtils.getFileTimestampsWithIno(libraryItemData.path)
|
||||||
return new LibraryItemScanData({
|
return new LibraryItemScanData({
|
||||||
|
122
test/server/MockDatabase.js
Normal file
122
test/server/MockDatabase.js
Normal file
@ -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<string, import('fs').Stats>} */
|
||||||
|
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<string, import('fs').Stats>} */
|
||||||
|
// 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
|
@ -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 LibraryFile = require('../../../server/objects/files/LibraryFile')
|
||||||
const EBookFile = require('../../../server/objects/files/EBookFile')
|
const EBookFile = require('../../../server/objects/files/EBookFile')
|
||||||
const AudioFile = require('../../../server/objects/files/AudioFile')
|
const AudioFile = require('../../../server/objects/files/AudioFile')
|
||||||
const LibraryItem = require('../../../server/models/LibraryItem')
|
|
||||||
const LibraryItemScanData = require('../../../server/scanner/LibraryItemScanData')
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -3,49 +3,18 @@ const expect = chai.expect
|
|||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const rewire = require('rewire')
|
const rewire = require('rewire')
|
||||||
const fileUtils = require('../../../server/utils/fileUtils')
|
const fileUtils = require('../../../server/utils/fileUtils')
|
||||||
const Database = require('../../../server/Database')
|
|
||||||
const { Sequelize } = require('sequelize')
|
|
||||||
const LibraryFile = require('../../../server/objects/files/LibraryFile')
|
const LibraryFile = require('../../../server/objects/files/LibraryFile')
|
||||||
const LibraryItem = require('../../../server/models/LibraryItem')
|
const LibraryItem = require('../../../server/models/LibraryItem')
|
||||||
const FileMetadata = require('../../../server/objects/metadata/FileMetadata')
|
const FileMetadata = require('../../../server/objects/metadata/FileMetadata')
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
|
const Database = require('../../../server/Database')
|
||||||
|
const { stubFileUtils, loadTestDatabase, getMockFileInfo, getRenamedMockFileInfo, buildBookLibraryItemParams } = require('../MockDatabase')
|
||||||
|
|
||||||
describe('LibraryScanner', () => {
|
describe('LibraryScanner', () => {
|
||||||
let getInoStub, getDeviceIdStub, getFileTimestampsWithInoStub, LibraryScanner, testLibrary
|
let LibraryScanner, testLibrary
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
getInoStub = sinon.stub(fileUtils, 'getIno')
|
stubFileUtils()
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
LibraryScanner = rewire('../../../server/scanner/LibraryScanner')
|
LibraryScanner = rewire('../../../server/scanner/LibraryScanner')
|
||||||
})
|
})
|
||||||
@ -245,79 +214,3 @@ describe('LibraryScanner', () => {
|
|||||||
let ItemToItemInoMatch = LibraryScanner.__get__('ItemToItemInoMatch')
|
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<string, import('fs').Stats>} */
|
|
||||||
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<string, import('fs').Stats>} */
|
|
||||||
// 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' }]
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user