--[[----------------------------------------------------------------------------

NmbFileMoverTest.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2017 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.utils
local KSPathUtils = require 'KSPathUtils'
local WFLogger = require 'WFLogger'
local WFFileUtils = require 'WFFileUtils'
local KSData = require 'KSData'
local WFOzTestUtils = require 'WFOzTestUtils'
local WFPlatformInfo = require 'WFPlatformInfo'
local KSIdGenerator = require 'KSIdGenerator'
local WFCrypto = require 'WFCrypto'

	-- WF.oz_client.managed_catalog
local WFCatMockCatalog = require 'WFCatMockCatalog'
local NmbFileMover = require 'NmbFileMover'
local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatPixelPathGenerator = require 'WFCatPixelPathGenerator'

--============================================================================--

local NmbFileMoverTest = {}

--------------------------------------------------------------------------------

local child = KSPathUtils.child

local logger = WFLogger 'NmbFileMoverTest'
logger:enable( 'print' )

--------------------------------------------------------------------------------

local counter = 1

local basePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
local originalVolumeGuid = WFFileUtils.getVolumeGuidForPath( basePath )
local originalVolumeName = WFFileUtils.getVolumeNameFromGuid( originalVolumeGuid )

local offlinePath = '/Volumes/foo/images'
local offlineVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
local offlineRelativePath = '/foo/images'

if WFPlatformInfo.isWindows then
	offlinePath = 'Q:\foo\images'
	offlineRelativePath = 'foo\images'
end

local mockImage = KSData.make( 'data' )
local mockImageVariation = KSData.make( 'foo' )

--------------------------------------------------------------------------------

local function assetDir( basePath )

	local dir1 = child( basePath, 'dir1' )
	local dir2 = child( dir1, 'dir2' )
	local dir3 = child( dir2, 'dir3' )

	return dir3

end

--------------------------------------------------------------------------------

local function getCurrentOriginalPath( originalBasePath, fileName )

	return child( assetDir( originalBasePath ), fileName )
	
end

--------------------------------------------------------------------------------

local function getGuidAndRelativePath( basePath, fileName, offline )

	local relativeBasePath = WFFileUtils.removeVolumeFromPath( basePath )
	
	local relativeFilePath = getCurrentOriginalPath( relativeBasePath, fileName )
	
	local volumeGuid = offline and offlineVolumeGuid or originalVolumeGuid

	return volumeGuid, relativeFilePath
	
end

--------------------------------------------------------------------------------

local function getCurrentProxyPath( proxyBasePath, fileName, offline )

	local basePath = offline and offlinePath or proxyBasePath
	return child( assetDir( basePath ), fileName )
	
end

--------------------------------------------------------------------------------

local function buildGuidBasePath( guid, relativePath )

	return KSPathUtils.child( guid, relativePath )
	
end 

--------------------------------------------------------------------------------

local function fileHyphenate( name, counter )

	if counter == 1 then
		return name .. '.txt'
	end

	return name .. '-' .. tostring( counter ) .. '.txt'

end

--------------------------------------------------------------------------------
	
local function modifyAsset( docValue, hasLocalMaster, hasLocalProxy, 
		isDenied, fileName, destinationPath, hasMatchingSHA,
		noFileOnDisk, assetsOffline, fileSize, isMarkedForOfflineUse,
		isVideo, originalBasePath, proxyBasePath, ozHasOriginal,
		createDuplicates )
	
	local originalFileName = fileName or counter .. 'original.txt'
	local proxyFileName = fileName or counter .. 'proxy.txt'

	local importSourceFileName = originalFileName
	if createDuplicates then
		importSourceFileName = 'original.txt'
		originalFileName = fileHyphenate( 'original', counter )
	end

	local localOnly = {
			files = {
				original = {},
				proxy = {}
			},
			deniedBy = {},
			isMarkedForOfflineUse = isMarkedForOfflineUse
		}

	local serverProvidedMetadata = {}

	local sha256 
	
	if hasLocalMaster then
	
		local volumeGuid, relativeFilePath = getGuidAndRelativePath( originalBasePath, originalFileName, assetsOffline )
		
		if not noFileOnDisk and not assetsOffline then
		
			local originalFilePath = WFFileUtils.createFullPathFromRelativePathAndGuid( relativeFilePath, volumeGuid )
		
			KSData.saveToFile( mockImage, originalFilePath )
			sha256 = originalFilePath and WFCrypto.digestFile( originalFilePath, 'SHA256' )
			assert( WFFileUtils.exists( originalFilePath ) )
			
		end
		
		localOnly.files.original.master = {
			relativePath = relativeFilePath,
			volumeGuid = volumeGuid,
		}
		
	else
	
		localOnly.files.original = { _delete = true }
		
	end
	
	if hasLocalProxy then

		local volumeGuid, relativeFilePath = getGuidAndRelativePath( proxyBasePath, proxyFileName, assetsOffline )
		
		if not noFileOnDisk and not assetsOffline then
		
			local proxyFilePath = WFFileUtils.createFullPathFromRelativePathAndGuid( relativeFilePath, volumeGuid )
		
			KSData.saveToFile( mockImage, proxyFilePath )
			assert( WFFileUtils.exists( proxyFilePath ) )
			
		end
		
		local sizeOfProxy = isVideo and '720p' or '2560'
		
		localOnly.files.proxy[ sizeOfProxy ] = {
			relativePath = relativeFilePath,
			volumeGuid = volumeGuid,
		}

	else
	
		localOnly.files.proxy = { _delete = true }
		
	end

	local links
	if ozHasOriginal then
		links = {}
		links[ '/rels/rendition_type/thumbnail2x' ] = { href = '/proxy/href/bogus/testing' }
		links[ '/rels/master' ] = { href = '/master/href/bogus/testing' }
	end

	local importSource = {
		fileName = importSourceFileName,
		fileSize = fileSize or 100,
		sha256 = sha256
	}
	
	if isDenied then
		localOnly.deniedBy[ 'NmbFileMover' ] = true
	end

	serverProvidedMetadata.links = links
	
	docValue.importSource = importSource
	docValue._localOnly = localOnly
	docValue.subtype = isVideo and 'video' or 'image'
	docValue._serverProvidedMetadata = serverProvidedMetadata
	docValue._id = string.format( '%032x', counter )
	docValue.captureDate = '2017-01-01T00:00:01'
	
	if destinationPath then
	
		local originalPath = WFCatPixelPathGenerator.pathForOriginal( destinationPath, originalFileName, docValue.captureDate )
		local proxyPath = WFCatPixelPathGenerator.pathForProxyTypeAndUuid( destinationPath, '2560', docValue._id )

		if hasMatchingSHA then
			KSData.saveToFile( mockImage, originalPath )
			KSData.saveToFile( mockImage, proxyPath )
			importSource.sha256 = WFCrypto.digestFile( originalPath, 'SHA256' )
		else
			KSData.saveToFile( mockImageVariation, originalPath )
			KSData.saveToFile( mockImageVariation, proxyPath )
		end
		
	end
	
	counter = counter + 1
	
	return docValue

end

--------------------------------------------------------------------------------

local parent = assert( KSPathUtils.parent )

local function calculateBaseBinaryPath( binaryPath )

	-- Original path: 	<base path>/originals/year/date/<file>
	-- Proxy path: 		<base path>/proxies/<proxy size>/<3-character subfolder>/<file>
	
	return parent( parent( parent( parent( binaryPath ) ) ) )
	
end

--------------------------------------------------------------------------------

local function docValueHasLocalOriginal( docValue )

	local localOriginalVolumeGuid = WFCatFileSpec.getOriginalVolumeGuidFromDocValue( docValue )
	local localOriginalRelativePath = WFCatFileSpec.getOriginalRelativePathFromDocValue( docValue )
	return localOriginalVolumeGuid and localOriginalRelativePath

end

--------------------------------------------------------------------------------

local function docValueHasLocalProxy( docValue )

	local localProxyVolumeGuid = WFCatFileSpec.getProxyVolumeGuidFromDocValue( docValue )
	local localProxyRelativePath = WFCatFileSpec.getProxyRelativePathFromDocValue( docValue )
	return localProxyVolumeGuid and localProxyRelativePath

end

--------------------------------------------------------------------------------

local function checkOriginalLocation( rxTest, docStore, docId, expectedBasePath )

	return docStore:docValue( docId )
	
		:map( function( docValue )
	
			local hasLocalOriginal = docValueHasLocalOriginal( docValue )
			
			if expectedBasePath then
			
				local originalPath = WFCatFileSpec.buildOriginalPathFromDocValue( docValue )
			
				rxTest:expectEqual( expectedBasePath, calculateBaseBinaryPath( originalPath ) )
				rxTest:expectEqual( true, not not WFFileUtils.exists( originalPath ) )
				return KSPathUtils.leafName( originalPath )
				
			else
			
				return not hasLocalOriginal
				
			end
			
		end )

end

--------------------------------------------------------------------------------

local function getOriginalLocationInfo( docValue )

	local originalPath = WFCatFileSpec.buildOriginalPathFromDocValue( docValue )
	return {
		hasLocalOriginal = not not docValueHasLocalOriginal( docValue ),
		basePath = calculateBaseBinaryPath( originalPath ),
		fileExists = originalPath and WFFileUtils.exists( originalPath ),
		leafName = originalPath and KSPathUtils.leafName( originalPath ),
	}

end

--------------------------------------------------------------------------------

local function checkProxyLocation( rxTest, docStore, docId, expectedBasePath )

	return docStore:docValue( docId )
	
		:map( function( docValue )
	
			local hasLocalProxy = docValueHasLocalProxy( docValue )
			
			if expectedBasePath then
			
				local proxyPath = WFCatFileSpec.buildProxyPathFromDocValue( docValue )
				rxTest:expectEqual( expectedBasePath, calculateBaseBinaryPath( proxyPath ) )
				rxTest:expectEqual( true, not not WFFileUtils.exists( proxyPath ) )
				return KSPathUtils.leafName( proxyPath )
				
			else
			
				return not hasLocalProxy
				
			end
			
			
		end )

end

--------------------------------------------------------------------------------

local function getProxyLocationInfo( docValue )

	local proxyPath = WFCatFileSpec.buildProxyPathFromDocValue( docValue )
	return {
		hasLocalProxy = not not docValueHasLocalProxy( docValue ),
		basePath = calculateBaseBinaryPath( proxyPath ),
		fileExists = proxyPath and WFFileUtils.exists( proxyPath ),
	}

end


--------------------------------------------------------------------------------

local function addMixedAssets( rxTest, originalBasePath, proxyBasePath, docStore, fileMover, options )

	options = options or {}

	counter = 1

	local assetDirOriginalBasePath = assetDir( originalBasePath )
	local assetDirProxyBasePath = assetDir( proxyBasePath )
	
	WFFileUtils.mkdir( assetDirOriginalBasePath )
	WFFileUtils.mkdir( assetDirProxyBasePath )
	
	if options.destinationPath then
		WFFileUtils.mkdir( assetDir( options.destinationPath ) )
	end
	
	rxTest:finally( function()
	
		WFFileUtils.delete( assetDirOriginalBasePath )
		WFFileUtils.delete( assetDirProxyBasePath )
		
		if options.destinationPath then
			WFFileUtils.delete( options.destinationPath )
		end
		
	end )
	
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore, modifyAsset )

	local ids = {}

	for i = 1, options.numberOfAssets or 0 do
	
		local id = addAssetFn( 
				options.hasLocalMaster, 
				options.hasLocalProxy, 
				options.isDenied,
				options.fileName,
				options.destinationPath,
				options.hasMatchingSHA,
				options.noFileOnDisk,
				options.assetsOffline,
				options.fileSize,
				options.isMarkedForOfflineUse,
				options.isVideo,
				originalBasePath,
				proxyBasePath,
				options.ozHasOriginal,
				options.createDuplicates )
				
		if fileMover and options.isDenied then
		
			rxTest:expectSingleValue( docStore:docValue( id )
				:flatMapLatest( function( docValue )
					return fileMover._failedFileDeniedListHelper:addToDeniedList( docValue._localId )
				end ),
				":ignore:" )
				
		end
		
		if options.hasLocalMaster then
			if options.noFileOnDisk or options.assetsOffline then
				rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), false )
			else
				rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, originalBasePath ), ':ignore:' )
			end
		else
			rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), true )
		end
		
		if options.hasLocalProxy then
			if options.noFileOnDisk or options.assetsOffline then
				rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i ), false )
			else
				rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i, proxyBasePath ), ':ignore:' )
			end
		else
			rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i ), true )
		end
		
		table.insert( ids, id )
		
	end

	return ids

