From 50797879d5bb8b7f06663b3caa4b28bd6b147c1a Mon Sep 17 00:00:00 2001 From: mikiher Date: Sat, 19 Oct 2024 00:10:29 +0300 Subject: [PATCH] Add a REINDEX NOCASE v2.15.1 migration and update v2.15.0 migration (#3533) * Add REINDEX NOCASE migration and update v2.15.0 migration * Update v2.15.0 migration test * Fix typo --- server/migrations/changelog.md | 7 +- .../v2.15.0-series-column-unique.js | 4 + server/migrations/v2.15.1-reindex-nocase.js | 43 +++++++ .../v2.15.0-series-column-unique.test.js | 105 ++++++++++-------- 4 files changed, 107 insertions(+), 52 deletions(-) create mode 100644 server/migrations/v2.15.1-reindex-nocase.js diff --git a/server/migrations/changelog.md b/server/migrations/changelog.md index b5dde749..92be2cd5 100644 --- a/server/migrations/changelog.md +++ b/server/migrations/changelog.md @@ -2,6 +2,7 @@ Please add a record of every database migration that you create to this file. This will help us keep track of changes to the database schema over time. -| Server Version | Migration Script Name | Description | -| -------------- | ---------------------------- | ------------------------------------------------- | -| v2.15.0 | v2.15.0-series-column-unique | Series must have unique names in the same library | +| Server Version | Migration Script Name | Description | +| -------------- | ---------------------------- | ------------------------------------------------------------------------------------ | +| v2.15.0 | v2.15.0-series-column-unique | Series must have unique names in the same library | +| v2.15.1 | v2.15.1-reindex-nocase | Fix potential db corruption issues due to bad sqlite extension introduced in v2.12.0 | diff --git a/server/migrations/v2.15.0-series-column-unique.js b/server/migrations/v2.15.0-series-column-unique.js index 96b0ea60..7f8526f9 100644 --- a/server/migrations/v2.15.0-series-column-unique.js +++ b/server/migrations/v2.15.0-series-column-unique.js @@ -18,6 +18,10 @@ async function up({ context: { queryInterface, logger } }) { // Upwards migration script logger.info('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique ') + // Run reindex nocase to fix potential corruption issues due to the bad sqlite extension introduced in v2.12.0 + logger.info('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues') + await queryInterface.sequelize.query('REINDEX NOCASE;') + // Check if the unique index already exists const seriesIndexes = await queryInterface.showIndex('Series') if (seriesIndexes.some((index) => index.name === 'unique_series_name_per_library')) { diff --git a/server/migrations/v2.15.1-reindex-nocase.js b/server/migrations/v2.15.1-reindex-nocase.js new file mode 100644 index 00000000..2ec9487d --- /dev/null +++ b/server/migrations/v2.15.1-reindex-nocase.js @@ -0,0 +1,43 @@ +/** + * @typedef MigrationContext + * @property {import('sequelize').QueryInterface} queryInterface - a suquelize QueryInterface object. + * @property {import('../Logger')} logger - a Logger object. + * + * @typedef MigrationOptions + * @property {MigrationContext} context - an object containing the migration context. + */ + +/** + * This upward migration script fixes old database corruptions due to the a bad sqlite extension introduced in v2.12.0. + * + * @param {MigrationOptions} options - an object containing the migration context. + * @returns {Promise} - A promise that resolves when the migration is complete. + */ +async function up({ context: { queryInterface, logger } }) { + // Upwards migration script + logger.info('[2.15.1 migration] UPGRADE BEGIN: 2.15.1-reindex-nocase ') + + // Run reindex nocase to fix potential corruption issues due to the bad sqlite extension introduced in v2.12.0 + logger.info('[2.15.1 migration] Reindexing NOCASE indices to fix potential hidden corruption issues') + await queryInterface.sequelize.query('REINDEX NOCASE;') + + logger.info('[2.15.1 migration] UPGRADE END: 2.15.1-reindex-nocase ') +} + +/** + * This downward migration script is a no-op. + * + * @param {MigrationOptions} options - an object containing the migration context. + * @returns {Promise} - A promise that resolves when the migration is complete. + */ +async function down({ context: { queryInterface, logger } }) { + // Downward migration script + logger.info('[2.15.1 migration] DOWNGRADE BEGIN: 2.15.1-reindex-nocase ') + + // This migration is a no-op + logger.info('[2.15.1 migration] No action required for downgrade') + + logger.info('[2.15.1 migration] DOWNGRADE END: 2.15.1-reindex-nocase ') +} + +module.exports = { up, down } diff --git a/test/server/migrations/v2.15.0-series-column-unique.test.js b/test/server/migrations/v2.15.0-series-column-unique.test.js index 4ae07e63..a9ad0fab 100644 --- a/test/server/migrations/v2.15.0-series-column-unique.test.js +++ b/test/server/migrations/v2.15.0-series-column-unique.test.js @@ -104,12 +104,13 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(5) + expect(loggerInfoStub.callCount).to.equal(6) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 0 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 0 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true // Validate rows in tables const series = await queryInterface.sequelize.query('SELECT "id", "name", "libraryId" FROM Series', { type: queryInterface.sequelize.QueryTypes.SELECT }) expect(series).to.have.length(3) @@ -144,14 +145,15 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(7) + expect(loggerInfoStub.callCount).to.equal(8) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 2 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 3" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 2 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 3" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true // Validate rows const series = await queryInterface.sequelize.query('SELECT "id", "name", "libraryId" FROM Series', { type: queryInterface.sequelize.QueryTypes.SELECT }) expect(series).to.have.length(3) @@ -181,12 +183,13 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(5) + expect(loggerInfoStub.callCount).to.equal(6) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 0 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 0 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true // Validate rows const series = await queryInterface.sequelize.query('SELECT "id", "name", "libraryId" FROM Series', { type: queryInterface.sequelize.QueryTypes.SELECT }) expect(series).to.have.length(2) @@ -211,15 +214,16 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(8) + expect(loggerInfoStub.callCount).to.equal(9) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 1 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Finished cleanup of bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 1 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Deduplicating bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Finished cleanup of bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(8).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true // validate rows const series = await queryInterface.sequelize.query('SELECT "id", "name", "libraryId" FROM Series', { type: queryInterface.sequelize.QueryTypes.SELECT }) expect(series).to.have.length(1) @@ -243,15 +247,16 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(8) + expect(loggerInfoStub.callCount).to.equal(9) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 1 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Finished cleanup of bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 1 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Deduplicating bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Finished cleanup of bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(8).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true // validate rows const series = await queryInterface.sequelize.query('SELECT "id", "name", "libraryId" FROM Series', { type: queryInterface.sequelize.QueryTypes.SELECT }) expect(series).to.have.length(1) @@ -274,15 +279,16 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(8) + expect(loggerInfoStub.callCount).to.equal(9) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 1 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Finished cleanup of bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true - expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 1 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplicating series "Series 1" in library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Deduplicating bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] Finished cleanup of bookId 4a38b6e5-0ae4-4de4-b119-4e33891bd63f in series "Series 1" of library 3a5a1c7c-a914-472e-88b0-b871ceae63e7'))).to.be.true + expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(8).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true // validate rows const series = await queryInterface.sequelize.query('SELECT "id", "name", "libraryId" FROM Series', { type: queryInterface.sequelize.QueryTypes.SELECT }) expect(series).to.have.length(1) @@ -318,15 +324,16 @@ describe('migration-v2.15.0-series-column-unique', () => { await up({ context: { queryInterface, logger: Logger } }) await down({ context: { queryInterface, logger: Logger } }) - expect(loggerInfoStub.callCount).to.equal(8) + expect(loggerInfoStub.callCount).to.equal(9) expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Found 0 duplicate series'))).to.be.true - expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true - expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] DOWNGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true - expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] Removed unique index on Series.name and Series.libraryId'))).to.be.true - expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] DOWNGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues'))).to.be.true + expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.15.0 migration] Found 0 duplicate series'))).to.be.true + expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.15.0 migration] Deduplication complete'))).to.be.true + expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.15.0 migration] Added unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.15.0 migration] UPGRADE END: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.15.0 migration] DOWNGRADE BEGIN: 2.15.0-series-column-unique '))).to.be.true + expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.15.0 migration] Removed unique index on Series.name and Series.libraryId'))).to.be.true + expect(loggerInfoStub.getCall(8).calledWith(sinon.match('[2.15.0 migration] DOWNGRADE END: 2.15.0-series-column-unique '))).to.be.true // Ensure index does not exist const indexes = await queryInterface.showIndex('Series') expect(indexes).to.not.deep.include({ tableName: 'Series', unique: true, fields: ['name', 'libraryId'], name: 'unique_series_name_per_library' })