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

WFCatVolumeAgentTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2022 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

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

	-- RxLua
local Rx = require 'Rx'

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

	-- WF.core.utils
local KSDate = require 'KSDate'
local KSIdGenerator = require 'KSIdGenerator'
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFFileUtils = require 'WFFileUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.oz_client.managed_catalog
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatAssetUtils = require 'WFCatAssetUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatVolumeAgent = require 'WFCatVolumeAgent'

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

local basePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )

local originalVolumeGuid = WFPlatformInfo.isDesktop
		and WFFileUtils.getVolumeGuidForPath( basePath )
		or nil

local derefUniversal = assert( WFCatAssetUtils.derefUniversal )

local hasSeen = {}

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

local WFCatVolumeAgentTest = {}

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

local function createFakeWFCatalog( rxTest )

	local optArgs = {}
		
	if WFPlatformInfo.isMobile() then
		optArgs.indexDefinitions = 'LrmCatalogIndexDefinitions.lua'
	end

	return WFCatMockCatalog.createFakeWFCatalog( rxTest , nil, nil, optArgs )

end

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

local catalogByTest = {}
local volumeAgentByTest = {}

local function setupVolumeAgent( rxTest, overrides )

	overrides = overrides or {}

	local volumeGuid = overrides.volumeGuid or originalVolumeGuid
	local keepAllOriginals = overrides.keepAllOriginals or false
	local keepAllProxies = overrides.keepAllProxies or false
	
	overrides.targetCacheSize = overrides.targetCacheSize or 1000
	
	if not overrides.debugProactivePurge then
		overrides.debugReactivePurge = true
	end

	if not hasSeen[ rxTest ] then
		rxTest:setTimeLimit( overrides.timeLimit or 10 )
	end
	
	hasSeen[ rxTest ] = true

	local wfCatalog = catalogByTest[ rxTest ] 
			or createFakeWFCatalog( rxTest )
	
	catalogByTest[ rxTest ] = wfCatalog

	local keptLocalInfo = {
		keepAllOriginals = keepAllOriginals,
		keepAllProxies = keepAllProxies,
	}
	
	local idleDownloader = nil
	local dataCache = nil
	
	local volumeRootPath = wfCatalog:fileSpec():catalogPath()
	
	if WFPlatformInfo.isDesktop then
	
		-- On LrD, the volume root path is relative to the volume guid
	
		volumeRootPath = WFFileUtils.removeVolumeFromPath( volumeRootPath )
	
	end
	
	local wfCatVolumeAgent = volumeAgentByTest[ rxTest ] 
			or WFCatVolumeAgent( 
					wfCatalog, volumeRootPath, volumeGuid,
					idleDownloader, dataCache, keptLocalInfo, overrides )

	volumeAgentByTest[ rxTest ] = wfCatVolumeAgent

	rxTest:finally( function()
	
		wfCatVolumeAgent:close()
		wfCatVolumeAgent = nil
		
		wfCatalog:close()
		wfCatalog = nil
		
		hasSeen[ rxTest ] = nil
		catalogByTest[ rxTest ] = nil
		volumeAgentByTest[ rxTest ] = nil
	
	end )
	
	return wfCatalog, wfCatVolumeAgent

end

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

local function createAssetDocument( rxTest, wfCatalog, assetId, optArgs )

	local assetDocValue, assetInfo 
			= WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optArgs )

	return assetDocValue, assetInfo

end

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

local function getDateNDaysAgo( n )

	local daysToSeconds = function( days )
		return days * 24 * 60 * 60
	end

	local nDaysAgoInSeconds = KSDate.timeNow() - daysToSeconds( n )

	return KSDate.timeInIso8601Format( nDaysAgoInSeconds )

end

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

-- @mark Misc Tests

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

function WFCatVolumeAgentTest.rxTestDaysToSeconds( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )

	local days = 10
	local expectedSeconds = 864000
	
	local seconds = volumeAgent.private._daysToSeconds( days )
	
	rxTest:expectEqual( expectedSeconds, seconds )
	
end

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

function WFCatVolumeAgentTest.rxTestIsVolumeOnlineStream( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )

	local isOnline = true
	local isReadOnly = false
	
	local observer = rxTest:observe( volumeAgent.private._isVolumeOnlineStream( volumeAgent ) )
	observer:expectData( isOnline, isReadOnly )
	
end

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

-- @mark Tests for Determining HOW MUCH to Purge

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

function WFCatVolumeAgentTest.rxTestGetFreeSpace( rxTest )

	local overrides = {
		minimumFreeSpace = 1000
	}

	local _, volumeAgent = setupVolumeAgent( rxTest, overrides )

	local cachePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	
	-- Setup observer to monitor a volume-status stream side effect
	-- (Updates the remainingSpace value)
	
	local observer = rxTest:observe( volumeAgent:getVolumeStatusStream():pluck( 'remainingSpace' ) )
	
	local freeSpace, totalSpace = volumeAgent.private._getFreeSpace( volumeAgent, cachePath )
	
	rxTest:expectEqual( freeSpace > 0, true )
	
	rxTest:expectEqual( totalSpace > freeSpace, true )
	
	-- Verify Side Effect
	
	local expectedRemainingSpace = freeSpace - overrides.minimumFreeSpace
	
	observer:expectDataWithinTime( 3, expectedRemainingSpace )

end

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

function WFCatVolumeAgentTest.rxTestCalculatePurgeBuffer( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )

	-- Reactive Purge: Photo Cache Too Big
	
	local reason = WFCatVolumeAgent.purgeReasons.TARGET_RANGE
	local currentCacheSize = 200
	local targetCacheSize = 100


	local expectedValue = 25
	
	local value = volumeAgent.private._calculatePurgeBuffer( 
			reason, currentCacheSize, targetCacheSize )
	
	rxTest:expectEqual( expectedValue, value )

	-- Reactive Purge: Device Out of Space, Photo Cache Too Big

	reason = WFCatVolumeAgent.purgeReasons.NO_DISK_SPACE
	currentCacheSize = 200
	targetCacheSize = 100


	expectedValue = 25

	value = volumeAgent.private._calculatePurgeBuffer(
			reason, currentCacheSize, targetCacheSize )

	rxTest:expectEqual( expectedValue, value )

	-- Reactive Purge: Device Out of Space, Photo Cache NOT Too Big

	reason = WFCatVolumeAgent.purgeReasons.NO_DISK_SPACE
	currentCacheSize = 50
	targetCacheSize = 100

	if WFPlatformInfo.isiOS or WFPlatformInfo.isMac then
		expectedValue = 1000 * 1000 * 500
	else
		expectedValue = 1024 * 1024 * 500
	end

	value = volumeAgent.private._calculatePurgeBuffer(
			reason, currentCacheSize, targetCacheSize )

	rxTest:expectEqual( expectedValue, value )

	-- Proactive Purge, All (Mobile)
	
	reason = WFCatVolumeAgent.purgeReasons.PROACTIVE_ALL
	currentCacheSize = 200
	targetCacheSize = 100

	expectedValue = 200
	
	value = volumeAgent.private._calculatePurgeBuffer( 
			reason, currentCacheSize, targetCacheSize )
	
	rxTest:expectEqual( expectedValue, value )

	-- Proactive Purge, Originals (LrD)
	
	reason = WFCatVolumeAgent.purgeReasons.PROACTIVE_ORIGINALS
	currentCacheSize = 200
	targetCacheSize = 100


	expectedValue = 200
	
	value = volumeAgent.private._calculatePurgeBuffer( 
			reason, currentCacheSize, targetCacheSize )
	
	rxTest:expectEqual( expectedValue, value )

	-- Proactive Purge, Proxies (LrD)
	
	reason = WFCatVolumeAgent.purgeReasons.PROACTIVE_PROXIES
	currentCacheSize = 200
	targetCacheSize = 100

	expectedValue = 200
	
	value = volumeAgent.private._calculatePurgeBuffer( 
			reason, currentCacheSize, targetCacheSize )
	
	rxTest:expectEqual( expectedValue, value )