end

--------------------------------------------------------------------------------

local function setupFileMover( rxTest, wfCatalog, optArgs )

	optArgs = optArgs or {}
	
	local uuid = KSIdGenerator.uuidInLowercaseHex()
	
	local originalBasePath = child( basePath, uuid .. "Original_Old" )
	local proxyBasePath = child( basePath, uuid .. "Proxy_Old" )

	local destinationOriginalBasePath = child( basePath, uuid .. "Original_New" )
	local destinationProxyBasePath = child( basePath, uuid .. "Proxy_New" )

	-- Cleanup anything that may have existed from a previous test run.
	
	WFFileUtils.delete( originalBasePath )
	WFFileUtils.delete( proxyBasePath )
	WFFileUtils.delete( destinationOriginalBasePath )
	WFFileUtils.delete( destinationProxyBasePath )
	WFFileUtils.delete( offlinePath )
	
	WFFileUtils.mkdir( originalBasePath )
	WFFileUtils.mkdir( proxyBasePath )
	
	optArgs.originalBasePath = optArgs.originalBasePath or destinationOriginalBasePath
	optArgs.proxyBasePath = optArgs.proxyBasePath or destinationProxyBasePath

	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	local fileSpec = wfCatalog:fileSpec()
	local deletionBacklog = wfCatalog:getDeletionBacklog()
	local keepAllOriginals = not not optArgs.keepAllOriginals
	local keepAllProxies = not not optArgs.keepAllProxies
	
	local fileMover = NmbFileMover( docStore, indexStore, fileSpec, deletionBacklog, keepAllOriginals, keepAllProxies, optArgs )

	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOutOfSpace = {},
		volumesOverDiskSpaceTarget = {},
	} )

	rxTest:finally( function()
		fileMover:close()
		wfCatalog:close()
		WFFileUtils.delete( originalBasePath )
		WFFileUtils.delete( proxyBasePath )
		WFFileUtils.delete( destinationOriginalBasePath )
		WFFileUtils.delete( destinationProxyBasePath )
		WFFileUtils.delete( offlinePath )
	end )

	return fileMover, originalBasePath, proxyBasePath

end

--------------------------------------------------------------------------------

local function expectedSuccessResult( filesMigrated, missingFiles)

	return {
		summary = {
			filesMigrated = filesMigrated,
			missingFiles = missingFiles or {},
			offlineDestinationDrives = {},
			offlineSourceDrives = {},
			spaceConstrainedDestinationDrives = {},
			volumeNames = {},
			success = true 
		}
	}

end

--------------------------------------------------------------------------------

local function removeSpaceConstraintValues( result )
	
	-- Actual disk space values are too squishy, so map them out.
	
	local summary = result.summary or {}
	local spaceConstrainedDestinationDrives = summary.spaceConstrainedDestinationDrives or {}
	
	for _, volume in pairs( spaceConstrainedDestinationDrives ) do
	
		volume.freeSpace = 'number'
		volume.spaceNeeded = 'number'
		
	end

	return result

