mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #3664 from advplyr/v2.17.3-fk-constraints-migration
Add migration to fix dropped foreign key constraints dropped in v2.17.0 migration
This commit is contained in:
		
						commit
						02ca926d88
					
				@ -3,8 +3,9 @@
 | 
				
			|||||||
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.
 | 
					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                                                                                                   |
 | 
					| 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.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                          |
 | 
					| v2.15.1        | v2.15.1-reindex-nocase       | Fix potential db corruption issues due to bad sqlite extension introduced in v2.12.0                          |
 | 
				
			||||||
| v2.15.2        | v2.15.2-index-creation       | Creates author, series, and podcast episode indexes                                                           |
 | 
					| v2.15.2        | v2.15.2-index-creation       | Creates author, series, and podcast episode indexes                                                           |
 | 
				
			||||||
| v2.17.0        | v2.17.0-uuid-replacement     | Changes the data type of columns with UUIDv4 to UUID matching the associated model                            |
 | 
					| v2.17.0        | v2.17.0-uuid-replacement     | Changes the data type of columns with UUIDv4 to UUID matching the associated model                            |
 | 
				
			||||||
 | 
					| v2.17.3        | v2.17.3-fk-constraints       | Changes the foreign key constraints for tables due to sequelize bug dropping constraints in v2.17.0 migration |
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										259
									
								
								server/migrations/v2.17.3-fk-constraints.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								server/migrations/v2.17.3-fk-constraints.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,259 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @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 changes foreign key constraints for the
 | 
				
			||||||
 | 
					 * libraryItems, feeds, mediaItemShares, playbackSessions, playlistMediaItems, and mediaProgresses tables.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {MigrationOptions} options - an object containing the migration context.
 | 
				
			||||||
 | 
					 * @returns {Promise<void>} - A promise that resolves when the migration is complete.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function up({ context: { queryInterface, logger } }) {
 | 
				
			||||||
 | 
					  // Upwards migration script
 | 
				
			||||||
 | 
					  logger.info('[2.17.3 migration] UPGRADE BEGIN: 2.17.3-fk-constraints')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const execQuery = queryInterface.sequelize.query.bind(queryInterface.sequelize)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Disable foreign key constraints for the next sequence of operations
 | 
				
			||||||
 | 
					  await execQuery(`PRAGMA foreign_keys = OFF;`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await execQuery(`BEGIN TRANSACTION;`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info('[2.17.3 migration] Updating libraryItems constraints')
 | 
				
			||||||
 | 
					    const libraryItemsConstraints = [
 | 
				
			||||||
 | 
					      { field: 'libraryId', onDelete: 'SET NULL', onUpdate: 'CASCADE' },
 | 
				
			||||||
 | 
					      { field: 'libraryFolderId', onDelete: 'SET NULL', onUpdate: 'CASCADE' }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    if (await changeConstraints(queryInterface, 'libraryItems', libraryItemsConstraints)) {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] Finished updating libraryItems constraints')
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] No changes needed for libraryItems constraints')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info('[2.17.3 migration] Updating feeds constraints')
 | 
				
			||||||
 | 
					    const feedsConstraints = [{ field: 'userId', onDelete: 'SET NULL', onUpdate: 'CASCADE' }]
 | 
				
			||||||
 | 
					    if (await changeConstraints(queryInterface, 'feeds', feedsConstraints)) {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] Finished updating feeds constraints')
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] No changes needed for feeds constraints')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (await queryInterface.tableExists('mediaItemShares')) {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] Updating mediaItemShares constraints')
 | 
				
			||||||
 | 
					      const mediaItemSharesConstraints = [{ field: 'userId', onDelete: 'SET NULL', onUpdate: 'CASCADE' }]
 | 
				
			||||||
 | 
					      if (await changeConstraints(queryInterface, 'mediaItemShares', mediaItemSharesConstraints)) {
 | 
				
			||||||
 | 
					        logger.info('[2.17.3 migration] Finished updating mediaItemShares constraints')
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        logger.info('[2.17.3 migration] No changes needed for mediaItemShares constraints')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] mediaItemShares table does not exist, skipping column change')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info('[2.17.3 migration] Updating playbackSessions constraints')
 | 
				
			||||||
 | 
					    const playbackSessionsConstraints = [
 | 
				
			||||||
 | 
					      { field: 'deviceId', onDelete: 'SET NULL', onUpdate: 'CASCADE' },
 | 
				
			||||||
 | 
					      { field: 'libraryId', onDelete: 'SET NULL', onUpdate: 'CASCADE' },
 | 
				
			||||||
 | 
					      { field: 'userId', onDelete: 'SET NULL', onUpdate: 'CASCADE' }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    if (await changeConstraints(queryInterface, 'playbackSessions', playbackSessionsConstraints)) {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] Finished updating playbackSessions constraints')
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] No changes needed for playbackSessions constraints')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info('[2.17.3 migration] Updating playlistMediaItems constraints')
 | 
				
			||||||
 | 
					    const playlistMediaItemsConstraints = [{ field: 'playlistId', onDelete: 'CASCADE', onUpdate: 'CASCADE' }]
 | 
				
			||||||
 | 
					    if (await changeConstraints(queryInterface, 'playlistMediaItems', playlistMediaItemsConstraints)) {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] Finished updating playlistMediaItems constraints')
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] No changes needed for playlistMediaItems constraints')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info('[2.17.3 migration] Updating mediaProgresses constraints')
 | 
				
			||||||
 | 
					    const mediaProgressesConstraints = [{ field: 'userId', onDelete: 'CASCADE', onUpdate: 'CASCADE' }]
 | 
				
			||||||
 | 
					    if (await changeConstraints(queryInterface, 'mediaProgresses', mediaProgressesConstraints)) {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] Finished updating mediaProgresses constraints')
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.info('[2.17.3 migration] No changes needed for mediaProgresses constraints')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await execQuery(`COMMIT;`)
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    logger.error(`[2.17.3 migration] Migration failed - rolling back. Error:`, error)
 | 
				
			||||||
 | 
					    await execQuery(`ROLLBACK;`)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await execQuery(`PRAGMA foreign_keys = ON;`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Completed migration
 | 
				
			||||||
 | 
					  logger.info('[2.17.3 migration] UPGRADE END: 2.17.3-fk-constraints')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This downward migration script is a no-op.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {MigrationOptions} options - an object containing the migration context.
 | 
				
			||||||
 | 
					 * @returns {Promise<void>} - A promise that resolves when the migration is complete.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function down({ context: { queryInterface, logger } }) {
 | 
				
			||||||
 | 
					  // Downward migration script
 | 
				
			||||||
 | 
					  logger.info('[2.17.3 migration] DOWNGRADE BEGIN: 2.17.3-fk-constraints')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // This migration is a no-op
 | 
				
			||||||
 | 
					  logger.info('[2.17.3 migration] No action required for downgrade')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Completed migration
 | 
				
			||||||
 | 
					  logger.info('[2.17.3 migration] DOWNGRADE END: 2.17.3-fk-constraints')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @typedef ConstraintUpdateObj
 | 
				
			||||||
 | 
					 * @property {string} field - The field to update
 | 
				
			||||||
 | 
					 * @property {string} onDelete - The onDelete constraint
 | 
				
			||||||
 | 
					 * @property {string} onUpdate - The onUpdate constraint
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @typedef SequelizeFKObj
 | 
				
			||||||
 | 
					 * @property {{ model: string, key: string }} references
 | 
				
			||||||
 | 
					 * @property {string} onDelete
 | 
				
			||||||
 | 
					 * @property {string} onUpdate
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {Object} fk - The foreign key object from PRAGMA foreign_key_list
 | 
				
			||||||
 | 
					 * @returns {SequelizeFKObj} - The foreign key object formatted for Sequelize
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const formatFKsPragmaToSequelizeFK = (fk) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    references: {
 | 
				
			||||||
 | 
					      model: fk.table,
 | 
				
			||||||
 | 
					      key: fk.to
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    onDelete: fk['on_delete'],
 | 
				
			||||||
 | 
					    onUpdate: fk['on_update']
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {import('sequelize').QueryInterface} queryInterface
 | 
				
			||||||
 | 
					 * @param {string} tableName
 | 
				
			||||||
 | 
					 * @param {ConstraintUpdateObj[]} constraints
 | 
				
			||||||
 | 
					 * @returns {Promise<Record<string, SequelizeFKObj>|null>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function getUpdatedForeignKeys(queryInterface, tableName, constraints) {
 | 
				
			||||||
 | 
					  const execQuery = queryInterface.sequelize.query.bind(queryInterface.sequelize)
 | 
				
			||||||
 | 
					  const quotedTableName = queryInterface.quoteIdentifier(tableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const foreignKeys = await execQuery(`PRAGMA foreign_key_list(${quotedTableName});`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let hasUpdates = false
 | 
				
			||||||
 | 
					  const foreignKeysByColName = foreignKeys.reduce((prev, curr) => {
 | 
				
			||||||
 | 
					    const fk = formatFKsPragmaToSequelizeFK(curr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const constraint = constraints.find((c) => c.field === curr.from)
 | 
				
			||||||
 | 
					    if (constraint && (constraint.onDelete !== fk.onDelete || constraint.onUpdate !== fk.onUpdate)) {
 | 
				
			||||||
 | 
					      fk.onDelete = constraint.onDelete
 | 
				
			||||||
 | 
					      fk.onUpdate = constraint.onUpdate
 | 
				
			||||||
 | 
					      hasUpdates = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { ...prev, [curr.from]: fk }
 | 
				
			||||||
 | 
					  }, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return hasUpdates ? foreignKeysByColName : null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Extends the Sequelize describeTable function to include the updated foreign key constraints
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {import('sequelize').QueryInterface} queryInterface
 | 
				
			||||||
 | 
					 * @param {String} tableName
 | 
				
			||||||
 | 
					 * @param {Record<string, SequelizeFKObj>} updatedForeignKeys
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function describeTableWithFKs(queryInterface, tableName, updatedForeignKeys) {
 | 
				
			||||||
 | 
					  const tableDescription = await queryInterface.describeTable(tableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const tableDescriptionWithFks = Object.entries(tableDescription).reduce((prev, [col, attributes]) => {
 | 
				
			||||||
 | 
					    let extendedAttributes = attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (updatedForeignKeys[col]) {
 | 
				
			||||||
 | 
					      extendedAttributes = {
 | 
				
			||||||
 | 
					        ...extendedAttributes,
 | 
				
			||||||
 | 
					        ...updatedForeignKeys[col]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { ...prev, [col]: extendedAttributes }
 | 
				
			||||||
 | 
					  }, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return tableDescriptionWithFks
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @see https://www.sqlite.org/lang_altertable.html#otheralter
 | 
				
			||||||
 | 
					 * @see https://sequelize.org/docs/v6/other-topics/query-interface/#changing-and-removing-columns-in-sqlite
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {import('sequelize').QueryInterface} queryInterface
 | 
				
			||||||
 | 
					 * @param {string} tableName
 | 
				
			||||||
 | 
					 * @param {ConstraintUpdateObj[]} constraints
 | 
				
			||||||
 | 
					 * @returns {Promise<boolean>} - Return false if no changes are needed, true otherwise
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function changeConstraints(queryInterface, tableName, constraints) {
 | 
				
			||||||
 | 
					  const updatedForeignKeys = await getUpdatedForeignKeys(queryInterface, tableName, constraints)
 | 
				
			||||||
 | 
					  if (!updatedForeignKeys) {
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const execQuery = queryInterface.sequelize.query.bind(queryInterface.sequelize)
 | 
				
			||||||
 | 
					  const quotedTableName = queryInterface.quoteIdentifier(tableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const backupTableName = `${tableName}_${Math.round(Math.random() * 100)}_backup`
 | 
				
			||||||
 | 
					  const quotedBackupTableName = queryInterface.quoteIdentifier(backupTableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const tableDescriptionWithFks = await describeTableWithFKs(queryInterface, tableName, updatedForeignKeys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const attributes = queryInterface.queryGenerator.attributesToSQL(tableDescriptionWithFks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create the backup table
 | 
				
			||||||
 | 
					    await queryInterface.createTable(backupTableName, attributes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const attributeNames = Object.keys(attributes)
 | 
				
			||||||
 | 
					      .map((attr) => queryInterface.quoteIdentifier(attr))
 | 
				
			||||||
 | 
					      .join(', ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Copy all data from the target table to the backup table
 | 
				
			||||||
 | 
					    await execQuery(`INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Drop the old (original) table
 | 
				
			||||||
 | 
					    await queryInterface.dropTable(tableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Rename the backup table to the original table's name
 | 
				
			||||||
 | 
					    await queryInterface.renameTable(backupTableName, tableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Validate that all foreign key constraints are correct
 | 
				
			||||||
 | 
					    const result = await execQuery(`PRAGMA foreign_key_check(${quotedTableName});`, {
 | 
				
			||||||
 | 
					      type: queryInterface.sequelize.Sequelize.QueryTypes.SELECT
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // There are foreign key violations, exit
 | 
				
			||||||
 | 
					    if (result.length) {
 | 
				
			||||||
 | 
					      return Promise.reject(`Foreign key violations detected: ${JSON.stringify(result, null, 2)}`)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    return Promise.reject(error)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = { up, down }
 | 
				
			||||||
							
								
								
									
										230
									
								
								test/server/migrations/v2.17.3-fk-constraints.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								test/server/migrations/v2.17.3-fk-constraints.test.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					const { expect } = require('chai')
 | 
				
			||||||
 | 
					const sinon = require('sinon')
 | 
				
			||||||
 | 
					const { up } = require('../../../server/migrations/v2.17.3-fk-constraints')
 | 
				
			||||||
 | 
					const { Sequelize, QueryInterface } = require('sequelize')
 | 
				
			||||||
 | 
					const Logger = require('../../../server/Logger')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('migration-v2.17.3-fk-constraints', () => {
 | 
				
			||||||
 | 
					  let sequelize
 | 
				
			||||||
 | 
					  /** @type {QueryInterface} */
 | 
				
			||||||
 | 
					  let queryInterface
 | 
				
			||||||
 | 
					  let loggerInfoStub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
 | 
				
			||||||
 | 
					    queryInterface = sequelize.getQueryInterface()
 | 
				
			||||||
 | 
					    loggerInfoStub = sinon.stub(Logger, 'info')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(() => {
 | 
				
			||||||
 | 
					    sinon.restore()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('up', () => {
 | 
				
			||||||
 | 
					    beforeEach(async () => {
 | 
				
			||||||
 | 
					      // Create associated tables: Users, libraries, libraryFolders, playlists, devices
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `users` (`id` UUID PRIMARY KEY);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `libraries` (`id` UUID PRIMARY KEY);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `libraryFolders` (`id` UUID PRIMARY KEY);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `playlists` (`id` UUID PRIMARY KEY);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `devices` (`id` UUID PRIMARY KEY);')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    afterEach(async () => {
 | 
				
			||||||
 | 
					      await queryInterface.dropAllTables()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should fix table foreign key constraints', async () => {
 | 
				
			||||||
 | 
					      // Create tables with missing foreign key constraints: libraryItems, feeds, mediaItemShares, playbackSessions, playlistMediaItems, mediaProgresses
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `libraryItems` (`id` UUID UNIQUE PRIMARY KEY, `libraryId` UUID REFERENCES `libraries` (`id`), `libraryFolderId` UUID REFERENCES `libraryFolders` (`id`));')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `feeds` (`id` UUID UNIQUE PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`));')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `mediaItemShares` (`id` UUID UNIQUE PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`));')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `playbackSessions` (`id` UUID UNIQUE PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`), `deviceId` UUID REFERENCES `devices` (`id`), `libraryId` UUID REFERENCES `libraries` (`id`));')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `playlistMediaItems` (`id` UUID UNIQUE PRIMARY KEY, `playlistId` UUID REFERENCES `playlists` (`id`));')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `mediaProgresses` (`id` UUID UNIQUE PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`));')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Validate that foreign key constraints are missing
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      let libraryItemsForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(libraryItems);`)
 | 
				
			||||||
 | 
					      expect(libraryItemsForeignKeys).to.have.deep.members([
 | 
				
			||||||
 | 
					        { id: 0, seq: 0, table: 'libraryFolders', from: 'libraryFolderId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' },
 | 
				
			||||||
 | 
					        { id: 1, seq: 0, table: 'libraries', from: 'libraryId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' }
 | 
				
			||||||
 | 
					      ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let feedsForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(feeds);`)
 | 
				
			||||||
 | 
					      expect(feedsForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let mediaItemSharesForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(mediaItemShares);`)
 | 
				
			||||||
 | 
					      expect(mediaItemSharesForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let playbackSessionForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(playbackSessions);`)
 | 
				
			||||||
 | 
					      expect(playbackSessionForeignKeys).to.deep.equal([
 | 
				
			||||||
 | 
					        { id: 0, seq: 0, table: 'libraries', from: 'libraryId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' },
 | 
				
			||||||
 | 
					        { id: 1, seq: 0, table: 'devices', from: 'deviceId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' },
 | 
				
			||||||
 | 
					        { id: 2, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' }
 | 
				
			||||||
 | 
					      ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let playlistMediaItemsForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(playlistMediaItems);`)
 | 
				
			||||||
 | 
					      expect(playlistMediaItemsForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'playlists', from: 'playlistId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let mediaProgressesForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(mediaProgresses);`)
 | 
				
			||||||
 | 
					      expect(mediaProgressesForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'NO ACTION', on_delete: 'NO ACTION', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Insert test data into tables
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('users', [{ id: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('libraries', [{ id: 'a41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('libraryFolders', [{ id: 'b41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('playlists', [{ id: 'f41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('devices', [{ id: 'g41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('libraryItems', [{ id: 'c1a96857-48a8-43b6-8966-abc909c55b0f', libraryId: 'a41a40e3-f516-40f5-810d-757ab668ebba', libraryFolderId: 'b41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('feeds', [{ id: 'd1a96857-48a8-43b6-8966-abc909c55b0f', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('mediaItemShares', [{ id: 'h1a96857-48a8-43b6-8966-abc909c55b0f', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('playbackSessions', [{ id: 'f1a96857-48a8-43b6-8966-abc909c55b0x', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f', deviceId: 'g41a40e3-f516-40f5-810d-757ab668ebba', libraryId: 'a41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('playlistMediaItems', [{ id: 'i1a96857-48a8-43b6-8966-abc909c55b0f', playlistId: 'f41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('mediaProgresses', [{ id: 'j1a96857-48a8-43b6-8966-abc909c55b0f', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Query data before migration
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      const libraryItems = await queryInterface.sequelize.query('SELECT * FROM libraryItems;')
 | 
				
			||||||
 | 
					      const feeds = await queryInterface.sequelize.query('SELECT * FROM feeds;')
 | 
				
			||||||
 | 
					      const mediaItemShares = await queryInterface.sequelize.query('SELECT * FROM mediaItemShares;')
 | 
				
			||||||
 | 
					      const playbackSessions = await queryInterface.sequelize.query('SELECT * FROM playbackSessions;')
 | 
				
			||||||
 | 
					      const playlistMediaItems = await queryInterface.sequelize.query('SELECT * FROM playlistMediaItems;')
 | 
				
			||||||
 | 
					      const mediaProgresses = await queryInterface.sequelize.query('SELECT * FROM mediaProgresses;')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Run migration
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      await up({ context: { queryInterface, logger: Logger } })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Validate that foreign key constraints are updated
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      libraryItemsForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(libraryItems);`)
 | 
				
			||||||
 | 
					      expect(libraryItemsForeignKeys).to.have.deep.members([
 | 
				
			||||||
 | 
					        { id: 0, seq: 0, table: 'libraryFolders', from: 'libraryFolderId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' },
 | 
				
			||||||
 | 
					        { id: 1, seq: 0, table: 'libraries', from: 'libraryId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' }
 | 
				
			||||||
 | 
					      ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      feedsForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(feeds);`)
 | 
				
			||||||
 | 
					      expect(feedsForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      mediaItemSharesForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(mediaItemShares);`)
 | 
				
			||||||
 | 
					      expect(mediaItemSharesForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      playbackSessionForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(playbackSessions);`)
 | 
				
			||||||
 | 
					      expect(playbackSessionForeignKeys).to.deep.equal([
 | 
				
			||||||
 | 
					        { id: 0, seq: 0, table: 'libraries', from: 'libraryId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' },
 | 
				
			||||||
 | 
					        { id: 1, seq: 0, table: 'devices', from: 'deviceId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' },
 | 
				
			||||||
 | 
					        { id: 2, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'CASCADE', on_delete: 'SET NULL', match: 'NONE' }
 | 
				
			||||||
 | 
					      ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      playlistMediaItemsForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(playlistMediaItems);`)
 | 
				
			||||||
 | 
					      expect(playlistMediaItemsForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'playlists', from: 'playlistId', to: 'id', on_update: 'CASCADE', on_delete: 'CASCADE', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      mediaProgressesForeignKeys = await queryInterface.sequelize.query(`PRAGMA foreign_key_list(mediaProgresses);`)
 | 
				
			||||||
 | 
					      expect(mediaProgressesForeignKeys).to.deep.equal([{ id: 0, seq: 0, table: 'users', from: 'userId', to: 'id', on_update: 'CASCADE', on_delete: 'CASCADE', match: 'NONE' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Validate that data is not changed
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      const libraryItemsAfter = await queryInterface.sequelize.query('SELECT * FROM libraryItems;')
 | 
				
			||||||
 | 
					      expect(libraryItemsAfter).to.deep.equal(libraryItems)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const feedsAfter = await queryInterface.sequelize.query('SELECT * FROM feeds;')
 | 
				
			||||||
 | 
					      expect(feedsAfter).to.deep.equal(feeds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const mediaItemSharesAfter = await queryInterface.sequelize.query('SELECT * FROM mediaItemShares;')
 | 
				
			||||||
 | 
					      expect(mediaItemSharesAfter).to.deep.equal(mediaItemShares)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const playbackSessionsAfter = await queryInterface.sequelize.query('SELECT * FROM playbackSessions;')
 | 
				
			||||||
 | 
					      expect(playbackSessionsAfter).to.deep.equal(playbackSessions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const playlistMediaItemsAfter = await queryInterface.sequelize.query('SELECT * FROM playlistMediaItems;')
 | 
				
			||||||
 | 
					      expect(playlistMediaItemsAfter).to.deep.equal(playlistMediaItems)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const mediaProgressesAfter = await queryInterface.sequelize.query('SELECT * FROM mediaProgresses;')
 | 
				
			||||||
 | 
					      expect(mediaProgressesAfter).to.deep.equal(mediaProgresses)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should keep correct table foreign key constraints', async () => {
 | 
				
			||||||
 | 
					      // Create tables with correct foreign key constraints: libraryItems, feeds, mediaItemShares, playbackSessions, playlistMediaItems, mediaProgresses
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `libraryItems` (`id` UUID PRIMARY KEY, `libraryId` UUID REFERENCES `libraries` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, `libraryFolderId` UUID REFERENCES `libraryFolders` (`id`) ON DELETE SET NULL ON UPDATE CASCADE);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `feeds` (`id` UUID PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `mediaItemShares` (`id` UUID PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `playbackSessions` (`id` UUID PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, `deviceId` UUID REFERENCES `devices` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, `libraryId` UUID REFERENCES `libraries` (`id`) ON DELETE SET NULL ON UPDATE CASCADE);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `playlistMediaItems` (`id` UUID PRIMARY KEY, `playlistId` UUID REFERENCES `playlists` (`id`) ON DELETE CASCADE ON UPDATE CASCADE);')
 | 
				
			||||||
 | 
					      await queryInterface.sequelize.query('CREATE TABLE `mediaProgresses` (`id` UUID PRIMARY KEY, `userId` UUID REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE);')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Insert test data into tables
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('users', [{ id: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('libraries', [{ id: 'a41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('libraryFolders', [{ id: 'b41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('playlists', [{ id: 'f41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('devices', [{ id: 'g41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('libraryItems', [{ id: 'c1a96857-48a8-43b6-8966-abc909c55b0f', libraryId: 'a41a40e3-f516-40f5-810d-757ab668ebba', libraryFolderId: 'b41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('feeds', [{ id: 'd1a96857-48a8-43b6-8966-abc909c55b0f', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('mediaItemShares', [{ id: 'h1a96857-48a8-43b6-8966-abc909c55b0f', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('playbackSessions', [{ id: 'f1a96857-48a8-43b6-8966-abc909c55b0x', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f', deviceId: 'g41a40e3-f516-40f5-810d-757ab668ebba', libraryId: 'a41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('playlistMediaItems', [{ id: 'i1a96857-48a8-43b6-8966-abc909c55b0f', playlistId: 'f41a40e3-f516-40f5-810d-757ab668ebba' }])
 | 
				
			||||||
 | 
					      await queryInterface.bulkInsert('mediaProgresses', [{ id: 'j1a96857-48a8-43b6-8966-abc909c55b0f', userId: 'e1a96857-48a8-43b6-8966-abc909c55b0f' }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Query data before migration
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      const libraryItems = await queryInterface.sequelize.query('SELECT * FROM libraryItems;')
 | 
				
			||||||
 | 
					      const feeds = await queryInterface.sequelize.query('SELECT * FROM feeds;')
 | 
				
			||||||
 | 
					      const mediaItemShares = await queryInterface.sequelize.query('SELECT * FROM mediaItemShares;')
 | 
				
			||||||
 | 
					      const playbackSessions = await queryInterface.sequelize.query('SELECT * FROM playbackSessions;')
 | 
				
			||||||
 | 
					      const playlistMediaItems = await queryInterface.sequelize.query('SELECT * FROM playlistMediaItems;')
 | 
				
			||||||
 | 
					      const mediaProgresses = await queryInterface.sequelize.query('SELECT * FROM mediaProgresses;')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await up({ context: { queryInterface, logger: Logger } })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.callCount).to.equal(14)
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(0).calledWith(sinon.match('[2.17.3 migration] UPGRADE BEGIN: 2.17.3-fk-constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(1).calledWith(sinon.match('[2.17.3 migration] Updating libraryItems constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(2).calledWith(sinon.match('[2.17.3 migration] No changes needed for libraryItems constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(3).calledWith(sinon.match('[2.17.3 migration] Updating feeds constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(4).calledWith(sinon.match('[2.17.3 migration] No changes needed for feeds constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(5).calledWith(sinon.match('[2.17.3 migration] Updating mediaItemShares constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(6).calledWith(sinon.match('[2.17.3 migration] No changes needed for mediaItemShares constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(7).calledWith(sinon.match('[2.17.3 migration] Updating playbackSessions constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(8).calledWith(sinon.match('[2.17.3 migration] No changes needed for playbackSessions constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(9).calledWith(sinon.match('[2.17.3 migration] Updating playlistMediaItems constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(10).calledWith(sinon.match('[2.17.3 migration] No changes needed for playlistMediaItems constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(11).calledWith(sinon.match('[2.17.3 migration] Updating mediaProgresses constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(12).calledWith(sinon.match('[2.17.3 migration] No changes needed for mediaProgresses constraints'))).to.be.true
 | 
				
			||||||
 | 
					      expect(loggerInfoStub.getCall(13).calledWith(sinon.match('[2.17.3 migration] UPGRADE END: 2.17.3-fk-constraints'))).to.be.true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // Validate that data is not changed
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      const libraryItemsAfter = await queryInterface.sequelize.query('SELECT * FROM libraryItems;')
 | 
				
			||||||
 | 
					      expect(libraryItemsAfter).to.deep.equal(libraryItems)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const feedsAfter = await queryInterface.sequelize.query('SELECT * FROM feeds;')
 | 
				
			||||||
 | 
					      expect(feedsAfter).to.deep.equal(feeds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const mediaItemSharesAfter = await queryInterface.sequelize.query('SELECT * FROM mediaItemShares;')
 | 
				
			||||||
 | 
					      expect(mediaItemSharesAfter).to.deep.equal(mediaItemShares)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const playbackSessionsAfter = await queryInterface.sequelize.query('SELECT * FROM playbackSessions;')
 | 
				
			||||||
 | 
					      expect(playbackSessionsAfter).to.deep.equal(playbackSessions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const playlistMediaItemsAfter = await queryInterface.sequelize.query('SELECT * FROM playlistMediaItems;')
 | 
				
			||||||
 | 
					      expect(playlistMediaItemsAfter).to.deep.equal(playlistMediaItems)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const mediaProgressesAfter = await queryInterface.sequelize.query('SELECT * FROM mediaProgresses;')
 | 
				
			||||||
 | 
					      expect(mediaProgressesAfter).to.deep.equal(mediaProgresses)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user