end

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

function WFCatVolumeAgentTest.rxTestGetDiskSpaceNeeded_BelowTarget( rxTest )

	-- Plenty of free space, cache is half full

	local overrides = {
		debugMockDiskFreeSpace = 100,
		debugMockLrSize = 10,
		targetCacheSize = 20,
		minimumFreeSpace = 0
	}

	local _, volumeAgent = setupVolumeAgent( rxTest, overrides )

	local expectedSpaceNeededToPreventNoDiskSpace = 0
	local expectedSpaceNeededToReachTargetDiskSpace = 0
	local expectedPercentOfTargetSpaceUsed = 0.5

	rxTest:expectSingleValue( volumeAgent.private._getDiskSpaceNeeded( volumeAgent ),
			expectedSpaceNeededToPreventNoDiskSpace,
			expectedSpaceNeededToReachTargetDiskSpace,
			expectedPercentOfTargetSpaceUsed )

end

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

function WFCatVolumeAgentTest.rxTestGetDiskSpaceNeeded_AboveTarget( rxTest )

	-- Cache is twice as big as it should be, plenty of free space.

	local overrides = {
		debugMockDiskFreeSpace = 100,
		debugMockLrSize = 10,
		targetCacheSize = 5,
		minimumFreeSpace = 0
	}

	local _, volumeAgent = setupVolumeAgent( rxTest, overrides )

	local expectedSpaceNeededToPreventNoDiskSpace = 0
	local expectedSpaceNeededToReachTargetDiskSpace = 5
	local expectedPercentOfTargetSpaceUsed = 2

	rxTest:expectSingleValue( volumeAgent.private._getDiskSpaceNeeded( volumeAgent ),
			expectedSpaceNeededToPreventNoDiskSpace,
			expectedSpaceNeededToReachTargetDiskSpace,
			expectedPercentOfTargetSpaceUsed )

end

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

function WFCatVolumeAgentTest.rxTestGetDiskSpaceNeeded_BelowTarget_OutOfSpace( rxTest )

	-- No disk space left, but cache is not full.

	local overrides = {
		debugMockDiskFreeSpace = 100,
		debugMockLrSize = 10,
		targetCacheSize = 20,
		minimumFreeSpace = 200
	}

	local _, volumeAgent = setupVolumeAgent( rxTest, overrides )

	local expectedSpaceNeededToPreventNoDiskSpace = 100
	local expectedSpaceNeededToReachTargetDiskSpace = 0
	local expectedPercentOfTargetSpaceUsed = 0.5

	rxTest:expectSingleValue( volumeAgent.private._getDiskSpaceNeeded( volumeAgent ),
			expectedSpaceNeededToPreventNoDiskSpace,
			expectedSpaceNeededToReachTargetDiskSpace,
			expectedPercentOfTargetSpaceUsed )

end

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

function WFCatVolumeAgentTest.rxTestGetDiskSpaceNeeded_AboveTarget_OutOfSpace( rxTest )

	-- No disk space left, and cache is bigger than it should be.
	
	-- In this scenario, preventing a no disk space scenario would also reclaim enough
	-- space to reach the target disk space usage.

	local overrides = {
		debugMockDiskFreeSpace = 100,
		debugMockLrSize = 10,
		targetCacheSize = 5,
		minimumFreeSpace = 200
	}

	local _, volumeAgent = setupVolumeAgent( rxTest, overrides )

	local expectedSpaceNeededToPreventNoDiskSpace = 100
	local expectedSpaceNeededToReachTargetDiskSpace = 0
	local expectedPercentOfTargetSpaceUsed = 2

	rxTest:expectSingleValue( volumeAgent.private._getDiskSpaceNeeded( volumeAgent ),
			expectedSpaceNeededToPreventNoDiskSpace,
			expectedSpaceNeededToReachTargetDiskSpace,
			expectedPercentOfTargetSpaceUsed )

end

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

function WFCatVolumeAgentTest.rxTestGetDiskSpaceNeeded_AboveTarget_OutOfSpace_Variation( rxTest )

	-- No disk space left, and cache is bigger than it should be.

	-- In this scenario, preventing a no disk space scenario would NOT reclaim enough
	-- space to reach the target disk space usage.

	local overrides = {
		debugMockDiskFreeSpace = 100,
		debugMockLrSize = 1000,
		targetCacheSize = 500,
		minimumFreeSpace = 200
	}

	local _, volumeAgent = setupVolumeAgent( rxTest, overrides )

	local expectedSpaceNeededToPreventNoDiskSpace = 100
	local expectedSpaceNeededToReachTargetDiskSpace = 400
	local expectedPercentOfTargetSpaceUsed = 2

	rxTest:expectSingleValue( volumeAgent.private._getDiskSpaceNeeded( volumeAgent ),
			expectedSpaceNeededToPreventNoDiskSpace,
			expectedSpaceNeededToReachTargetDiskSpace,
			expectedPercentOfTargetSpaceUsed )

end

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

-- @mark Tests for Purge Algorithm 

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

function WFCatVolumeAgentTest.rxTestIsProtectedFromPurge( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )

	local protectedLocalId = 1
	local protectedDocId = KSIdGenerator.uuidInLowercaseHex()

	local notProtectedLocalId = 2
	local notProtectedDocId = KSIdGenerator.uuidInLowercaseHex()

	local protectedFromPurge = {
		[ protectedLocalId ]= true,
		[ protectedDocId ] = true
	}

	local isProtected = volumeAgent.private._isProtectedFromPurge( 
			volumeAgent, protectedFromPurge, protectedLocalId, protectedDocId )

	rxTest:expectEqual( true, isProtected )

	isProtected = volumeAgent.private._isProtectedFromPurge( 
			volumeAgent, protectedFromPurge, protectedLocalId, notProtectedDocId )

	rxTest:expectEqual( true, isProtected )
	
	isProtected = volumeAgent.private._isProtectedFromPurge( 
			volumeAgent, protectedFromPurge, notProtectedLocalId, protectedDocId )

	rxTest:expectEqual( true, isProtected )
	
	isProtected = volumeAgent.private._isProtectedFromPurge( 
			volumeAgent, protectedFromPurge, notProtectedLocalId, notProtectedDocId )

	rxTest:expectEqual( false, isProtected )