end

--------------------------------------------------------------------------------

local function expectedOriginalBasePath( fileMover )

	return WFFileUtils.createFullPathFromRelativePathAndGuid( fileMover._relativeBasePathForOriginals, fileMover._volumeGuidForOriginals )

end

--------------------------------------------------------------------------------

local function expectedProxyBasePath( fileMover )

	return WFFileUtils.createFullPathFromRelativePathAndGuid( fileMover._relativeBasePathForProxies, fileMover._volumeGuidForProxies )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicOriginalMigration( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		hasLocalProxy = false,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
					
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestDuplicatedOriginalMigration( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()

	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		hasLocalProxy = false,
		isDenied = false,
		createDuplicates = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( docStore:docValue( i ):map( getOriginalLocationInfo ), {
			hasLocalOriginal = true,
			basePath = expectedOriginalBasePath( fileMover ),
			fileExists = 'file',
			leafName = fileHyphenate( 'original', i )
		} )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicProxyMigration( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = false,
		hasLocalProxy = true,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i, expectedProxyBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicVideoMigration_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local optArgs = {
		disableHardLinks = true
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		isVideo = true
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	-- Everything should be migrated because videos that are not on oz require migration.

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicVideoMigration_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()

	local optArgs = {
		disableHardLinks = true
	}

	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )

	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		isVideo = true,
		ozHasOriginal = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	-- Nothing should be migrated because we only migrate videos that require migration.

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), true )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicVideoMigration_Required( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local optArgs = {
		disableHardLinks = true
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		isVideo = true,
		isMarkedForOfflineUse = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicMigration_StoreAllOriginalsLocally( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local optArgs = {
		disableHardLinks = true,
		keepAllOriginals = true
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		isVideo = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicMigration_Videos_StoreAllOriginalsLocally( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local optArgs = {
		disableHardLinks = true,
		keepAllOriginals = true
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		isVideo = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicMigration_StoreAllProxiesLocally( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local optArgs = {
		disableHardLinks = true,
		keepAllProxies = true
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	local options = {
		numberOfAssets = 10,
		hasLocalProxy = true,
		isVideo = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i, expectedProxyBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicMigration_Videos_StoreAllProxiesLocally( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local optArgs = {
		disableHardLinks = true,
		keepAllProxies = true
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	local options = {
		numberOfAssets = 10,
		hasLocalProxy = true,
		isVideo = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )
	
	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i, expectedProxyBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBasicProgress( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 3,
		hasLocalMaster = true,
		hasLocalProxy = false,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = rxTest:observe( fileMover:maybePerformMigration()
		:map( function( data )
		
			-- Map out some extra info
		
			local extraInfo = data.extraInfo
		
			if extraInfo then
				extraInfo.newParentFolder = nil
				extraInfo.newVolume = nil
				extraInfo.oldParentFolder = nil
				extraInfo.oldVolume = nil
			end
		
			return data
		
		end ) )
		
	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 0,
		extraInfo = {
			isFreemiumMigration = false,
		},
	} )
	
	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 3,
		extraInfo = {
			currentStep = "REMAPPING",
		},
	} )
	
	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 1/3,
		processedCount = 1,
		totalCount = 3,
		extraInfo = {
			currentStep = "REMAPPING",
		},
	} )
	
	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 2/3,
		processedCount = 2,
		totalCount = 3,
		extraInfo = {
			currentStep = "REMAPPING",
		},
	} )

	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 1,
		processedCount = 3,
		totalCount = 3,
		extraInfo = {
			currentStep = "REMAPPING",
		},
	} )

	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 1,
		processedCount = 3,
		totalCount = 3,
		extraInfo = {
			currentStep = "REMAPPING",
		},
	} )

	migrationObs:expectData( {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 3,
	} )

	migrationObs:expectDataWithinTime( 3, {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 1/3,
		processedCount = 1,
		totalCount = 3,
		extraInfo = {
			currentStep = "MOVING",
		},
	} )
	
	migrationObs:expectDataWithinTime( 3, {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 2/3,
		processedCount = 2,
		totalCount = 3,
		extraInfo = {
			currentStep = "MOVING",
		},
	} )

	migrationObs:expectDataWithinTime( 3, {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 1,
		processedCount = 3,
		totalCount = 3,
		extraInfo = {
			currentStep = "MOVING",
		},
	} )

	migrationObs:expectDataWithinTime( 3, {
		description = "Migrating Files",
		errorCount = 0,
		percentComplete = 1,
		processedCount = 3,
		totalCount = 3,
		extraInfo = {
			summary = {
				filesMigrated = 3,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {},
				success = true,
			},
		}
	} )

	migrationObs:expectCompleted()

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestProxyAndOriginalMigration( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets * 2 ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i, expectedProxyBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOverTargetDiskSpaceOnOriginalDrive_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOverDiskSpaceTarget = {
			[ originalVolumeGuid ] = true
		},
		volumesOutOfSpace = {},
	} )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		ozHasOriginal = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), true )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOverTargetDiskSpaceOnOriginalDrive_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )

	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOverDiskSpaceTarget = {
			[ originalVolumeGuid ] = true
		},
		volumesOutOfSpace = {},
	} )

	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( docStore:docValue( i ):map( getOriginalLocationInfo ), {
			hasLocalOriginal = true,
			basePath = expectedOriginalBasePath( fileMover ),
			fileExists = 'file',
			leafName = tostring( i ) .. 'original.txt',
		} )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOverTargetDiskSpaceOnOriginalDrive_MarkedForOfflineUse( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOverDiskSpaceTarget = {
			[ originalVolumeGuid ] = true
		},
		volumesOutOfSpace = {},
	} )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		isMarkedForOfflineUse = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i, expectedOriginalBasePath( fileMover ) ), ':ignore:' )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestNoDiskSpaceOnOriginalDrive( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOutOfSpace = {
			[ originalVolumeGuid ] = true
		},
		volumesOverDiskSpaceTarget = {},
	} )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs:map( removeSpaceConstraintValues ),
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {
					[ originalVolumeGuid ] = {
						freeSpace = 'number',
						outOfSpace = true,
						spaceNeeded = 'number',
					},
				},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeName,
				},
				success = false,
			}
		})

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOverTargetDiskSpaceOnProxyDrive_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOverDiskSpaceTarget = {
			[ originalVolumeGuid ] = true
		},
		volumesOutOfSpace = {},
	} )
	
	local options = {
		numberOfAssets = 10,
		hasLocalProxy = true,
		ozHasOriginal = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i ), true )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOverTargetDiskSpaceOnProxyDrive_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )

	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOverDiskSpaceTarget = {
			[ originalVolumeGuid ] = true
		},
		volumesOutOfSpace = {},
	} )

	local options = {
		numberOfAssets = 10,
		hasLocalProxy = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )

	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( docStore:docValue( i ):map( getProxyLocationInfo ), {
			hasLocalProxy = true,
			basePath = expectedOriginalBasePath( fileMover ),
			fileExists = 'file',
		} )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestNoDiskSpaceOnProxyDrive( rxTest )

	rxTest:setTimeLimit( 30 )

	local optArgs = {
		disableHardLinks = true
	}

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOverDiskSpaceTarget = {},
		volumesOutOfSpace = {
			[ originalVolumeGuid ] = true
		},
	} )
	
	local options = {
		numberOfAssets = 10,
		hasLocalProxy = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs:map( removeSpaceConstraintValues ),
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {
					[ originalVolumeGuid ] = {
						freeSpace = 'number',
						outOfSpace = true,
						spaceNeeded = 'number',
					},
				},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeName,
				},
				success = false,
			}
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestNothingToMigrate( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = false,
		hasLocalProxy = false,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( 0 ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), true )
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i ), true )
	end
	
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestEverythingIsDenied( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, {
		summary = {
			filesMigrated = 0,
			missingFiles = {},
			offlineDestinationDrives = {},
			offlineSourceDrives = {},
			spaceConstrainedDestinationDrives = {},
			volumeNames = {},
			success = false,
			numberOfDeniedFiles = 10,
		} } )
		
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOriginalMigrationAlreadyExists_MatchingSHA( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = true,
		fileName = 'foo',
		destinationPath = expectedOriginalBasePath( fileMover ),
		hasMatchingSHA = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, 1, expectedOriginalBasePath( fileMover ) ), options.fileName )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentOriginalPath( originalBasePath, options.fileName ) ) )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOriginalMigrationAlreadyExists_DifferentSHA( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = true,
		fileName = 'foo',
		destinationPath = expectedOriginalBasePath( fileMover ),
		hasMatchingSHA = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, 1, expectedOriginalBasePath( fileMover ) ), 'foo-2' )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentOriginalPath( originalBasePath, options.fileName ) ) )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestProxyMigrationAlreadyExists_MatchingSHA( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		hasLocalProxy = true,
		fileName = 'foo',
		destinationPath = expectedProxyBasePath( fileMover ),
		hasMatchingSHA = true,
	}
	
	local ids = addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	local proxyFileName = KSPathUtils.leafName( WFCatPixelPathGenerator.pathForProxyTypeAndUuid( options.destinationPath, '2560', ids[ 1 ] ) )
	
	rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, 1, expectedProxyBasePath( fileMover ) ), proxyFileName )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentProxyPath( proxyBasePath, options.fileName ) ) )

