mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-08-03 15:11:44 +00:00
125 lines
3.9 KiB
JavaScript
125 lines
3.9 KiB
JavaScript
|
|
const { expect } = require('chai')
|
||
|
|
const sinon = require('sinon')
|
||
|
|
const os = require('os')
|
||
|
|
const Path = require('path')
|
||
|
|
const EventEmitter = require('events')
|
||
|
|
const childProcess = require('child_process')
|
||
|
|
const sqlite3 = require('sqlite3')
|
||
|
|
|
||
|
|
const BackupManager = require('../../../server/managers/BackupManager')
|
||
|
|
const Backup = require('../../../server/objects/Backup')
|
||
|
|
const Database = require('../../../server/Database')
|
||
|
|
|
||
|
|
describe('BackupManager', () => {
|
||
|
|
let originalDialect
|
||
|
|
let originalDbPath
|
||
|
|
let originalConfigPath
|
||
|
|
let originalMetadataPath
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
originalDialect = Database.dialect
|
||
|
|
originalDbPath = Database.dbPath
|
||
|
|
originalConfigPath = global.ConfigPath
|
||
|
|
originalMetadataPath = global.MetadataPath
|
||
|
|
global.MetadataPath = os.tmpdir()
|
||
|
|
})
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
Database.dialect = originalDialect
|
||
|
|
Database.dbPath = originalDbPath
|
||
|
|
global.ConfigPath = originalConfigPath
|
||
|
|
global.MetadataPath = originalMetadataPath
|
||
|
|
sinon.restore()
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should select Postgres custom-format backups for the Postgres dialect', () => {
|
||
|
|
Database.dialect = 'postgres'
|
||
|
|
const manager = new BackupManager()
|
||
|
|
|
||
|
|
expect(manager.databaseBackupConfig).to.deep.equal({
|
||
|
|
dialect: 'postgres',
|
||
|
|
entryName: 'absdatabase.postgres.dump'
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should create Postgres dumps with pg_dump and preserve the connection URL', async () => {
|
||
|
|
Database.dialect = 'postgres'
|
||
|
|
Database.dbPath = 'postgresql://localhost:5432/audiobookshelf'
|
||
|
|
global.ConfigPath = os.tmpdir()
|
||
|
|
|
||
|
|
const execFileStub = sinon.stub(childProcess, 'execFile').callsFake((command, args, options, callback) => {
|
||
|
|
callback(null, '', '')
|
||
|
|
})
|
||
|
|
const manager = new BackupManager()
|
||
|
|
const backup = new Backup()
|
||
|
|
backup.id = '2026-08-02T0130'
|
||
|
|
|
||
|
|
const dumpPath = await manager.backupPostgresDb(backup)
|
||
|
|
|
||
|
|
expect(dumpPath).to.equal(Path.join(os.tmpdir(), 'absdatabase.2026-08-02T0130.postgres.dump'))
|
||
|
|
expect(execFileStub.calledOnce).to.equal(true)
|
||
|
|
expect(execFileStub.firstCall.args[0]).to.equal('pg_dump')
|
||
|
|
expect(execFileStub.firstCall.args[1]).to.deep.equal([
|
||
|
|
'--format=custom',
|
||
|
|
'--no-owner',
|
||
|
|
'--no-acl',
|
||
|
|
'--file',
|
||
|
|
dumpPath,
|
||
|
|
'--dbname',
|
||
|
|
Database.dbPath
|
||
|
|
])
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should restore Postgres dumps in one transaction and clean existing objects', async () => {
|
||
|
|
Database.dialect = 'postgres'
|
||
|
|
Database.dbPath = 'postgresql://localhost:5432/audiobookshelf'
|
||
|
|
|
||
|
|
const execFileStub = sinon.stub(childProcess, 'execFile').callsFake((command, args, options, callback) => {
|
||
|
|
callback(null, '', '')
|
||
|
|
})
|
||
|
|
const manager = new BackupManager()
|
||
|
|
|
||
|
|
await manager.restorePostgresDb('/config/absdatabase-postgres-temp.dump')
|
||
|
|
|
||
|
|
expect(execFileStub.firstCall.args[0]).to.equal('pg_restore')
|
||
|
|
expect(execFileStub.firstCall.args[1]).to.deep.equal([
|
||
|
|
'--clean',
|
||
|
|
'--if-exists',
|
||
|
|
'--exit-on-error',
|
||
|
|
'--single-transaction',
|
||
|
|
'--no-owner',
|
||
|
|
'--no-acl',
|
||
|
|
'--dbname',
|
||
|
|
Database.dbPath,
|
||
|
|
'/config/absdatabase-postgres-temp.dump'
|
||
|
|
])
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should reject SQLite backup open errors without an uncaught sqlite event', async () => {
|
||
|
|
Database.dialect = 'sqlite'
|
||
|
|
Database.dbPath = '/config/absdatabase.sqlite'
|
||
|
|
global.ConfigPath = os.tmpdir()
|
||
|
|
|
||
|
|
sinon.stub(sqlite3, 'Database').callsFake(function (_dbPath, callback) {
|
||
|
|
const db = new EventEmitter()
|
||
|
|
db.close = (closeCallback) => closeCallback()
|
||
|
|
process.nextTick(() => callback(Object.assign(new Error('unable to open database file'), { code: 'SQLITE_CANTOPEN' })))
|
||
|
|
return db
|
||
|
|
})
|
||
|
|
|
||
|
|
const manager = new BackupManager()
|
||
|
|
const backup = new Backup()
|
||
|
|
backup.id = '2026-08-02T0130'
|
||
|
|
|
||
|
|
let error
|
||
|
|
try {
|
||
|
|
await manager.backupSqliteDb(backup)
|
||
|
|
} catch (caughtError) {
|
||
|
|
error = caughtError
|
||
|
|
}
|
||
|
|
|
||
|
|
expect(error).to.be.an('error')
|
||
|
|
expect(error.code).to.equal('SQLITE_CANTOPEN')
|
||
|
|
})
|
||
|
|
})
|