end

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

function WFCatVolumeAgentTest.rxTestGetBinaryTypesToPurge( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )

	local purgeReason, keepAllOriginals, keepAllProxies

	if WFPlatformInfo.isMobile() then
	
		keepAllOriginals = false -- Always false for mobile.
		keepAllProxies = false -- Always false for mobile.
	
		purgeReason = volumeAgent.private._purgeReasons.PROACTIVE_ALL
	
		local binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
	
		local expectedTypes = {
			'original'
		}
	
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
	
		purgeReason = volumeAgent.private._purgeReasons.TARGET_RANGE
	
		binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
			
		expectedTypes = {
			'original',
			'proxy',
			'xmp_develop',
			'aux',
			'fullsize',
			'preview'
		}
	
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
	
	else
	
		keepAllOriginals = false
		keepAllProxies = false
	
		purgeReason = volumeAgent.private._purgeReasons.PROACTIVE_ORIGINALS
	
		local binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
	
		local expectedTypes = {
			'original'
		}
	
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
	
		purgeReason = volumeAgent.private._purgeReasons.PROACTIVE_PROXIES
	
		binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
			
		expectedTypes = {
			'proxy'
		}
	
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
	
		purgeReason = volumeAgent.private._purgeReasons.TARGET_RANGE
	
		binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
			
		expectedTypes = {
			'original',
			'proxy',
			'xmp_develop',
			'aux'
		}
	
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
	
		keepAllOriginals = true
		keepAllProxies = true
	
		purgeReason = volumeAgent.private._purgeReasons.PROACTIVE_ORIGINALS
	
		binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
	
		expectedTypes = {}
	
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
	
		purgeReason = volumeAgent.private._purgeReasons.PROACTIVE_PROXIES
	
		binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )
			
		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
		
		purgeReason = volumeAgent.private._purgeReasons.TARGET_RANGE
	
		binaryTypesToPurge = volumeAgent.private._getBinaryTypesToPurge( 
			purgeReason, keepAllOriginals, keepAllProxies )

		rxTest:expectEqual( expectedTypes, binaryTypesToPurge )
		
	end

end

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

function WFCatVolumeAgentTest.rxTestProcessBatchOfBinariesForPurge( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )
	
	local spaceNeeded = 10
	
	local binaryType = 'proxy'
	
	local asset1LocalId = 1
	local asset2LocalId = 2
	local asset3LocalId = 3
	local asset4LocalId = 4
	
	local asset1DocId = KSIdGenerator.uuidInLowercaseHex()
	local asset2DocId = KSIdGenerator.uuidInLowercaseHex()
	local asset3DocId = KSIdGenerator.uuidInLowercaseHex()
	local asset4DocId = KSIdGenerator.uuidInLowercaseHex()
	
	local batchOfAssets = {
		{ asset1LocalId, asset1DocId, 3, { '2560' } },
		{ asset2LocalId, asset2DocId, 3, { '1080p' } },
		{ asset2LocalId, asset2DocId, 3, { '1080p' } }, -- Duplicate
		{ asset3LocalId, asset3DocId, 3, { '720p', '360p' } }
	}
	
	local noopFn = function() end
	
	local getNextBatch = function()
	
		return Rx.Observable.just( 
			{
				{ asset4LocalId, asset4DocId, 3, { 'fullsize' } }
			}, 
			noopFn )
	
	end 

	-- Verify processBatchOfBinariesForPurge when there are enough
	-- binaries to purge to reclaim spaceNeeded.

	local processBinariesStream = Rx.ReplaySubject( 1 )

	volumeAgent.private._processBatchOfBinariesForPurge( 
			volumeAgent, binaryType, spaceNeeded, batchOfAssets, 
			getNextBatch, {}, processBinariesStream )

	rxTest:expectSingleValue( processBinariesStream, {
		proxy = {
			[ asset1LocalId ] = { '2560' },
			[ asset2LocalId ] = { '1080p' },
			[ asset3LocalId ] = { '720p', '360p' },
			[ asset4LocalId ] = { 'fullsize' },
		}
	}, -2 )

	-- Reset for the next test
	
	processBinariesStream:dispose()
	volumeAgent._isBinaryMarkedForPurgeByTypeAndLocalId = {}
		-- Clear the cache of previously purged binaries
	
	-- Verify processBatchOfBinariesForPurge when there are NOT enough
	-- binaries to purge to reclaim spaceNeeded.
	
	processBinariesStream = Rx.ReplaySubject( 1 )

	getNextBatch = function()
	
		return Rx.Observable.just( {}, noopFn )
	
	end 

	volumeAgent.private._processBatchOfBinariesForPurge( 
			volumeAgent, binaryType, spaceNeeded, batchOfAssets, 
			getNextBatch, {}, processBinariesStream )

	rxTest:expectSingleValue( processBinariesStream, {
		proxy = {
			[ asset1LocalId ] = { '2560' },
			[ asset2LocalId ] = { '1080p' },
			[ asset3LocalId ] = { '720p', '360p' }
		}
	}, 1 )

end

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