end

--------------------------------------------------------------------------------

local function removeDNGExtension( path )
	local name = string.replace( path, '.dng','')
	return name
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestProxyMigrationAlreadyExists_DifferentSHA( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		hasLocalProxy = true,
		fileName = 'foo',
		destinationPath = expectedProxyBasePath( fileMover ),
		hasMatchingSHA = false,
	}
	
	local ids = addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets ) )
	
	local proxyFileName = KSPathUtils.leafName( WFCatPixelPathGenerator.pathForProxyTypeAndUuid( options.destinationPath, '2560', ids[ 1 ] ) )
	
	rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, 1, expectedProxyBasePath( fileMover ) )
		:map( function( path )
			return removeDNGExtension( path )
		end ), removeDNGExtension( proxyFileName ) )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentProxyPath( proxyBasePath, options.fileName ) ) )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_NothingAtPath_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		missingFiles = { child( assetDir( originalBasePath ), 'foo') },
		hasLocalMaster = true,
		fileName = 'foo',
		noFileOnDisk = true,
		ozHasOriginal = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, docStore, fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets, options.missingFiles ) )
	
	rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, 1 ), true )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentOriginalPath( originalBasePath, options.fileName ) ) )
	
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_NothingAtPath_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 1,
		missingFiles = { child( assetDir( originalBasePath ), 'foo') },
		hasLocalMaster = true,
		fileName = 'foo',
		noFileOnDisk = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, docStore, fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( options.numberOfAssets, options.missingFiles ) )

	rxTest:expectSingleValue( docStore:docValue( 1 ):map( getOriginalLocationInfo ), {
		hasLocalOriginal = true,
		basePath = expectedOriginalBasePath( fileMover ),
		fileExists = false, -- file not present but path preserved because it hasn't been uploaded yet (so user knows it is missing)
		leafName = 'foo',
	} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_NotEnoughSpace( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	WFFileUtils.mkdir( originalBasePath )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = true,
		fileName = 'foo',
		fileSize = WFFileUtils.freeSpaceAtPath( originalBasePath ) + 100000,
		isMarkedForOfflineUse = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, docStore, fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs:map( removeSpaceConstraintValues ),
	{
		summary = {
		
			filesMigrated = 0,
			
			missingFiles = {},
			
			offlineDestinationDrives = {},
			
			offlineSourceDrives = {},
			
			spaceConstrainedDestinationDrives = {
				[ originalVolumeGuid ] = {
					freeSpace = 'number',
					spaceNeeded = 'number',
				}
			},
			
			volumeNames = {
				[ originalVolumeGuid ] = originalVolumeName,
			},
			
			success = false,
		
		}
		
	} )
	
	local filePath = getCurrentOriginalPath( originalBasePath, options.fileName )
	
	rxTest:expectEqual( true, not not WFFileUtils.exists( filePath ) )
	
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_OfflineOriginalDestinationDrive( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local relativePath = "/originals/foo"
	local volumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local optArgs = {
		originalsPathInfo = {
			volumeGuid = volumeGuid,
			relativePath = relativePath
		}
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {
			[ volumeGuid ] = true,
		},
		volumesLowOnSpace = {},
		volumesOutOfSpace = {},
		volumesOverDiskSpaceTarget = {},
	} )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = true,
		hasLocalProxy = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs,
		{
			summary = {
				filesMigrated = 1,
				
				missingFiles = {},
			
				offlineDestinationDrives = {
					[ volumeGuid ] = 1,
				},
				
				offlineSourceDrives = {},
				
				spaceConstrainedDestinationDrives = {},
				
				volumeNames = {
					[ volumeGuid ] = volumeGuid,
				},
					
				success = false,
			}
		} )
		
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_OfflineProxyDestinationDrive( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local relativePath = "/proxies/foo"
	local volumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local optArgs = {
		proxiesPathInfo = {
			volumeGuid = volumeGuid,
			relativePath = relativePath
		}
	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	fileMover:reportStatusOfVolumes( {
		offlineVolumes = {
			[ volumeGuid ] = true,
		},
		volumesLowOnSpace = {},
		volumesOutOfSpace = {},
		volumesOverDiskSpaceTarget = {},
	} )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = true,
		hasLocalProxy = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs,
		{
			summary = {
				filesMigrated = 1,
				
				missingFiles = {},
			
				offlineDestinationDrives = {
					[ volumeGuid ] = 1,
				},
				
				offlineSourceDrives = {},
				
				spaceConstrainedDestinationDrives = {},
				
				volumeNames = {
					[ volumeGuid ] = volumeGuid,
				},
				
				success = false,
			}
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_OfflineOriginalSourceDrive( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = true,
		hasLocalProxy = false,
		assetsOffline = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs,
		{
			summary = {
				filesMigrated = 0,
				
				missingFiles = {},
			
				offlineDestinationDrives = {},
				
				offlineSourceDrives = {
					[ offlineVolumeGuid ] = 1,
				},
				
				spaceConstrainedDestinationDrives = {},
				
				volumeNames = {
					[ offlineVolumeGuid ] = offlineVolumeGuid,
				},
				
				success = false,
			}
		} )
		
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_OfflineProxySourceDrive_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 1,
		hasLocalMaster = false,
		hasLocalProxy = true,
		assetsOffline = true,
		ozHasOriginal = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	-- If the proxy volume is offline, that means the catalog was likely cloned/copied.
	-- We simple remap/delete the proxies when this happens.
	
	rxTest:expectSingleValue( migrationObs,
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {
				},
				offlineDestinationDrives = {
				},
				offlineSourceDrives = {
				},
				spaceConstrainedDestinationDrives = {
				},
				success = true,
				volumeNames = {
				},
			}
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigration_OfflineProxySourceDrive_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 1,
		hasLocalMaster = false,
		hasLocalProxy = true,
		assetsOffline = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	-- If the proxy volume is offline, that means the catalog was likely cloned/copied.
	-- We don't delete the proxy annotations if oz doesn't have the originals as a precaution.

	-- Note that users no longer have any UI to change this, they may be able to do this via conflig
	-- flags but it is essentially an unsupported feature.

	rxTest:expectSingleValue( migrationObs,
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {
				},
				offlineDestinationDrives = {
				},
				offlineSourceDrives = {
					[ offlineVolumeGuid ] = 1,
				},
				spaceConstrainedDestinationDrives = {
				},
				success = false,
				volumeNames = {
					[ offlineVolumeGuid ] = offlineVolumeGuid,
				},
			}
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_success( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 0,
			}
		}
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						originalBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local proxySourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						proxyBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 2,
		originalsToMigrate = 2,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {},
				success = true,
			}
		} )
	
	observer:expectCompleted()
	
	observer:dispose()	
		
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_offlineSourceOriginal( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 0,
			}
		}
	
	local offlineSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local offlineSourceVolumeGuid2 = KSIdGenerator.uuidInLowercaseHex()
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						"/foo/originals",
					},
					numberOfAssets = 2,
					online = true,
			},
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						"/bar/originals",
					},
					numberOfAssets = 5,
					online = false,
			},
			[ offlineSourceVolumeGuid2 ] = {
					basePaths = {
						"/baz/originals",
					},
					numberOfAssets = 3,
					online = false,
			}

		}
	
	local proxySourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						"/foo/proxies",
					},
					numberOfAssets = 2,
					online = true,
			}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 2,
		originalsToMigrate = 10,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, 
			destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {
					[ offlineSourceVolumeGuid ] = 5,
					[ offlineSourceVolumeGuid2 ] = 3,
				},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {
					[ offlineSourceVolumeGuid ] = offlineSourceVolumeGuid,
					[ offlineSourceVolumeGuid2 ] = offlineSourceVolumeGuid2,
				},
				success = false,
			}
		} )
		
	observer:expectCompleted()

	observer:dispose()

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_offlineSourceProxy( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 0,
			}
		}
	
	local offlineSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local offlineSourceVolumeGuid2 = KSIdGenerator.uuidInLowercaseHex()
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						"/foo/originals",
					},
					numberOfAssets = 2,
					online = true,
			},
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						"/bar/originals",
					},
					numberOfAssets = 10,
					online = false,
			},

		}
	
	local proxySourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						"/foo/proxies",
					},
					numberOfAssets = 2,
					online = true,
			},
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						"/bar/proxies",
					},
					numberOfAssets = 5,
					online = false,
			},
			[ offlineSourceVolumeGuid2 ] = {
					basePaths = {
						"/baz/proxies",
					},
					numberOfAssets = 3,
					online = false,
			}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 10,
		originalsToMigrate = 12,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {
					[ offlineSourceVolumeGuid ] = 15,
					[ offlineSourceVolumeGuid2 ] = 3,
				},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {
					[ offlineSourceVolumeGuid ] = offlineSourceVolumeGuid,
					[ offlineSourceVolumeGuid2 ] = offlineSourceVolumeGuid2,
				},
				success = false,
			}
		} )
		
	observer:dispose()
		
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_offlineDestinationOriginal( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local originalVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local relativeOriginalPath = 'a/random/dest/path/originals'
	
	local proxyVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local relativeProxyPath = 'a/random/dest/path/proxies'
	
	local optArgs = {
	
		originalsPathInfo = {
			volumeGuid = originalVolumeGuid,
			relativePath = relativeOriginalPath
		},
		
		proxiesPathInfo = {
			volumeGuid = proxyVolumeGuid,
			relativePath = relativeProxyPath
		}

	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = false,
				freeSpace = 0,
			},
			[ proxyVolumeGuid ] = {
				online = true,
				freeSpace = 0,
			},
		}
	
	local onlineSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local offlineSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local originalSourceVolumeStatuses = 
		{
			[ onlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/originals',
					},
					numberOfAssets = 2,
					online = true,
			},
			
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/originals',
					},
					numberOfAssets = 6,
					online = false,
			},
			
		}
	
	local proxySourceVolumeStatuses = 
		{
			[ onlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/proxies',
					},
					numberOfAssets = 2,
					online = true,
			},
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/proxies',
					},
					numberOfAssets = 5,
					online = false,
			}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 7,
		originalsToMigrate = 8,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {
					[ originalVolumeGuid ] = 8,
				},
				offlineSourceDrives = {
					[ offlineSourceVolumeGuid ] = 11,
				},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeGuid,
					[ offlineSourceVolumeGuid ] = offlineSourceVolumeGuid,
				},
				success = false,
			}
		} )

	observer:dispose()

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_offlineDestinationProxy( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local originalVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local relativeOriginalPath = 'a/random/dest/path/originals'
	
	local proxyVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local relativeProxyPath = 'a/random/dest/path/proxies'
	
	local optArgs = {
	
		originalsPathInfo = {
			volumeGuid = originalVolumeGuid,
			relativePath = relativeOriginalPath
		},
		
		proxiesPathInfo = {
			volumeGuid = proxyVolumeGuid,
			relativePath = relativeProxyPath
		}

	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 0,
			},
			[ proxyVolumeGuid ] = {
				online = false,
				freeSpace = 0,
			},
		}
	
	local onlineSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local offlineSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local originalSourceVolumeStatuses = 
		{
			[ onlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/originals',
					},
					numberOfAssets = 2,
					online = true,
			},
			
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/originals',
					},
					numberOfAssets = 6,
					online = false,
			},
			
		}
	
	local proxySourceVolumeStatuses = 
		{
			[ onlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/proxies',
					},
					numberOfAssets = 2,
					online = true,
			},
			[ offlineSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/proxies',
					},
					numberOfAssets = 5,
					online = false,
			}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 7,
		originalsToMigrate = 8,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {
					[ proxyVolumeGuid ] = 7,
				},
				offlineSourceDrives = {
					[ offlineSourceVolumeGuid ] = 11,
				},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {
					[ proxyVolumeGuid ] = proxyVolumeGuid,
					[ offlineSourceVolumeGuid ] = offlineSourceVolumeGuid,
				},
				success = false,
			}
		} )

	observer:dispose()

end


--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_offlineEverything( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local originalVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local relativeOriginalPath = 'a/random/dest/path/originals'
	
	local proxyVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	local relativeProxyPath = 'a/random/dest/path/proxies'
	
	local optArgs = {
	
		originalsPathInfo = {
			volumeGuid = originalVolumeGuid,
			relativePath = relativeOriginalPath
		},
		
		proxiesPathInfo = {
			volumeGuid = proxyVolumeGuid,
			relativePath = relativeProxyPath
		}

	}

	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = false,
				freeSpace = 0,
			},
			[ proxyVolumeGuid ] = {
				online = false,
				freeSpace = 0,
			},
		}
	
	local offlineOriginalsSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/originals',
					},
					numberOfAssets = 2,
					online = false,
			},
			
			[ offlineOriginalsSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/originals',
					},
					numberOfAssets = 6,
					online = false,
			},
			
		}
	
	local offlineProxiesSourceVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local proxySourceVolumeStatuses = 
		{
			[ offlineProxiesSourceVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/proxies',
					},
					numberOfAssets = 2,
					online = false,
			},
			[ proxyVolumeGuid ] = {
					basePaths = {
						'a/random/source/path/proxies',
					},
					numberOfAssets = 5,
					online = false,
			}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 7,
		originalsToMigrate = 8,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {
					[ originalVolumeGuid ] = 8,
					[ proxyVolumeGuid ] = 7,
				},
				offlineSourceDrives = {
					[ offlineOriginalsSourceVolumeGuid ] = 6,
					[ offlineProxiesSourceVolumeGuid ] = 2,
					[ originalVolumeGuid ] = 2,
					[ proxyVolumeGuid ] = 5,
				},
				spaceConstrainedDestinationDrives = {},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeGuid,
					[ proxyVolumeGuid ] = proxyVolumeGuid,
					[ offlineOriginalsSourceVolumeGuid ] = offlineOriginalsSourceVolumeGuid,
					[ offlineProxiesSourceVolumeGuid ] = offlineProxiesSourceVolumeGuid
				},
				success = false,
			}
		} )

	observer:dispose()

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_sameDriveOffline( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local originalPath = "/Volumes/assets/foo" 
	local proxyPath = "/Volumes/assets/bar"
	
	local volumePath = WFFileUtils.volumeForPath( originalPath )
	WFFileUtils.delete( volumePath )
	
	local volumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local relativeOriginalPath = WFFileUtils.removeVolumeFromPath( originalPath )
	local relativeProxyPath = WFFileUtils.removeVolumeFromPath( proxyPath )
	
	local optArgs = {
	
		originalsPathInfo = {
			volumeGuid = volumeGuid,
			relativePath = relativeOriginalPath
		},
		
		proxiesPathInfo = {
			volumeGuid = volumeGuid,
			relativePath = relativeProxyPath
		}

	}
	
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog, optArgs )
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ volumeGuid ] = {
				online = false,
				freeSpace = 0,
			}
		}
	
	local onlineVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local originalSourceVolumeStatuses = 
		{
			[ volumeGuid ] = {
					basePaths = {
						relativeOriginalPath,
					},
					numberOfAssets = 2,
					online = false,
			},
			
			[ onlineVolumeGuid ] = {
					basePaths = {
						"/a/b/c/originals",
					},
					numberOfAssets = 6,
					online = true,
			},
			
		}
	
	local offlineVolumeGuid = KSIdGenerator.uuidInLowercaseHex()
	
	local proxySourceVolumeStatuses = 
		{
			[ offlineVolumeGuid ] = {
					basePaths = {
						"/a/b/c/proxies",
					},
					numberOfAssets = 2,
					online = false,
			},
			[ volumeGuid ] = {
					basePaths = {
						relativeProxyPath,
					},
					numberOfAssets = 5,
					online = false,
			}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 7,
		originalsToMigrate = 8,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {
					[ volumeGuid ] = 15,
				},
				offlineSourceDrives = {
					[ volumeGuid ] = 7,
					[ offlineVolumeGuid ] = 2,
				},
				volumeNames = {
					[ volumeGuid ] = volumeGuid,
					[ offlineVolumeGuid ] = offlineVolumeGuid,
				},
				spaceConstrainedDestinationDrives = {},
				success = false,
			}
		} )

	observer:dispose()

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_NotEnoughRoomOriginals( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 10,
			}
		}
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						originalBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local proxySourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						proxyBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 2,
		originalsToMigrate = 2,
		sizeOfRequiredOriginals = 20,
		sizeOfRequiredProxies = 0,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {
					[ originalVolumeGuid ] = {
						freeSpace = 10,
						spaceNeeded = 20,
					}
				},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeName,
				},
				success = false,
			}
		} )
	
	observer:expectCompleted()
	
	observer:dispose()

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_NotEnoughRoomProxies( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 10,
			}
		}
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						originalBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local proxySourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						proxyBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 2,
		originalsToMigrate = 2,
		sizeOfRequiredOriginals = 0,
		sizeOfRequiredProxies = 20,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {
					[ originalVolumeGuid ] = {
						freeSpace = 10,
						spaceNeeded = 20,
					}
				},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeName,
				},
				success = false,
			}
		} )
	
	observer:expectCompleted()
	
	observer:dispose()