function WFCatVolumeAgentTest.rxTestFetchBinariesToPurge( rxTest )

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest )
	
	local docValues = {}
	
	local assetLocalId = 1
	local assetDocId = KSIdGenerator.uuidInLowercaseHex()
	local assetDocValue = createAssetDocument( rxTest, wfCatalog, assetDocId )
	KSTableUtils.setNested( assetDocValue, nil, 'importSource', 'importTimestamp' )
		-- want this to be considered for inactive purge so remove importTimestamp
		-- which could make asset seem active if timestamp is recent

	docValues[ #docValues + 1 ] = assetDocValue
	
	local asset2LocalId = 2
	local asset2DocId = KSIdGenerator.uuidInLowercaseHex()
	local asset2DocValue = createAssetDocument( rxTest, wfCatalog, asset2DocId )
	KSTableUtils.setNested( asset2DocValue, getDateNDaysAgo( 1 ), '_serverProvidedMetadata', 'updated' )
		-- Should be important because it has been updated recently.
		-- This should not be considered for INACTIVE purge.
		
	docValues[ #docValues + 1 ] = asset2DocValue
	
	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( docValues ) )
	
	local spaceNeeded = 100
	local binaryType = 'proxy'
	local isProactivePurge = false

	-- Verify _fetchBinariesToPurge for REACTIVE purge.

	local processBinariesStream = volumeAgent.private._fetchBinariesToPurge( 
			volumeAgent, isProactivePurge, binaryType, spaceNeeded, {} ) 

	rxTest:expectSingleValue( processBinariesStream, {
		proxy = {
			[ assetLocalId ] = { '2560' },
			[ asset2LocalId ] = { '2560' }
		} }, 16 )
			-- 100 - ( #"proxy_data"=10 + #uuid=32 ) * 2 = 16

	-- Reset for the next test
	
	processBinariesStream:dispose()
	volumeAgent._isBinaryMarkedForPurgeByTypeAndLocalId = {}
		-- Clear the cache of previously purged binaries

	isProactivePurge = true

	-- Verify _fetchBinariesToPurge for PROACTIVE purge; should only
	-- fetch inactive/unimportant assets.

	processBinariesStream = volumeAgent.private._fetchBinariesToPurge( 
			volumeAgent, isProactivePurge, binaryType, spaceNeeded, {} ) 

	rxTest:expectSingleValue( processBinariesStream, {
		proxy = {
			[ assetLocalId ] = { '2560' }
		} }, 58 )
			-- 100 - ( #"proxy_data"=10 + #uuid=32 ) = 58

end

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

function WFCatVolumeAgentTest.rxTestPurgeToReclaimSpace( rxTest )

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest )
	
	local docValues = {}
	
	local assetLocalId = 1
	local assetDocValue, asset1Info = createAssetDocument( rxTest, wfCatalog )
	docValues[ #docValues + 1 ] = assetDocValue

	local asset2LocalId = 2
	local asset2DocValue, asset2Info = createAssetDocument( rxTest, wfCatalog )
	docValues[ #docValues + 1 ] = asset2DocValue

	local asset3LocalId = 3	
	local asset3DocValue, asset3Info = createAssetDocument( rxTest, wfCatalog )
	docValues[ #docValues + 1 ] = asset3DocValue
	
	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( docValues ) )

	local reason = volumeAgent.private._purgeReasons.TARGET_RANGE
	
	local binaryTypesToPurge = { 'original', 'proxy', 'xmp_develop' }
	
	local spaceNeeded = 100000

	local stream = volumeAgent.private._purgeToReclaimSpace( 
			volumeAgent, reason, binaryTypesToPurge, spaceNeeded ) 

	local spaceReclaimed = 
			asset1Info.originalFileSize
			+ asset1Info.xmpSidecarFileSize
			+ asset1Info.proxyFileSize
			+ asset1Info.xmpDevelopSettingsFileSize
			+ asset2Info.originalFileSize
			+ asset2Info.xmpSidecarFileSize
			+ asset2Info.proxyFileSize
			+ asset1Info.xmpDevelopSettingsFileSize
			+ asset3Info.originalFileSize
			+ asset3Info.xmpSidecarFileSize
			+ asset3Info.proxyFileSize
			+ asset3Info.xmpDevelopSettingsFileSize
			
	local expectedSpaceNeeded = spaceNeeded - spaceReclaimed

	local expectedPurgeList = {
		original = {
			[ assetLocalId ] = true,
			[ asset2LocalId ] = true,
			[ asset3LocalId ] = true
		},
		proxy = {
			[ assetLocalId ] = { '2560' },
			[ asset2LocalId ] = { '2560' },
			[ asset3LocalId ] = { '2560' }
		},
		xmp_develop = {
			[ assetLocalId ] = true,
			[ asset2LocalId ] = true,
			[ asset3LocalId ] = true
		}
	}

	local wereFilesDeleted = true

	rxTest:expectSingleValue( 
			stream, expectedSpaceNeeded, expectedPurgeList, wereFilesDeleted )

end

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

function WFCatVolumeAgentTest.rxTestPurgeToReclaimSpace_AllOrNothing( rxTest )

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest )
	
	local assetLocalId = 1
	local assetDocValue, asset1Info = createAssetDocument( rxTest, wfCatalog )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDocValue ) )

	local reason = volumeAgent.private._purgeReasons.NO_DISK_SPACE_ALL_OR_NOTHING
	
	local binaryTypesToPurge = { 'proxy' }
	
	local spaceNeeded = 1

	local stream = volumeAgent.private._purgeToReclaimSpace( 
			volumeAgent, reason, binaryTypesToPurge, spaceNeeded ) 

	local spaceReclaimed = asset1Info.proxyFileSize
			
	local expectedSpaceNeeded = spaceNeeded - spaceReclaimed

	local expectedPurgeList = {
		proxy = {
			[ assetLocalId ] = { '2560' }
		}
	}

	local wereFilesDeleted = true

	rxTest:expectSingleValue( 
			stream, expectedSpaceNeeded, expectedPurgeList, wereFilesDeleted )

end

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

function WFCatVolumeAgentTest.rxTestPurgeToReclaimSpace_AllOrNothing_NotEnoughSpace( rxTest )

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest )
	
	local assetLocalId = 1
	local assetDocValue, asset1Info = createAssetDocument( rxTest, wfCatalog )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDocValue ) )

	local reason = volumeAgent.private._purgeReasons.NO_DISK_SPACE_ALL_OR_NOTHING
	
	local binaryTypesToPurge = { 'proxy' }
	
	local spaceNeeded = 100000

	local stream = volumeAgent.private._purgeToReclaimSpace( 
			volumeAgent, reason, binaryTypesToPurge, spaceNeeded ) 

	local spaceReclaimed = asset1Info.proxyFileSize
			
	local expectedSpaceNeeded = spaceNeeded - spaceReclaimed

	local expectedPurgeList = {
		proxy = {
			binaryType = "proxy",
			[ assetLocalId ] = { '2560' }
		}
	}

	local wereFilesDeleted = false

	rxTest:expectSingleValue( 
			stream, expectedSpaceNeeded, expectedPurgeList, wereFilesDeleted )

end

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

-- @mark Tests for Determining WHEN to Reactive Purge

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

local function createNAssetDocuments( rxTest, wfCatalog, n, optArgs )

	local docValues = {}
	
	for i=1, n, 1 do
	
		local assetDocValue = createAssetDocument( rxTest, wfCatalog, nil, optArgs )

		KSTableUtils.setNested( assetDocValue, nil, 'importSource', 'importTimestamp' )
			-- by default want assets to be considered for inactive purge so remove
			-- importTimestamp which could make asset seem active if timestamp is recent

		docValues[ #docValues + 1 ] = assetDocValue

	end
	
	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( docValues ) )

	return docValues

end

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