end


--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMigrationComplete_NotEnoughRoomProxiesAndOriginalsSameDrive( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover )

	local destinationVolumeStatuses =
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 10,
			}
		}
	
	local originalSourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						originalBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local proxySourceVolumeStatuses = 
		{
			[ originalVolumeGuid ] = {
					basePaths = {
						proxyBasePath,
					},
					numberOfAssets = 2,
					online = true,
				}
		}
	
	local sourceVolumeStatuses = {
		originalSourceVolumeStatuses = originalSourceVolumeStatuses,
		proxySourceVolumeStatuses = proxySourceVolumeStatuses,
		proxiesToMigrate = 2,
		originalsToMigrate = 2,
		sizeOfRequiredOriginals = 20,
		sizeOfRequiredProxies = 20,
	}
	
	local observer = rxTest:observe( fileMover._migrationProgress:status():takeLast( 1 ):pluck( 'extraInfo' ) )
	
	fileMover.private._migrationComplete( fileMover, destinationVolumeStatuses, sourceVolumeStatuses )

	observer:expectData(
		{
			summary = {
				filesMigrated = 0,
				missingFiles = {},
				offlineDestinationDrives = {},
				offlineSourceDrives = {},
				spaceConstrainedDestinationDrives = {
					[ originalVolumeGuid ] = {
						freeSpace = 10,
						spaceNeeded = 40,
					}
				},
				volumeNames = {
					[ originalVolumeGuid ] = originalVolumeName,
				},
				success = false,
			}
		} )
	
	observer:expectCompleted()
	
	observer:dispose()

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestAllVolumesOffline( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover = setupFileMover( rxTest, wfCatalog )

	local volumes = {
		["1"] = {
			online = true,
		},
		["2"] = {
			online = true,
		}
	}

	local result = fileMover.private._allVolumesOfflineOrOutOfSpace( volumes )
		
	rxTest:expectEqual( false, result )
	
	volumes = {
		["1"] = {
			online = true,
		},
		["2"] = {
			online = false,
		}
	}

	result = fileMover.private._allVolumesOfflineOrOutOfSpace( volumes )
		
	rxTest:expectEqual( false, result )
	
	volumes = {
		["1"] = {
			online = false,
		},
		["2"] = {
			online = false,
		}
	}

	result = fileMover.private._allVolumesOfflineOrOutOfSpace( volumes )
		
	rxTest:expectEqual( true, result )
	
	result = fileMover.private._allVolumesOfflineOrOutOfSpace( {} )
		
	rxTest:expectEqual( true, result )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestGetOnlineGuidBasePathsFromVolumes( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local volumes = {
		["online1"] = {
			basePaths = {
				originalBasePath,
			},
			numberOfAssets = 4,
			online = true,
		},
		["offline"] = {
			basePaths = {
				"/I/am/offline"	
			},
			numberOfAssets = 2,
			online = false,
		},
		["online2"] = {
			basePaths = {
				proxyBasePath,
			},
			numberOfAssets = 5,
			online = true,
		}
	}

	local basePaths = fileMover.private._getOnlineGuidBasePathsFromVolumes( volumes )
		
	rxTest:expectEqual( 
		{
			originalBasePath,
			proxyBasePath,
		}, basePaths )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMonitorDestinationVolumes( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover = setupFileMover( rxTest, wfCatalog )

	local function removeFreeSpace( result )
	
		-- Actual disk space values are too squishy, so map them out.
		
		for _, value in pairs( result ) do
		
			rxTest:expectEqual( not not value.freeSpace, true )
			value.freeSpace = 'number'
		
		end
	
		return result
	
	end 

	local statusOfVolumes = {
		offlineVolumes = {},
		volumesLowOnSpace = {},
		volumesOutOfSpace = {},
		volumesOverDiskSpaceTarget = {},
	}

	local obs = fileMover.private._monitorDestinationVolumes( originalVolumeGuid, originalVolumeGuid, Observable.just( statusOfVolumes ) )
		:take( 1 )
		
	rxTest:expectSingleValue( obs:map( removeFreeSpace ), 
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 'number',
			},
		} )
	
	statusOfVolumes = {
		offlineVolumes = {
			[ offlineVolumeGuid ] = true,
		},
		volumesLowOnSpace = {},
		volumesOutOfSpace = {},
		volumesOverDiskSpaceTarget = {},
	}
	
	obs = fileMover.private._monitorDestinationVolumes( offlineVolumeGuid, originalVolumeGuid, Observable.just( statusOfVolumes ) )
		:take( 1 )
		
	rxTest:expectSingleValue( obs:map( removeFreeSpace ), 
		{
			[ originalVolumeGuid ] = {
				online = true,
				freeSpace = 'number',
			},
			[ offlineVolumeGuid ] = {
				online = false,
				freeSpace = 'number',
			},
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMonitorSourceVolumes_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 2,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = false,
		ozHasOriginal = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local function removeSizes( result )
	
		-- Actual disk space values are too squishy, so map them out.
	
		result.sizeOfAllProxies = 1
		return result
	
	end

	local obs = fileMover.private._monitorSourceVolumes( wfCatalog:indexStore(), originalVolumeGuid, 'foo', originalVolumeGuid, 'bar' )
		:take( 1 )
		
	local relativeFilePath = WFFileUtils.removeVolumeFromPath( originalBasePath )
	local originalGuidBasePath = buildGuidBasePath( originalVolumeGuid, relativeFilePath )
	
	local proxyRelativeFilePath = WFFileUtils.removeVolumeFromPath( proxyBasePath )
	local proxyGuidBasePath = buildGuidBasePath( originalVolumeGuid, proxyRelativeFilePath )
		
	rxTest:expectSingleValue( obs:map( removeSizes ), 
		{
			originalSourceVolumeStatuses = {
				[ originalVolumeGuid ] = {
					basePaths = {
						originalGuidBasePath,
					},
					numberOfAssets = 2,
					online = true,
				},
			},
			originalsToMigrate = 2,
			proxySourceVolumeStatuses = {
				[ originalVolumeGuid ] = {
					basePaths = {
						proxyGuidBasePath,
					},
					numberOfAssets = 2,
					online = true,
				},
			},
			proxiesToMigrate = 2,
			sizeOfAllOriginals = 200,
			sizeOfAllProxies = 1,
			sizeOfRequiredOriginals = 0,
			sizeOfRequiredProxies = 0,
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestMonitorSourceVolumes_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 2,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = false,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local function removeSizes( result )

		-- Actual disk space values are too squishy, so map them out.

		result.sizeOfAllProxies = 1
		return result

	end

	local obs = fileMover.private._monitorSourceVolumes( wfCatalog:indexStore(), originalVolumeGuid, 'foo', originalVolumeGuid, 'bar' )
		:take( 1 )

	local relativeFilePath = WFFileUtils.removeVolumeFromPath( originalBasePath )
	local originalGuidBasePath = buildGuidBasePath( originalVolumeGuid, relativeFilePath )

	local proxyRelativeFilePath = WFFileUtils.removeVolumeFromPath( proxyBasePath )
	local proxyGuidBasePath = buildGuidBasePath( originalVolumeGuid, proxyRelativeFilePath )

	rxTest:expectSingleValue( obs:map( removeSizes ),
		{
			originalSourceVolumeStatuses = {
				[ originalVolumeGuid ] = {
					basePaths = {
						originalGuidBasePath,
					},
					numberOfAssets = 2,
					online = true,
				},
			},
			originalsToMigrate = 2,
			proxySourceVolumeStatuses = {
				[ originalVolumeGuid ] = {
					basePaths = {
						proxyGuidBasePath,
					},
					numberOfAssets = 2,
					online = true,
				},
			},
			proxiesToMigrate = 2,
			sizeOfAllOriginals = 200,
			sizeOfAllProxies = 1,
			sizeOfRequiredOriginals = 200,
			sizeOfRequiredProxies = 0,
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestGetSourceBasePaths_noOzOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 2,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	-- Since we are searching for base paths that don't match the base paths passed in,
	-- this should return 0 results.
	
	local relativeFilePath = WFFileUtils.removeVolumeFromPath( originalBasePath )
	local originalGuidBasePath = buildGuidBasePath( originalVolumeGuid, relativeFilePath )
	
	local proxyRelativeFilePath = WFFileUtils.removeVolumeFromPath( proxyBasePath )
	local proxyGuidBasePath = buildGuidBasePath( originalVolumeGuid, proxyRelativeFilePath )
	
	local obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), originalGuidBasePath, proxyGuidBasePath )
		:take( 1 )

	rxTest:expectSingleValue( obs, {}, {} )
	
	-- Searching on a made up original base path should return the real original base path.
	
	obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), 'foo', proxyGuidBasePath )
		:take( 1 )
		
	rxTest:expectSingleValue( obs,
		{ {
			originalVolumeGuid,
			originalGuidBasePath,
			2,
			200,
			200,
		} }, {} )
	
	-- Searching on a made up proxy base path should return the real proxy base path.
		
	obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), originalGuidBasePath, 'bar' )
		:take( 1 )
		
	local proxyPathSize = WFFileUtils.calculatePathSize( proxyBasePath )
		
	rxTest:expectSingleValue( obs,
		{},
		{
			{
				originalVolumeGuid,
				proxyGuidBasePath,
				2,
				proxyPathSize
			},
		} )
		
	-- Putting it all together..
	
	obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), 'foo', 'bar' )
		:take( 1 )
		
	rxTest:expectSingleValue( obs,
		{
			{
				originalVolumeGuid,
				originalGuidBasePath,
				2,
				200,
				200
			},
		},
		{
			{
				originalVolumeGuid,
				proxyGuidBasePath,
				2,
				proxyPathSize
			},
		} )

end

--------------------------------------------------------------------------------


function NmbFileMoverTest.rxTestGetSourceBasePaths_ozHasOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 2,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = false,
		ozHasOriginal = true,
	}

	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	-- Since we are searching for base paths that don't match the base paths passed in,
	-- this should return 0 results.

	local relativeFilePath = WFFileUtils.removeVolumeFromPath( originalBasePath )
	local originalGuidBasePath = buildGuidBasePath( originalVolumeGuid, relativeFilePath )

	local proxyRelativeFilePath = WFFileUtils.removeVolumeFromPath( proxyBasePath )
	local proxyGuidBasePath = buildGuidBasePath( originalVolumeGuid, proxyRelativeFilePath )

	local obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), originalGuidBasePath, proxyGuidBasePath )
		:take( 1 )

	rxTest:expectSingleValue( obs, {}, {} )

	-- Searching on a made up original base path should return the real original base path.

	obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), 'foo', proxyGuidBasePath )
		:take( 1 )

	rxTest:expectSingleValue( obs,
		{ {
			originalVolumeGuid,
			originalGuidBasePath,
			2,
			200,
			0,
		} }, {} )

	-- Searching on a made up proxy base path should return the real proxy base path.

	obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), originalGuidBasePath, 'bar' )
		:take( 1 )

	local proxyPathSize = WFFileUtils.calculatePathSize( proxyBasePath )

	rxTest:expectSingleValue( obs,
		{},
		{
			{
				originalVolumeGuid,
				proxyGuidBasePath,
				2,
				proxyPathSize
			},
		} )

	-- Putting it all together..

	obs = fileMover.private._getSourceBasePathsAndSizes( wfCatalog:indexStore(), 'foo', 'bar' )
		:take( 1 )

	rxTest:expectSingleValue( obs,
		{
			{
				originalVolumeGuid,
				originalGuidBasePath,
				2,
				200,
				0
			},
		},
		{
			{
				originalVolumeGuid,
				proxyGuidBasePath,
				2,
				proxyPathSize
			},
		} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestGetSourceVolumeStatusFromBasePaths( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local relativeFilePath = WFFileUtils.removeVolumeFromPath( originalBasePath )
	local originalGuidBasePath = buildGuidBasePath( originalVolumeGuid, relativeFilePath )
	
	local proxyRelativeFilePath = WFFileUtils.removeVolumeFromPath( proxyBasePath )
	local proxyGuidBasePath = buildGuidBasePath( originalVolumeGuid, proxyRelativeFilePath )

	local offlineGuidBasePath = buildGuidBasePath( offlineVolumeGuid, offlineRelativePath )

	local basePaths = {
		{
			originalVolumeGuid,
			originalGuidBasePath,
			10,
			1000,
			500
		},
		{
			originalVolumeGuid,
			proxyGuidBasePath,
			5,
			200,
			0
		},
		{
			offlineVolumeGuid,
			offlineGuidBasePath,
			7,
			700,
			700
			
		}
	}
	
	local sourceVolumeStatuses, numberOfAssetsToMigrate, sizeOfAllAssets, sizeOfRequiredAssets 
			= fileMover.private._getSourceVolumeStatusFromBasePaths( basePaths )

	rxTest:expectEqual( sourceVolumeStatuses, 
		{
			[ originalVolumeGuid ] = {
				basePaths = {
					originalGuidBasePath,
					proxyGuidBasePath
				},
				numberOfAssets = 15,
				online = true,
			},
			[ offlineVolumeGuid ] = {
				basePaths = {
					offlineGuidBasePath,
				},
				numberOfAssets = 7,
				online = false,
			}
		} )
	
	rxTest:expectEqual( 22, numberOfAssetsToMigrate )
	rxTest:expectEqual( 1900, sizeOfAllAssets )
	rxTest:expectEqual( 1200, sizeOfRequiredAssets )
	
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBuildMigrationQuery( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )

	local options = {
		numberOfAssets = 2,
		hasLocalMaster = true,
		hasLocalProxy = false,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )
	
	local function mapAwayCruft( rows )

		for _, row in ipairs( rows ) do
		
			row[ 1 ] = 'id'
			row[ 2 ] = 'localId'
			row[ 7 ] = 'date'
			row[ 12 ] = nil -- Remove isDenied status
			row[ 13 ] = nil -- Remove SHA
			row[ 14 ] = nil -- Remove mark for offline use status
			row[ 15 ] = nil -- Remove asset exists on Oz status
			row[ 16 ] = nil -- Remove isVideo
		end

		return rows

	end

	local volumeGuid = WFFileUtils.getVolumeGuidForPath( originalBasePath )
	local relativeFilePath = WFFileUtils.removeVolumeFromPath( originalBasePath )
	local originalGuidBasePath = buildGuidBasePath( volumeGuid, relativeFilePath )
	
	local proxyVolumeGuid = WFFileUtils.getVolumeGuidForPath( proxyBasePath )
	local proxyRelativeFilePath = WFFileUtils.removeVolumeFromPath( proxyBasePath )
	local proxyGuidBasePath = buildGuidBasePath( proxyVolumeGuid, proxyRelativeFilePath )

	local query = fileMover.private._buildMigrationQuery( wfCatalog:indexStore(), true, true, { originalGuidBasePath }, { proxyGuidBasePath } )
		:take( 1 )
		
	rxTest:expectSingleValue( query:map( mapAwayCruft ),
		{ {
			'id',
			'localId',
			originalGuidBasePath,
			volumeGuid,
			getCurrentOriginalPath( relativeFilePath, "1original.txt" ),
			"1original.txt",
			'date'
		},
		{
			'id',
			'localId',
			originalGuidBasePath,
			volumeGuid,
			getCurrentOriginalPath( relativeFilePath, "2original.txt" ),
			"2original.txt",
			'date'
		} } )
	
	query = fileMover.private._buildMigrationQuery( wfCatalog:indexStore(), true, false, { originalGuidBasePath }, { proxyGuidBasePath } )
		:take( 1 )
		
	rxTest:expectSingleValue( query:map( mapAwayCruft ),
		{ {
			'id',
			'localId',
			originalGuidBasePath,
			volumeGuid,
			getCurrentOriginalPath( relativeFilePath, "1original.txt" ),
			"1original.txt",
			'date'
		},
		{
			'id',
			'localId',
			originalGuidBasePath,
			volumeGuid,
			getCurrentOriginalPath( relativeFilePath, "2original.txt" ),
			"2original.txt",
			'date'
		} } )
		
	query = fileMover.private._buildMigrationQuery( wfCatalog:indexStore(), false, true, { originalGuidBasePath }, { proxyGuidBasePath } )
		:take( 1 )
		
	-- The original destination drive is offline, so we expect the query to return 0 results ( since there are no proxies
	-- that need to be moved ).
		
	rxTest:expectSingleValue( query:map( mapAwayCruft ), {} )
		
	-- Both destination drives are offline, so expect the query to return 0 results.
	
	query = fileMover.private._buildMigrationQuery( wfCatalog:indexStore(), false, false, { originalGuidBasePath }, { proxyGuidBasePath } )
		:take( 1 )
		
	rxTest:expectSingleValue( query:map( mapAwayCruft ), {} )
		
	-- The destination source drive is offline, so we expect 0 results.
	
	query = fileMover.private._buildMigrationQuery( wfCatalog:indexStore(), true, true, {}, { proxyGuidBasePath } )
		:take( 1 )
		
	rxTest:expectSingleValue( query:map( mapAwayCruft ), {} )
	
	-- Both destination source drives are offline, so we expect 0 results.
	
	query = fileMover.private._buildMigrationQuery( wfCatalog:indexStore(), true, true, {}, {} )
		:take( 1 )
		
	rxTest:expectSingleValue( query:map( mapAwayCruft ), {} )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestBuildWhereClause( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local fileMover = setupFileMover( rxTest, wfCatalog )

	local values = {
		'1',
		'2',
		'3'
	}

	local whereClause = fileMover.private._buildWhereClause( 'foo', values )

	rxTest:expectEqual( [[ WHERE ( foo = '1' OR foo = '2' OR foo = '3' )]], whereClause )

	whereClause = fileMover.private._buildWhereClause( 'foo', values, true )
	
	rxTest:expectEqual( [[ OR ( foo = '1' OR foo = '2' OR foo = '3' )]], whereClause )
	
end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestForgetVolume( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		hasLocalProxy = true,
		isDenied = false,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), false )
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i ), false )
	end

	rxTest:expectOnlyCompleted( fileMover:forgetVolume( originalVolumeGuid ) )
	
	for i = 1, options.numberOfAssets do
		rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, i ), true )
		rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, i ), true )
	end

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestOriginalRemapping( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = true,
		assetsOffline = true,
		fileName = 'foo',
		destinationPath = expectedOriginalBasePath( fileMover ),
		hasMatchingSHA = true,
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( 0 ) )
	
	rxTest:expectSingleValue( checkOriginalLocation( rxTest, docStore, 1, expectedOriginalBasePath( fileMover ) ), options.fileName )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentOriginalPath( originalBasePath, options.fileName ) ) )