local function testReactivePurging( rxTest, overrides, expectedPurgeLists )

	local debugMockCacheSize = overrides.debugMockLrSize
	local targetCacheSize = overrides.targetCacheSize
	local debugMockDiskFreeSpace = overrides.debugMockDiskFreeSpace
	local minimumFreeSpace = overrides.minimumFreeSpace
	
	overrides.debugReactivePurge = true
	
	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	createNAssetDocuments( rxTest, wfCatalog, 1 )
	
	local purgeListStreamCount = 0
	
	local purgeListObserver = rxTest:observe( volumeAgent:_DEBUG_purgeListStream()
		:map( function( ... )
			purgeListStreamCount = purgeListStreamCount + 1
			return ...
		end ) )
	
	local reactivePurgeCompleteObserver = rxTest:observe( volumeAgent:_DEBUG_reactivePurgeRunCompleteStream()
		:map( function( results )
		
			if results.spaceNeededAfterPurgeToReachTargetDiskSpace > 0 then
		
				-- Verify that things were purged.
		
				rxTest:expectEqual( true, 
						results.spaceNeededAfterPurgeToReachTargetDiskSpace 
								< results.spaceNeededBeforePurgeToReachTargetDiskSpace )
		
			end
		
			results.spaceNeededAfterPurgeToReachTargetDiskSpace = nil
		
			if results.spaceNeededAfterPurgeToPreventNoDiskSpace > 0 then
		
				-- Verify that things were purged.
		
				rxTest:expectEqual( true, 
						results.spaceNeededAfterPurgeToPreventNoDiskSpace 
								< results.spaceNeededBeforePurgeToPreventNoDiskSpace )
		
			end
		
			results.spaceNeededAfterPurgeToReachTargetDiskSpace = nil
			results.spaceNeededAfterPurgeToPreventNoDiskSpace = nil
			
			return results
		
		end ) )
		
	volumeAgent:start()
	
	-- Verify the Purge List Generated via Reactive Purge
	
	for _, expectedPurgeList in ipairs( expectedPurgeLists ) do
	
		purgeListObserver:expectData( expectedPurgeList )
		
	end
	
	-- Wait for Reactive Purge To Complete
	
	local expectedSpaceNeededBeforePurgeToPreventNoDiskSpace = 0
	
	if minimumFreeSpace then
	
		expectedSpaceNeededBeforePurgeToPreventNoDiskSpace
				= minimumFreeSpace - debugMockDiskFreeSpace
			
	end
	
	local expectedSpaceNeededBeforePurgeToReachTargetDiskSpace
		= math.max( 0, debugMockCacheSize - targetCacheSize - expectedSpaceNeededBeforePurgeToPreventNoDiskSpace )
	
	reactivePurgeCompleteObserver:expectDataWithinTime( 5, {
		spaceNeededBeforePurgeToPreventNoDiskSpace = expectedSpaceNeededBeforePurgeToPreventNoDiskSpace,
		spaceNeededBeforePurgeToReachTargetDiskSpace = expectedSpaceNeededBeforePurgeToReachTargetDiskSpace,
	} )
	
	volumeAgent:stop()

	rxTest:expectEqual( #expectedPurgeLists, purgeListStreamCount )

end

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

function WFCatVolumeAgentTest.rxTestReactivePurging_AboveTarget( rxTest )

	-- Cache is too big. Purging one original should fix this.

	local overrides = {
		debugMockLrSize = 10,
		targetCacheSize = 1
	}

	local expectedPurgeLists = {
		{
			original = {
				true
			}
		}
	}

	testReactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestReactivePurging_AboveTarget_OutOfSpace( rxTest )

	-- Cache is too big and hard drive is out of space.

	local overrides = {
		debugMockLrSize = 100,
		targetCacheSize = 1,
		debugMockDiskFreeSpace = 1000,
		minimumFreeSpace = 10000
	}
	
	local expectedPurgeLists
	
	if WFPlatformInfo.isDesktop then

		expectedPurgeLists = {
			{
				original = {
					true
				},
				proxy = {
					{
						"2560"
					}
				},
				xmp_develop = {
					true,
				},
			}
		}

	else
	
		expectedPurgeLists = {
			{
				fullsize = {
					true
				},
				original = {
					true
				},
				preview = {
					true
				},
				proxy = {
					{
						"2560"
					}
				},
				xmp_develop = {
					true
				}
			}
		}
	
	end

	testReactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestReactivePurging_AboveTarget_OutOfSpace_Variation( rxTest )

	local overrides = {
		debugMockLrSize = 10000,
		targetCacheSize = 1000,
		debugMockDiskFreeSpace = 1,
		minimumFreeSpace = 10
	}
	
	local expectedPurgeLists

	if WFPlatformInfo.isDesktop then

		expectedPurgeLists = {
			{
				original = {
					true
				}
			},
			{
				proxy = {
					{
						"2560"
					}
				},
				xmp_develop = {
					true,
				},
			}
		}

	else
	
		expectedPurgeLists = {
			{
				original = {
					true
				}
			},
			{
				fullsize = {
					true
				},
				preview = {
					true
				},
				proxy = {
					{
						"2560"
					}
				},
				xmp_develop = {
					true
				}
			}
		}
	
	end

	testReactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestReactivePurging_BelowTarget_OutOfSpace( rxTest )

	local overrides = {
		debugMockLrSize = 1,
		targetCacheSize = 100,
		debugMockDiskFreeSpace = 1000,
		minimumFreeSpace = 10000
	}

	local expectedPurgeLists

	if WFPlatformInfo.isDesktop then

		expectedPurgeLists = {
			{
				original = {
					true
				},
				proxy = {
					{
						"2560"
					}
				},
				xmp_develop = {
					true,
				},
			}
		}

	else
	
		expectedPurgeLists = {
			{
				fullsize = {
					true
				},
				original = {
					true
				},
				preview = {
					true
				},
				proxy = {
					{
						"2560"
					}
				},
				xmp_develop = {
					true
				}
			}
		}
	
	end

	testReactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

-- @mark Tests for Determining WHEN to Proactive Purge

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

local function testProactivePurging( rxTest, overrides, expectedPurgeLists )

	local debugMockCacheSize = overrides.debugMockLrSize
	
	local keepAllOriginals = overrides.keepAllOriginals
	
	overrides.debugProactivePurge = true
	
	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	createNAssetDocuments( rxTest, wfCatalog, 1 )
	
	local purgeListStreamCount = 0
	
	local purgeListObserver = rxTest:observe( volumeAgent:_DEBUG_purgeListStream()
		:map( function( ... )
			purgeListStreamCount = purgeListStreamCount + 1
			return ...
		end ) )
	
	local purgeCompleteObserver = rxTest:observe( volumeAgent:_DEBUG_proactivePurgeRunCompleteStream()
	
		:map( function( results )
		
			if results.spaceToAttemptToReclaimProactively > 0 then
		
				-- Verify that things were purged.
		
				rxTest:expectEqual( true, results.spaceReclaimed > 0 )
		
			end
			
			results.spaceReclaimed = nil
			
			return results
		
		end ) )
		
	volumeAgent:start()
	
	-- Verify the Purge List Generated via Reactive Purge
	
	for _, expectedPurgeList in ipairs( expectedPurgeLists ) do
	
		purgeListObserver:expectData( expectedPurgeList )
		
	end
	
	-- Wait for Proactive Purge To Complete
	
	local expectedSpaceToAttemptToReclaimProactively = debugMockCacheSize
	
	local purgeReason = WFPlatformInfo.isDesktop 
			and volumeAgent.private._purgeReasons.PROACTIVE_ORIGINALS
			or volumeAgent.private._purgeReasons.PROACTIVE_ALL
	
	if keepAllOriginals then
	
		purgeCompleteObserver:expectDataWithinTime( 5, {
			reason = purgeReason,
			spaceToAttemptToReclaimProactively = 0
		} )
	
	else
	
		purgeCompleteObserver:expectDataWithinTime( 5, {
			reason = purgeReason,
			spaceToAttemptToReclaimProactively = expectedSpaceToAttemptToReclaimProactively,
		} )
	
	end
	
	volumeAgent:stop()

	rxTest:expectEqual( #expectedPurgeLists, purgeListStreamCount )

end

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

function WFCatVolumeAgentTest.rxTestProactivePurging_AboveTarget( rxTest )

	-- Above the target threshold, we can purge both originals and proxies.

	local overrides = {
		debugMockLrSize = 10000,
		targetCacheSize = 1000,
	}

	local expectedPurgeLists

	if WFPlatformInfo.isDesktop then
	
		expectedPurgeLists = {
			{
				original = {
					true
				}
			}
		}
	
	else
	
		expectedPurgeLists = {
			{
				original = {
					true
				}
			}
		}
	
	end

	testProactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

if WFPlatformInfo.isDesktop then

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

function WFCatVolumeAgentTest.rxTestProactivePurging_KeepAllOriginals( rxTest )

	-- If keepAllOriginals is set, we should not purge originals.

	local overrides = {
		debugMockLrSize = 10000,
		targetCacheSize = 9000,
		keepAllOriginals = true
	}

	local expectedPurgeLists = {}

	testProactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestProactivePurging_KeepAllProxies( rxTest )
	
	-- If keepAllProxies is set, we should not purge proxies.
	
	local overrides = {
		debugMockLrSize = 10000,
		targetCacheSize = 9000,
		keepAllProxies = true
	}

	local expectedPurgeLists = {
		{
			original = {
				true
			}
		}
	}

	testProactivePurging( rxTest, overrides, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestProactivePurging_KeepEverything( rxTest )

	-- Should be nothing to purge

	local overrides = {
		debugMockLrSize = 10000,
		targetCacheSize = 9000,
		keepAllOriginals = true,
		keepAllProxies = true
	}

	local expectedPurgeLists = {}

	testProactivePurging( rxTest, overrides, expectedPurgeLists )

end

else

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

function WFCatVolumeAgentTest.rxTestProactivePurging_BelowTarget( rxTest )

	local overrides = {
		debugMockLrSize = 10000,
		targetCacheSize = 5000,
	}

	local expectedPurgeLists = {
		{
			original = {
				true
			}
		}
	}

	testProactivePurging( rxTest, overrides, expectedPurgeLists )

end

end

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

-- @mark API / Misc Tests

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

local function doesUniversalFieldExist( docValue, ... )

	return not not derefUniversal( docValue, ... )

end

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

local function docHasOriginal( rxTest, wfCatalog, docId )

	local docValue = rxTest:getFirstValue( wfCatalog:docStore():docValue( docId ) )
	
	return doesUniversalFieldExist(
			docValue, 'files', 'original', 'master', 'relativePath' )
					
end

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

function WFCatVolumeAgentTest.rxTestProtectFromPurge( rxTest )
	
	local overrides = {
		debugMockLrSize = 100000,
		targetCacheSize = 100
	}
	
	overrides.debugReactivePurge = true
	
	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	local docValues = createNAssetDocuments( rxTest, wfCatalog, 2 )
	
	local protectedDocId = docValues[ 1 ]._id
	local unprotectedDocId = docValues[ 2 ]._id
	
	volumeAgent:protectFromPurge( { protectedDocId } )
	
	local reactivePurgeCompleteObserver = rxTest:observe( volumeAgent:_DEBUG_reactivePurgeRunCompleteStream()
		:map( function() 
			return true
		end ) )
		
	-- Verify documents have originals
	
	rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, protectedDocId ) )
	rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, unprotectedDocId ) )
		
	volumeAgent:start()

	-- Wait for Reactive Purge To Complete
	
	reactivePurgeCompleteObserver:expectData( true )

	
	volumeAgent:stop()
	
	-- Verify everything was purged except the protected document.
	
	rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, protectedDocId ) )
	rxTest:expectEqual( false, docHasOriginal( rxTest, wfCatalog, unprotectedDocId ) )
	
	-- Now remove the purge protection and reactivate purge.
	
	volumeAgent:releaseProtectionFromPurge( { protectedDocId } )
	
	volumeAgent:start()

	-- Wait for Reactive Purge To Complete
	
	reactivePurgeCompleteObserver:expectData( true )
	
	volumeAgent:stop()
	
	-- Verify the document has been purged.
	
	rxTest:expectEqual( false, docHasOriginal( rxTest, wfCatalog, protectedDocId ) )
	
end

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

function WFCatVolumeAgentTest.rxTestAddToProtectedRingBuffer( rxTest )
	
	local overrides = {
		debugMockLrSize = 100000,
		targetCacheSize = 100
	}
	
	overrides.debugReactivePurge = true
	
	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	-- The protected ring buffer has a size of 10, so adding 12 entries to it
	-- should remove protection from the first two entries.
	
	local docValues = createNAssetDocuments( rxTest, wfCatalog, 12 )
	
	for i=1,12,1 do
		volumeAgent:addToProtectedRingBuffer( docValues[ i ]._id )
	end
	
	local reactivePurgeCompleteObserver = rxTest:observe( volumeAgent:_DEBUG_reactivePurgeRunCompleteStream()
		:map( function() 
			return true
		end ) )
		
	-- Verify documents have originals
	
	for i=1,12,1 do
		rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, docValues[ i ]._id ) )
	end
	
	volumeAgent:start()

	-- Wait for Reactive Purge To Complete
	
	reactivePurgeCompleteObserver:expectData( true )

	volumeAgent:stop()
	
	-- Verify everything was purged except the protected documents.
	
	rxTest:expectEqual( false, docHasOriginal( rxTest, wfCatalog, docValues[ 1 ]._id ) )
	rxTest:expectEqual( false, docHasOriginal( rxTest, wfCatalog, docValues[ 2 ]._id ) )
	
	for i=3,12,1 do
		rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, docValues[ i ]._id ) )
	end
	
end

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

function WFCatVolumeAgentTest.rxTestVolumeStatusStream_NoSpace( rxTest )

	-- Create a no disk space scenario using the overrides.
	
	local overrides = {
		debugMockLrSize = 1000,
		targetCacheSize = 10000,
		debugMockDiskFreeSpace = 0,
		minimumFreeSpace = 100
	}

	local _, wfCatVolumeAgent = setupVolumeAgent( rxTest, overrides )
	
	local statusStream = wfCatVolumeAgent:getVolumeStatusStream()
	local observer = rxTest:observe( statusStream )
	
	wfCatVolumeAgent:start()

	observer:expectDataWithinTime( 5, {
		volumeGuid = originalVolumeGuid,
		isOnline = true, 
		isOutOfSpace = true,
		isReadOnly = false,
		remainingSpace = 0,
		isOverTargetDiskSpaceUsage = false,
	} )
	