end

--------------------------------------------------------------------------------

function NmbFileMoverTest.rxTestProxyRemapping( rxTest )

	rxTest:setTimeLimit( 30 )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local fileMover, originalBasePath, proxyBasePath = setupFileMover( rxTest, wfCatalog )
	
	local options = {
		numberOfAssets = 10,
		hasLocalMaster = false,
		hasLocalProxy = true,
		assetsOffline = true,
		fileName = 'foo',
		destinationPath = expectedProxyBasePath( fileMover )
	}
	
	addMixedAssets( rxTest, originalBasePath, proxyBasePath, wfCatalog:docStore(), fileMover, options )

	local migrationObs = fileMover:maybePerformMigration():takeLast( 1 ):pluck( 'extraInfo' )

	rxTest:expectSingleValue( migrationObs, expectedSuccessResult( 0 ) )
	
	rxTest:expectSingleValue( checkProxyLocation( rxTest, docStore, 1, expectedProxyBasePath( fileMover ) ), ':ignore:' )
	
	rxTest:expectEqual( false, WFFileUtils.exists( getCurrentProxyPath( proxyBasePath, options.fileName ) ) )

end

--------------------------------------------------------------------------------

if WFPlatformInfo.isAndroid or WFPlatformInfo.isiOS then return {} end
	-- This test suite is only relevant to desktop platforms.

return WFRxTestCase.wrapSuite( 'NmbFileMoverTest', NmbFileMoverTest )