end

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

function WFCatVolumeAgentTest.rxTestVolumeStatusStream_BelowTarget( rxTest )

	local overrides = {
		debugMockLrSize = 1000,
		debugMockDiskFreeSpace = 100000,
		targetCacheSize = 10000,
		minimumFreeSpace = 0
	}

	local _, wfCatVolumeAgent = setupVolumeAgent( rxTest, overrides )
	
	local statusStream = wfCatVolumeAgent:getVolumeStatusStream()
	local observer = rxTest:observe( statusStream )
	
	wfCatVolumeAgent:start()

	observer:expectDataWithinTime( 5, {
		volumeGuid = originalVolumeGuid,
		isOnline = true, 
		isOutOfSpace = false,
		isReadOnly = false,
		remainingSpace = overrides.debugMockDiskFreeSpace,
		isOverTargetDiskSpaceUsage = false,
	} )
	
end

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

function WFCatVolumeAgentTest.rxTestVolumeStatusStream_AboveTarget( rxTest )

	local overrides = {
		debugMockLrSize = 1000,
		debugMockDiskFreeSpace = 100000,
		targetCacheSize = 100,
		minimumFreeSpace = 0
	}

	local _, wfCatVolumeAgent = setupVolumeAgent( rxTest, overrides )
	
	local statusStream = wfCatVolumeAgent:getVolumeStatusStream()
	local observer = rxTest:observe( statusStream )
	
	wfCatVolumeAgent:start()

	observer:expectDataWithinTime( 5, {
		volumeGuid = originalVolumeGuid,
		isOnline = true, 
		isOverTargetDiskSpaceUsage = true,
		isOutOfSpace = false,
		isReadOnly = false,
		remainingSpace = overrides.debugMockDiskFreeSpace,
		
	} )
	
end

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

function WFCatVolumeAgentTest.rxTestVolumeStatusStream_OfflineVolume( rxTest )

	local overrides = {
		debugMockLrSize = 100,
		debugMockDiskFreeSpace = 10000,
		volumeGuid = KSIdGenerator.uuidInLowercaseHex(),
		targetCacheSize = 200,
		minimumFreeSpace = 0
	}

	local _, wfCatVolumeAgent = setupVolumeAgent( rxTest, overrides )
	
	local statusStream = wfCatVolumeAgent:getVolumeStatusStream()
	local observer = rxTest:observe( statusStream )
	
	wfCatVolumeAgent:start()

	observer:expectDataWithinTime( 5, {
		volumeGuid = overrides.volumeGuid,
		isOnline = not WFPlatformInfo.isDesktop, 
		isOutOfSpace = false,
		isReadOnly = false,
		remainingSpace = overrides.debugMockDiskFreeSpace,
		isOverTargetDiskSpaceUsage = false,
	} )
	
end

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

function WFCatVolumeAgentTest.rxTestStopPurging_testNightly( rxTest )

	local overrides = {
		debugReactivePurge = true
	}

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	local docValues = createNAssetDocuments( rxTest, wfCatalog, 3 )
		
	for _, docValue in ipairs( docValues ) do
		rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, docValue._id ) )
	end
		
	volumeAgent:start()
	volumeAgent:stop()
	
	-- Sleep and expect the same results
	
	rxTest:sleep( 2 )

	for _, docValue in ipairs( docValues ) do
		rxTest:expectEqual( true, docHasOriginal( rxTest, wfCatalog, docValue._id ) )
	end

end

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

local desc = 'required description'

local function testTryToClaimSpace( 
		rxTest, overrides, spaceToReclaim, expectedSpaceToReclaim, expectedPurgeLists )

	overrides.debugReactivePurge = true
	
	local forcePurge = not not overrides.forcePurge
	
	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	createNAssetDocuments( rxTest, wfCatalog, 1 )
	
	local purgeListStreamCount = 0
	
	local purgeListObserver = rxTest:observe( volumeAgent:_DEBUG_purgeListStream()
	
		:map( function( purgeList )
			
			if next( purgeList ) then
				purgeListStreamCount = purgeListStreamCount + 1
			end
			
			return purgeList
			
		end ) )

	rxTest:expectSingleValue( volumeAgent:tryToClaimSpace( desc, spaceToReclaim, forcePurge ), 
			expectedSpaceToReclaim )
	
	local shouldFilesExist = expectedSpaceToReclaim < spaceToReclaim and not forcePurge

	-- Verify the Purge List Generated via Reactive Purge
	
	for binaryType, expectedPurgeList in ipairs( expectedPurgeLists ) do
	
		purgeListObserver:expectDataWithinTime( 2, expectedPurgeList )
		
		-- Verify that things were purged when appropriate.
		
		for id in pairs( expectedPurgeList ) do
			if binaryType == 'original' then
				rxTest:expectEqual( shouldFilesExist, docHasOriginal( rxTest, wfCatalog, id ) )
			end
		end
		
	end
	
	rxTest:expectEqual( #expectedPurgeLists, purgeListStreamCount )
	
end

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

function WFCatVolumeAgentTest.rxTestTryToClaimSpace_Success( rxTest )

	local overrides = {
		debugMockLrSize = 1000,
		targetCacheSize = 1000,
		debugMockDiskFreeSpace = 10,
		minimumFreeSpace = 10,
	}

	local expectedPurgeLists = {
		{
			original = {
				true
			}
		}
	}

	local spaceToReclaim = 5
	local expectedSpaceToReclaim = 5
	
	testTryToClaimSpace( rxTest, overrides, spaceToReclaim, expectedSpaceToReclaim, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestTryToClaimSpace_NoOp( rxTest )

	-- No need to purge anything!

	local overrides = {
		debugMockLrSize = 10,
		targetCacheSize = 10000,
		debugMockDiskFreeSpace = 1000,
		minimumFreeSpace = 500,
	}

	local expectedPurgeLists = {}

	local spaceToReclaim = 150
	local expectedSpaceToReclaim = 150
	
	testTryToClaimSpace( rxTest, overrides, spaceToReclaim, expectedSpaceToReclaim, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestTryToClaimSpace_Fail( rxTest )

	-- Attempt to claim more space than can be reclaimed.

	local overrides = {
		debugMockLrSize = 1000,
		targetCacheSize = 1000,
		debugMockDiskFreeSpace = 500,
		minimumFreeSpace = 500,
	}

	local expectedPurgeLists = {
		{
			original = {
				true,
				binaryType = "original",
			},
			proxy = {
				{
					"2560",
				},
				binaryType = "proxy",
			},
			xmp_develop = {
				true,
				binaryType = "xmp_develop",
			}
		}
	}
	
	if WFPlatformInfo.isMobile() then
	
		expectedPurgeLists = {
			{
				fullsize = {
					true,
					binaryType = "fullsize",
				},
				original = {
					true,
					binaryType = "original",
				},
				preview = {
					true,
					binaryType = "preview",
				},
				proxy = {
					{
						"2560",
					},
					binaryType = "proxy",
				},
				xmp_develop = {
					true,
					binaryType = "xmp_develop",
				}
			}
		}
	
	end
	
	local spaceToReclaim = 150000
	
	local expectedSpaceToReclaim
	
	-- This is the total size of all purgeable binaries for the document
	-- used in this test case.
	
	if WFPlatformInfo.isDesktop then
		expectedSpaceToReclaim = 1732
			-- xmp=1199 + xmp_develop=446 + original=45 + proxy=42 = 1732
	else
		expectedSpaceToReclaim = 622
			-- xmp_develop=446 + fullsize=45 + original=45 + preview=44 + proxy=42 = 622
	end	
	
	testTryToClaimSpace( rxTest, overrides, spaceToReclaim, expectedSpaceToReclaim, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestTryToClaimSpace_Fail_Force( rxTest )

	-- Attempt to claim more space than can be reclaimed.

	local overrides = {
		debugMockLrSize = 1000,
		targetCacheSize = 1000,
		debugMockDiskFreeSpace = 500,
		minimumFreeSpace = 500,
		forcePurge = true
	}

	local expectedPurgeLists = {
		{
			original = {
				true
			},
			proxy = {
				{
					"2560"
				}
			},
			xmp_develop = {
				true,
			}
		}
	}
	
	if WFPlatformInfo.isMobile() then
	
		expectedPurgeLists = {
			{
				fullsize = {
					true,
				},
				original = {
					true,
				},
				preview = {
					true,
				},
				proxy = {
					{
						"2560",
					},
				},
				xmp_develop = {
					true,
				}
			}
		}
	
	end
	
	local spaceToReclaim = 150000
	
	local expectedSpaceToReclaim
	
	-- This is the total size of all purgeable binaries for the document
	-- used in this test case.
	
	if WFPlatformInfo.isDesktop then
		expectedSpaceToReclaim = 1732
			-- xmp=1199 + xmp_develop=446 + original=45 + proxy=42 = 1732
	else
		expectedSpaceToReclaim = 622
			-- xmp_develop=446 + fullsize=45 + original=45 + preview=44 + proxy=42 = 622
	end
	
	testTryToClaimSpace( rxTest, overrides, spaceToReclaim, expectedSpaceToReclaim, expectedPurgeLists )

end

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

function WFCatVolumeAgentTest.rxTestTryToClaimSpace_SimultaneousQuery( rxTest )

	local _, volumeAgent = setupVolumeAgent( rxTest )

	local availableSpace1 = volumeAgent:tryToClaimSpace( desc, 1 )
	local availableSpace2 = volumeAgent:tryToClaimSpace( desc, 1 )
	
	-- Two clients are asking to claim space at the same time.
	
	local availableSpace = require 'Rx'.Observable.zip( availableSpace1, availableSpace2 )
		:map( function( space1, space2 )
			return true
		end )
	
	rxTest:expectSingleValue( availableSpace, true )
	
end

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

function WFCatVolumeAgentTest.rxTestClearCache( rxTest )

	rxTest:setTimeLimit( 5 )

	-- The cache is not full, and we have plenty of free space.
	-- Still, emptying the cache should force us to remove everything!
	
	local overrides = {
		targetCacheSize = 1000000,
		debugMockDiskFreeSpace = 10000000,
		minimumFreeSpace = 10,
		debugReactivePurge = true
	}

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )
	
	-- Create 5 Assets
	
	createNAssetDocuments( rxTest, wfCatalog, 5 )

	-- We should purge all binaries within those 5 assets.

	local expectedPurgeLists = {
		{
			original = { true, true, true, true, true },
			proxy = {
				{ "2560" },
				{ "2560" },
				{ "2560" },
				{ "2560" },
				{ "2560" }
			},
			xmp_develop = { true, true, true, true, true }
		}
	}
	
	if WFPlatformInfo.isMobile() then
	
		expectedPurgeLists = {
			{
				fullsize = { true, true, true, true, true },
				original = { true, true, true, true, true },
				preview = { true, true, true, true, true },
				proxy = {
					{ "2560" },
					{ "2560" },
					{ "2560" },
					{ "2560" },
					{ "2560" }
				},
				xmp_develop = { true, true, true, true, true }
			}
		}
	
	end

	local purgeListStreamCount = 0
	
	local purgeListObserver = rxTest:observe( volumeAgent:_DEBUG_purgeListStream()
	
		:map( function( purgeList )
			
			if next( purgeList ) then
				purgeListStreamCount = purgeListStreamCount + 1
			end
			
			return purgeList
			
		end ) )

	rxTest:expectSingleValue( volumeAgent:clearCache(), true )

	-- Verify the Purge List Generated via Reactive Purge
	
	for binaryType, expectedPurgeList in ipairs( expectedPurgeLists ) do
	
		purgeListObserver:expectDataWithinTime( 2, expectedPurgeList )
		
		-- Verify that things were purged when appropriate.
		
		for id in pairs( expectedPurgeList ) do
			if binaryType == 'original' then
				rxTest:expectEqual( false, docHasOriginal( rxTest, wfCatalog, id ) )
			end
		end
		
	end
	
	rxTest:expectEqual( #expectedPurgeLists, purgeListStreamCount )

end

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

-- @mark Performance Tests

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

local function testPurgePerformance_testPerformance( rxTest, numberOfAssets, forcePurge )

	local oneTB = 1 * 1024 * 1024 * 1024 * 1024 -- 1 Terrabyte

	local overrides = {
		debugMockLrSize = oneTB,
		targetCacheSize = oneTB,
		debugReactivePurge = true,
		debugMockDiskFreeSpace = 1000,
		minimumFreeSpace = 500,
		timeLimit = 1000
	}

	local wfCatalog, volumeAgent = setupVolumeAgent( rxTest, overrides )

	createNAssetDocuments( rxTest, wfCatalog, numberOfAssets )

	rxTest:measurePerformance( function()

		local observer = rxTest:observe( volumeAgent:tryToClaimSpace( 'perf', oneTB, forcePurge ) )

		observer:expectDataWithinTime( 1000, ":ignore:" )

	end )

end

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

function WFCatVolumeAgentTest.rxTestPurgePerformance_1000_NoFileDeletion_testPerformance( rxTest )
 
	testPurgePerformance_testPerformance( rxTest, 1000 )

end

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

function WFCatVolumeAgentTest.DISABLED_rxTestPurgePerformance_10000_NoFileDeletion_testPerformance( rxTest )

	testPurgePerformance_testPerformance( rxTest, 10000 )

end

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

function WFCatVolumeAgentTest.DISABLED_rxTestPurgePerformance_100000_NoFileDeletion_testPerformance( rxTest )

	testPurgePerformance_testPerformance( rxTest, 100000 )

end

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

function WFCatVolumeAgentTest.rxTestPurgePerformance_1000_testPerformance( rxTest )

	testPurgePerformance_testPerformance( rxTest, 1000, true )

end

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

return WFRxTestCase.wrapSuite( 'WFCatVolumeAgentTest', WFCatVolumeAgentTest )
