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

PixelProviderLocalBinaryPathCheckTaskTest.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.

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

if not require 'WFPlatformInfo'.hasACR then return {} end

local Rx = require 'Rx'
local Observable = Rx.Observable

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

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFFileUtils = require 'WFFileUtils'
local KSData = require 'KSData'
local WFCrypto = require 'WFCrypto'
local WFTableMerge = require 'WFTableMerge'

	-- WF.oz_client.managed_catalog
local WFCatMockCatalog = require 'WFCatMockCatalog'

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

local PixelPathProviderScheduler = require 'PixelPathProviderScheduler'
local PixelProviderLocalBinaryPathCheckTask = require 'PixelProviderLocalBinaryPathCheckTask'
local PixelPathProviderRequestTypes = require 'PixelPathProviderRequestTypes'
local PixelPathProviderStateMachine = require 'PixelPathProviderStateMachine'

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

local PixelPathProviderRequestTypeNames = PixelPathProviderRequestTypes.names

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

local KSTableUtils_derefOpt = assert( KSTableUtils.derefOpt, 
										"KSTableUtils.derefOpt undefined" )

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

local function basicDocValue( binaryType, fileName, captureDate, sha256, filePath, addLocalOnlyAnnotation )

	local docValue = {

		type = 'asset',
		subtype = 'image',
		
		captureDate = captureDate,
		
		importSource = {
			importTimestamp = "2015-05-26T17:34:23-07:00",
			fileName = fileName,
			sha256 = sha256,
		},

		_serverProvidedMetadata = {
			revision_ids = {
				KSIdGenerator.uuidInLowercaseHex()
			},
			links = {
			
				[ '/rels/master' ] = {
					href = 'path/to/original.jpg',
				},
				[ '/rels/proxy_type/2560' ] = {
					href = 'path/to/proxy.jpg',
				}
			}
		}

	}
	
	if addLocalOnlyAnnotation then
	
		local localOnly = {}
	
		local relativePath = WFFileUtils.removeVolumeFromPath( filePath ) or filePath
		local volumeGuid = WFFileUtils.volumeForPath( filePath ) or "fakeVolumeGuid"
	
		if binaryType == 'original' then

			KSTableUtils.setNested( localOnly, relativePath, 'files', 'original', 'master', 'relativePath' )
			KSTableUtils.setNested( localOnly, volumeGuid, 'files', 'original', 'master', 'volumeGuid' )
			KSTableUtils.setNested( localOnly, sha256, 'files', 'original', 'master', 'sha256' )

		elseif binaryType == 'proxy' then

			KSTableUtils.setNested( localOnly, relativePath, 'files', 'proxy', '2560', 'relativePath' )
			KSTableUtils.setNested( localOnly, volumeGuid, 'files', 'proxy', '2560', 'volumeGuid' )
			KSTableUtils.setNested( localOnly, sha256, 'files', 'proxy', '2560', 'sha256' )

		elseif binaryType == 'xmp' then

			KSTableUtils.setNested( localOnly, relativePath, 'files', 'xmp', 'xmp', 'path' )

		end
		
		KSTableUtils.setNested( docValue, localOnly, '_localOnly' )
	
	end
	
	return docValue
	
end

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

local function createAsset( rxTest, wfCatalog, binaryType, optArgs )

	optArgs = optArgs or {}
	
	local addLocalOnlyAnnotation = optArgs.addLocalOnlyAnnotation
	local createLocalOriginal = optArgs.createLocalOriginal
	local createLocalProxy = optArgs.createLocalProxy
	local forceFakeSha256 = optArgs.forceFakeSha256
	local doesNotExistOnOz = optArgs.doesNotExistOnOz
	local willNotUploadOriginal = optArgs.willNotUploadOriginal
	local hasCopyId = optArgs.hasCopyId
	local isSpaceAsset = optArgs.isSpaceAsset
	local hasOriginalSourceLinkForCopy = optArgs.hasOriginalSourceLinkForCopy

	local docId = KSIdGenerator.uuidInLowercaseHex()
	
	local fileName = KSIdGenerator.uuidInLowercaseHex()
	
	local captureDate = "2015-05-26T17:34:23-07:00"
	
	local filePath = wfCatalog:fileSpec():pathForOriginal( fileName, captureDate )
	
	local sha256 = KSIdGenerator.uuidInLowercaseHex()

	if createLocalOriginal or createLocalProxy then
	
		local mockImage = KSData.make( fileName )
		KSData.saveToFile( mockImage, filePath )	
		sha256 = forceFakeSha256 and sha256 or WFCrypto.digestFile( filePath, 'SHA256' )

		rxTest:finally( function()
			WFFileUtils.delete( filePath )
		end )

	end
	
	local docValue = basicDocValue( binaryType, fileName, captureDate, sha256, filePath, addLocalOnlyAnnotation )
	docValue._id = docId
	
	if doesNotExistOnOz then
		docValue._serverProvidedMetadata = nil
	end

	if hasCopyId then
		KSTableUtils.setNested( docValue, KSIdGenerator.uuidInLowercaseHex(), 'copy', 'copy_id' )
	end

	if isSpaceAsset then
		KSTableUtils.setNested( docValue, KSIdGenerator.uuidInLowercaseHex(), '_spaceId' )
	end
	
	if hasOriginalSourceLinkForCopy then
		KSTableUtils.setNested( docValue, "/href/bogus/testing", '_localOnly', 'sourceOriginalLink' )
	end

	if willNotUploadOriginal then
		KSTableUtils.setNested( docValue, true, 'importSource', 'willNotUploadMaster' )
	end
	
	rxTest:waitFor( wfCatalog:docStore():setDocValue( docValue ) )
	
	local localId = rxTest:getFirstValue( wfCatalog:docStore():docValue( docId ):pluck( '_localId' ) )

	return localId, filePath, sha256

end

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

local function checkStandardOriginalAnnotations( rxTest, docStore, localId, shouldExist, expectedSha )

	rxTest:expectSingleValue(
		docStore:docValue( localId )
			:map( function( docValue )
			
				local localOnlyFiles = KSTableUtils_derefOpt( docValue, '_localOnly', 'files' )
				local volumeGuid = KSTableUtils_derefOpt( localOnlyFiles, 'original', 'master', 'volumeGuid' )
				local relativePath = KSTableUtils_derefOpt( localOnlyFiles, 'original', 'master', 'relativePath' )
				local sha256 = KSTableUtils_derefOpt( localOnlyFiles, 'original', 'master', 'sha256' )
			
				return {
					not not volumeGuid,
					not not relativePath,
					expectedSha and sha256 or ( not not sha256 ),
				}
				
			end ), { shouldExist, shouldExist, expectedSha or shouldExist } )

end

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

local function checkStandardProxyAnnotations( rxTest, docStore, localId, shouldExist )

	rxTest:expectSingleValue(
		docStore:docValue( localId )
			:map( function( docValue )
			
				local localOnlyFiles = KSTableUtils_derefOpt( docValue, '_localOnly', 'files' )
				local volumeGuid = KSTableUtils_derefOpt( localOnlyFiles, 'proxy', '2560', 'volumeGuid' )
				local relativePath = KSTableUtils_derefOpt( localOnlyFiles, 'proxy', '2560', 'relativePath' )

				return {
					not not volumeGuid,
					not not relativePath
				}
				
			end ), { shouldExist, shouldExist } )

end

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

local function checkForErrorAnnotation( rxTest, docStore, localId, shouldExist )

	rxTest:expectSingleValue(
		docStore:docValue( localId ):pluck( '_localOnly', 'localOriginalError' ), 
		shouldExist and 'InvalidLocalOriginal' or nil )

end

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

local function rxTestCheckExpectedPathForOriginal( rxTest )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local ozUserId = wfCatalog:ozUserId()
	local docStore = wfCatalog:docStore()
	local checkTask = PixelProviderLocalBinaryPathCheckTask
	local fileSpec = wfCatalog:fileSpec()
	
	local mockCacheChecker = {
	
		checkPath = function( _, path )
			return Observable.just( WFFileUtils.exists( path ) )
		end
		
	}
	
	-- No untracked local original
	
	local optArgs = {
		createLocalOriginal = false,
		forceFakeSha256 = false
	}
	
	local localId = createAsset( rxTest, wfCatalog, 'original', optArgs )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId, false )
	
	rxTest:expectSingleValue( checkTask.checkExpectedPathForOriginal( ozUserId, docStore, mockCacheChecker, fileSpec, localId ), false )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId, false )
	
	-- Untracked local original exists
	
	optArgs = {
		createLocalOriginal = true,
		forceFakeSha256 = false
	}
	
	local localId_2, filePath_2, sha256_2 = createAsset( rxTest, wfCatalog, 'original', optArgs )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId_2, false )
	
	rxTest:expectSingleValue( checkTask.checkExpectedPathForOriginal( ozUserId, docStore, mockCacheChecker, fileSpec, localId_2 ), filePath_2 )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId_2, true, sha256_2 )
	
	-- Untracked local original exists but has a different SHA
	
	optArgs = {
		createLocalOriginal = true,
		forceFakeSha256 = true
	}
	
	local localId_3 = createAsset( rxTest, wfCatalog, 'original', optArgs )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId_3, false )
	
	rxTest:expectSingleValue( checkTask.checkExpectedPathForOriginal( ozUserId, docStore, mockCacheChecker, fileSpec, localId_3 ), false )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId_3, false )

end

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

local function rxTestCheckForBinaryAndMaybeDeleteAnnotation_Original( rxTest )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local checkTask = PixelProviderLocalBinaryPathCheckTask
	
	local mockCacheChecker = {
	
		checkPath = function( _, path )
			return Observable.just( WFFileUtils.exists( path ) )
		end
		
	}
	
	-- Local Original Exists
	
	local optArgs = {
		createLocalOriginal = true,
		doesNotExistOnOz = false,
		willNotUploadOriginal = false,
		addLocalOnlyAnnotation = true,
	}
	
	local localId, filePath = createAsset( rxTest, wfCatalog, 'original', optArgs )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId, true )
	
	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath, localId, 'original' ), filePath )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId, true )
	
	checkForErrorAnnotation( rxTest, docStore, localId, false )
	
	-- Local Original Does Not Exist, Exists on Oz
	
	optArgs = {
		createLocalOriginal = false,
		doesNotExistOnOz = false,
		willNotUploadOriginal = false,
		addLocalOnlyAnnotation = true,
	}
	
	local localId_2, filePath_2 = createAsset( rxTest, wfCatalog, 'original', optArgs )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId_2, true )
	
	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_2, localId_2, 'original' ), false )
	
	checkStandardOriginalAnnotations( rxTest, docStore, localId_2, false )
	
	checkForErrorAnnotation( rxTest, docStore, localId_2, false )

	-- Local Original Does Not Exist, Does Not Exist on Oz, Cannot Copy

	optArgs = {
		createLocalOriginal = false,
		doesNotExistOnOz = true,
		willNotUploadOriginal = false,
		addLocalOnlyAnnotation = true,
	}

	local localId_3, filePath_3 = createAsset( rxTest, wfCatalog, 'original', optArgs )

	checkStandardOriginalAnnotations( rxTest, docStore, localId_3, true )

	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_3, localId_3, 'original' ), false )

	-- Annotation should Stll Exist

	checkStandardOriginalAnnotations( rxTest, docStore, localId_3, true )

	-- An Error Annotation Should be added to the document:

	checkForErrorAnnotation( rxTest, docStore, localId_3, true )

	-- Local Original Does Not Exist, Does Not Exist on Oz, Will Not Upload Local Original

	optArgs = {
		createLocalOriginal = false,
		doesNotExistOnOz = true,
		willNotUploadOriginal = true,
		addLocalOnlyAnnotation = true,
	}

	local localId_4, filePath_4 = createAsset( rxTest, wfCatalog, 'original', optArgs )

	checkStandardOriginalAnnotations( rxTest, docStore, localId_4, true )

	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_4, localId_4, 'original' ), false )

	checkStandardOriginalAnnotations( rxTest, docStore, localId_4, true )

	checkForErrorAnnotation( rxTest, docStore, localId_4, false )

	-- Local Original Does Not Exist, Does Not Exist on Oz, Can Copy w/ copyId

	optArgs = {
		createLocalOriginal = false,
		doesNotExistOnOz = true,
		willNotUploadOriginal = false,
		addLocalOnlyAnnotation = true,
		hasCopyId = true
	}

	local localId_5, filePath_5 = createAsset( rxTest, wfCatalog, 'original', optArgs )

	checkStandardOriginalAnnotations( rxTest, docStore, localId_5, true )

	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_5, localId_5, 'original' ), false )

	-- Annotation should be removed

	checkStandardOriginalAnnotations( rxTest, docStore, localId_5, false )

	-- An Error Annotation should not be added to the document:

	checkForErrorAnnotation( rxTest, docStore, localId_5, false )

	-- Local Original Does Not Exist, Does Not Exist on Oz, Can Copy w/ Space Asset

	optArgs = {
		createLocalOriginal = false,
		doesNotExistOnOz = true,
		willNotUploadOriginal = false,
		addLocalOnlyAnnotation = true,
		isSpaceAsset = true,
		hasOriginalSourceLinkForCopy = true
	}

	local localId_6, filePath_6 = createAsset( rxTest, wfCatalog, 'original', optArgs )

	checkStandardOriginalAnnotations( rxTest, docStore, localId_6, true )

	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_6, localId_6, 'original' ), false )

	-- Annotation should be removed

	checkStandardOriginalAnnotations( rxTest, docStore, localId_6, false )

	-- An Error Annotation should not be added to the document:

	checkForErrorAnnotation( rxTest, docStore, localId_6, false )

end

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

local function rxTestCheckForBinaryAndMaybeDeleteAnnotation_Proxy( rxTest )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local docStore = wfCatalog:docStore()
	local checkTask = PixelProviderLocalBinaryPathCheckTask
	
	local mockCacheChecker = {
	
		checkPath = function( _, path )
			return Observable.just( WFFileUtils.exists( path ) )
		end
		
	}
	
	-- Local Proxy Exists
	
	local optArgs = {
		createLocalProxy = true,
		doesNotExistOnOz = false,
		addLocalOnlyAnnotation = true,
	}
	
	local localId, filePath = createAsset( rxTest, wfCatalog, 'proxy', optArgs )
	
	checkStandardProxyAnnotations( rxTest, docStore, localId, true )
	
	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath, localId, 'proxy' ), filePath )
	
	checkStandardProxyAnnotations( rxTest, docStore, localId, true )
	
	-- Local Proxy Does Not Exist, Exists on Oz
	
	optArgs = {
		createLocalProxy = false,
		doesNotExistOnOz = false,
		addLocalOnlyAnnotation = true,
	}
	
	local localId_2, filePath_2 = createAsset( rxTest, wfCatalog, 'proxy', optArgs )
	
	checkStandardProxyAnnotations( rxTest, docStore, localId_2, true )
	
	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_2, localId_2, 'proxy' ), false )
	
	checkStandardProxyAnnotations( rxTest, docStore, localId_2, false )
	
	-- Local Proxy Does Not Exist, Does Not Exist on Oz
	
	optArgs = {
		createLocalProxy = false,
		doesNotExistOnOz = true,
		addLocalOnlyAnnotation = true,
	}
	
	local localId_3, filePath_3 = createAsset( rxTest, wfCatalog, 'proxy', optArgs )
	
	checkStandardProxyAnnotations( rxTest, docStore, localId_3, true )
	
	rxTest:expectSingleValue( checkTask.checkForBinaryAndMaybeDeleteAnnotation( docStore, mockCacheChecker, filePath_3, localId_3, 'proxy' ), false )
	
	checkStandardProxyAnnotations( rxTest, docStore, localId_3, true )

end

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

local function makeSchedulerForTest( rxTest )

	local catalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local scheduler = PixelPathProviderScheduler( catalog )

	rxTest:finally( function() scheduler:close() end )

	return scheduler, catalog

end

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

local function makeMockDocstoreForLocalBinaryCheckTask() -- rxTest

	local docValuesPerId = {}

	local mockDocStore = {

		applyOverlay = 
			function( _, overlay ) 

				local assetId = 
					assert( 
						KSTableUtils.derefOpt( overlay, '_localId' ) or
							KSTableUtils.derefOpt( overlay, '_id' ), 
						"missing _id" )

				overlay._id = nil

				overlay._localId = nil

				docValuesPerId[ assetId ] = 
					WFTableMerge.applyOverlay( 
						docValuesPerId[ assetId ] or {}, overlay )

				return Rx.Observable.just( assetId )

			end,

		docValue =
			function( _, assetId )

				local docValue = docValuesPerId[ assetId ]

				if not docValue then

					return Rx.Observable.throw( string.format( "no docValue for %s", assetId ) )

				end

				return Rx.Observable.just( docValue )

			end,
	}

	return mockDocStore, docValuesPerId

end

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

local _uuids = {}

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

local function uuid( i )

	_uuids[ i ] = _uuids[ i ] or KSIdGenerator.uuidInLowercaseHex()

	return _uuids[ i ]

end

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

local function localAssetId( i )

	return i

end

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

local function pixStreamId( i )

	return string.format( "pixStreamId%d", i )

end

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

local function updateBaseRenderInfoCacheSha256( baseRenderInfoCache, assetId, newSha256 )

	local baseRenderInfo = baseRenderInfoCache:popCacheItem( assetId ) or {}

	baseRenderInfo.localOriginalSha256 = newSha256

	baseRenderInfoCache
		:pushCacheItem( assetId, baseRenderInfo )

end

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

local function rxTestOriginalSha256Checks( rxTest )

	rxTest:setTimeLimit( 5 )
	
	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.runScheduledRequests( scheduler )

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderLocalBinaryPathCheckTask" )
	
	local mockDocStore, docValuesPerId = makeMockDocstoreForLocalBinaryCheckTask()

	SchedulerTestAPI.setTaskState( 
		scheduler, "PixelProviderLocalBinaryPathCheckTask",
		"docStore", mockDocStore )

	local expectedLocalFileExistsState = {

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal ] =
				PixelPathProviderStateMachine.SendDataStates.HaveLocalNegativePath,

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckAnyLocalNegativeForRender ] =
				PixelPathProviderStateMachine.RenderStates.NeedsRender,

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForAnyLocalNegative ] =
				PixelPathProviderStateMachine.SendDataStates.HaveLocalNegativePath,

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath ] =
				PixelPathProviderStateMachine.SendDataStates.HaveOriginalForBestPossibleLocalNegativePath,

		}

	local expectedNeedsDownloadState = {

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal ] =
				PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckAnyLocalNegativeForRender ] =
				PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForAnyLocalNegative ] =
				PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,

			[ PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath ] =
				PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath,

		}

	local needsCheckForLocalOriginalStates = KSTableUtils.extractKeys( expectedLocalFileExistsState )


	for _, needCheckForOriginalState in ipairs( needsCheckForLocalOriginalStates ) do

		rxTest:log( "add fake doc value for asset 1" )

		docValuesPerId[ 1 ] = 
			{ 
				_id = uuid( 1 ),
				_serverProvidedMetadata = {
					links = {
						[ "/rels/proxy_type/2560" ] =  {
							href = "yass"
						}
					}
				}
			}

		scheduler:addRequest( pixStreamId( 1 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

		for _, existingLocalSha256Value in ipairs( { "invalid", "stable", "volatile", "missing" } ) do

			-- install a cache checker that possibly updates the baseRenderInfoCache when the file is checked
			SchedulerTestAPI.setTaskState( 
				scheduler, "PixelProviderLocalBinaryPathCheckTask",
				"fileChecker", { 
					checkPath = 
						function( _, path ) 

							if string.find( path, "2560" ) then

								-- this is a proxy path and we want to act like it doesn't exist
								return Rx.Observable.just( false )

							end

							if string.find( path, "volatile" ) then

								updateBaseRenderInfoCacheSha256( 
									scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
									localAssetId( 1 ),
									"differentSha256" )

							end

							return Rx.Observable.just( "file" )

						end } )

			for _, hasValidDownloadOzLink in ipairs( { true, false } ) do

				rxTest:logf( "========>Running test with start state %s, existingLocalSha256Value %s, hasValidDownloadOzLink %s", 
								needCheckForOriginalState, existingLocalSha256Value, hasValidDownloadOzLink )

				-- add the needed baseRenderInfoCache data
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache
					:pushCacheItem( 
						localAssetId( 1 ), 
						{ 
							originalPath = string.format( "fakePathWithSha256_%s", existingLocalSha256Value ),
							localOriginalSha256 = existingLocalSha256Value ~= "missing" and existingLocalSha256Value or nil,
							proxies = { { size="2560", href="notAProxy" } },
							ozValidOriginalLink = hasValidDownloadOzLink and "sure" or nil,
						} )

				SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 1 ), 
											needCheckForOriginalState )

				SchedulerTestAPI.runScheduledRequests( scheduler )

				local expectedEndStates = 
						( not hasValidDownloadOzLink or
							existingLocalSha256Value == "stable" or 
							existingLocalSha256Value == "missing" ) and
						expectedLocalFileExistsState or
						expectedNeedsDownloadState

				local expectedEndState = expectedEndStates[ needCheckForOriginalState ]

				local actualEndState = scheduler:_maybeGetCurrState( pixStreamId( 1 ), localAssetId( 1 ) )

				rxTest:logf( "Checking the %s test end state is %s which is a %s state", 
								needCheckForOriginalState, expectedEndState,
								expectedEndStates == expectedNeedsDownloadState and
									"needs download state" or "have local file" )

				rxTest:expectEqual( expectedEndState, actualEndState )

			end
		end

		scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache:destroyAll()
		
	end

end

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

local function rxTestResolvedLocalOnlyDocValueAfterExistingFileCheck( rxTest )

	rxTest:setTimeLimit( 5 )
	
	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.runScheduledRequests( scheduler )

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderLocalBinaryPathCheckTask" )
	
	local mockDocStore, docValuesPerId = makeMockDocstoreForLocalBinaryCheckTask()

	SchedulerTestAPI.setTaskState( 
		scheduler, "PixelProviderLocalBinaryPathCheckTask",
		"docStore", mockDocStore )

	-- install a cache checker that says all file checks exist
	SchedulerTestAPI.setTaskState( 
		scheduler, "PixelProviderLocalBinaryPathCheckTask",
		"fileChecker", { 
			checkPath = 
				function()

					return Rx.Observable.just( "file" )

				end } )

	for _, ozBinaryType in ipairs( { 'original', 'proxy', 'xmp' } ) do

		local ozBinarySubtype =
				( ozBinaryType == "original" and 'master' ) or
				( ozBinaryType == "proxy" and '2560' ) or
				( ozBinaryType == "xmp" and 'xmp' ) or
				error( string.format( "can't compute ozBinarySubtype for %s", ozBinaryType ) )

		local baseRenderInfoPathField =
				( ozBinaryType == 'original' and "originalPath" ) or
				( ozBinaryType == 'proxy' and "proxyPath" ) or
				( ozBinaryType == 'xmp' and "xmpSidecarPath" ) 

		local initialFileCheckStates = 
				( ozBinaryType == 'original' and 
					{
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal,
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath
					} ) or

				( ozBinaryType == 'proxy' and 
					{
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalProxy,
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckLocalProxyForRender,
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckProxyForBestPossibleLocalNegativePath
					} ) or

				( ozBinaryType == 'xmp' and 
					{

						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalXmpSidecar,

					} ) or
				{}

		scheduler:addRequest( pixStreamId( 1 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

		for _, initialFileCheckState in ipairs( initialFileCheckStates ) do

			for _, docValueLocalOnlyPathExists in ipairs( { true, false } ) do

			local willCheckSha256 =
					not docValueLocalOnlyPathExists and
						ozBinaryType == "original"

			local sha256OverrideBehaviors =
					willCheckSha256 and
							{ "matching", "mismatched", "error triggering" } or
							{ "ignored" }

			for _, sha256OverrideBehavior in ipairs( sha256OverrideBehaviors ) do

				rxTest:logf( "========>Running test with start state %s and docValueLocalOnlyPathExists %s",
							initialFileCheckState, docValueLocalOnlyPathExists )

				local fileName = KSIdGenerator.uuidInLowercaseHex()
				local captureDate = "2015-05-26T17:34:23-07:00"
				local filePath = fileName
				local sha256 = KSIdGenerator.uuidInLowercaseHex()

				if sha256OverrideBehavior ~= "ignored" then

					-- original paths do a sha check which we must emulate
					PixelProviderLocalBinaryPathCheckTask.privateUnitTestExports.setSha256OverrideFunc(
						function() 

							if sha256OverrideBehavior == "error triggering" then

								return Rx.Observable.throw( "emulated sha256 error" )

							end

							if sha256OverrideBehavior == "mismatched" then

								return Rx.Observable.just( KSIdGenerator.uuidInLowercaseHex() ) 

							end

							return Rx.Observable.just( sha256 ) 

						end )

				end

				local docValue = 
						basicDocValue( 
							ozBinaryType, fileName, captureDate, sha256,
							filePath, docValueLocalOnlyPathExists )

				docValue._id =  uuid( 1 )

				docValuesPerId[ 1 ] = docValue

				local baseRenderInfo = {

					proxies = { { size="2560", href="notAProxy" } },

					ozValidOriginalLink = "sure",

					localOriginalSha256 = "yes",

				}

				if docValueLocalOnlyPathExists then

					baseRenderInfo[ baseRenderInfoPathField ] = baseRenderInfoPathField

					if ozBinarySubtype == "proxy" then

						baseRenderInfo.proxyPathOzBinarySubtype = '2560'

					end

				end

				-- add the needed baseRenderInfoCache data
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache
					:pushCacheItem( localAssetId( 1 ), baseRenderInfo )

				SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 1 ), 
											initialFileCheckState )

				SchedulerTestAPI.runScheduledRequests( scheduler )
				SchedulerTestAPI.runScheduledRequests( scheduler )
				SchedulerTestAPI.runScheduledRequests( scheduler )

				PixelProviderLocalBinaryPathCheckTask.privateUnitTestExports.setSha256OverrideFunc(
					nil )

				local expectedEndState = 

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal and
						not willCheckSha256 and
							PixelPathProviderStateMachine.SendDataStates.HaveLocalNegativePath ) or
					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal and
						willCheckSha256 and
						(
							( sha256OverrideBehavior == "matching" and
								PixelPathProviderStateMachine.SendDataStates.HaveLocalNegativePath ) or
							( sha256OverrideBehavior == "mismatched" and
								PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload ) or
							PixelPathProviderStateMachine.SendDataStates.SendNoDownloadableOriginal 
						)
					) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath and
						not willCheckSha256 and
							PixelPathProviderStateMachine.SendDataStates.HaveOriginalForBestPossibleLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath and
						willCheckSha256 and
						(
							( sha256OverrideBehavior == "matching" and
								PixelPathProviderStateMachine.SendDataStates.HaveOriginalForBestPossibleLocalNegativePath ) or
							( sha256OverrideBehavior == "mismatched" and
								PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath ) or
							PixelPathProviderStateMachine.EndStates.WaitForDownloadableOriginalForBestPossibleLocalNegativePath
						) 
					) or
						

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalProxy and
							PixelPathProviderStateMachine.SendDataStates.HaveLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckLocalProxyForRender and
						PixelPathProviderStateMachine.RenderStates.NeedsRender ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckProxyForBestPossibleLocalNegativePath and
							PixelPathProviderStateMachine.SendDataStates.HaveProxyForBestPossibleLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalXmpSidecar and
							PixelPathProviderStateMachine.SendDataStates.HaveLocalXmpSidecarPath ) or

					"unknown expected state!"

				local actualEndState = scheduler:_maybeGetCurrState( pixStreamId( 1 ), localAssetId( 1 ) )

				rxTest:logf( "Checking the %s test with %s sha256 has end state %s ", 
								initialFileCheckState, sha256OverrideBehavior, expectedEndState )

				rxTest:expectEqual( expectedEndState, actualEndState )

				local docValueLocalOnlyPathInfo =
					KSTableUtils.derefOpt( docValuesPerId[ 1 ], "_localOnly", "files", ozBinaryType, ozBinarySubtype )

				local expectDocValueToHaveData =
						( ozBinaryType == "xmp" and
							docValueLocalOnlyPathExists ) or
						( ozBinaryType == "original" and
							( docValueLocalOnlyPathExists or
								sha256OverrideBehavior == "matching" ) ) or
						ozBinaryType == "proxy"

				local expectedLocalOnlyPathInfoType =
							expectDocValueToHaveData and "table" or "nil"

				rxTest:logf( "checking docValue %s local binary path table expecting %s actual is %s", ozBinaryType, expectedLocalOnlyPathInfoType, type( docValueLocalOnlyPathInfo ) )
 
				rxTest:expectEqual( 
					expectedLocalOnlyPathInfoType,
					type( docValueLocalOnlyPathInfo ) )


				rxTest:logf( "checking docValue has local binary path table with path %s for %s %s", expectDocValueToHaveData, ozBinaryType, ozBinarySubtype )

				rxTest:expectEqual( 
						KSTableUtils.derefOpt( docValueLocalOnlyPathInfo, "path" ) ~= nil or
							KSTableUtils.derefOpt( docValueLocalOnlyPathInfo, "relativePath" ) ~= nil,
						expectDocValueToHaveData )

			end
			end

		end

		scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache:destroyAll()
		
	end

end

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

local function addDocValueLinks( docValue, ozBinaryType, linksAreValid )

	local uriName = 
			( ozBinaryType == "original" and "/rels/master" ) or
			( ozBinaryType == "proxy" and "/rels/proxy_type/2560" ) or
			( ozBinaryType == "xmp" and "/rels/xmp" )

	if not uriName then

		return docValue

	end

	KSTableUtils.setNested( 
		docValue, "fakeHref", "_serverProvidedMetadata", "links", uriName, "href"  )

	if linksAreValid then

		return docValue

	end

	KSTableUtils.setNested( 
		docValue, true, "_serverProvidedMetadata", "links", uriName, "invalid"  )

	return docValue

end

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

local function rxTestNoLocalOnlyDocValueAfterMissingFileCheck( rxTest )

	rxTest:setTimeLimit( 5 )
	
	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.runScheduledRequests( scheduler )

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderLocalBinaryPathCheckTask" )
	
	local mockDocStore, docValuesPerId = makeMockDocstoreForLocalBinaryCheckTask()

	SchedulerTestAPI.setTaskState( 
		scheduler, "PixelProviderLocalBinaryPathCheckTask",
		"docStore", mockDocStore )

	-- install a cache checker that says all file checks exist
	SchedulerTestAPI.setTaskState( 
		scheduler, "PixelProviderLocalBinaryPathCheckTask",
		"fileChecker", { 
			checkPath = 
				function()

					return Rx.Observable.just( false )

				end } )

	for _, ozBinaryType in ipairs( { 'original', 'proxy', 'xmp' } ) do

		local ozBinarySubtype =
				( ozBinaryType == "original" and 'master' ) or
				( ozBinaryType == "proxy" and '2560' ) or
				( ozBinaryType == "xmp" and 'xmp' ) or
				error( string.format( "can't compute ozBinarySubtype for %s", ozBinaryType ) )

		local baseRenderInfoPathField =
				( ozBinaryType == 'original' and "originalPath" ) or
				( ozBinaryType == 'proxy' and "proxyPath" ) or
				( ozBinaryType == 'xmp' and "xmpSidecarPath" ) 

		local initialFileCheckStates = 
				( ozBinaryType == 'original' and 
					{
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal,
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath
					} ) or

				( ozBinaryType == 'proxy' and 
					{
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalProxy,
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckLocalProxyForRender,
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckProxyForBestPossibleLocalNegativePath
					} ) or

				( ozBinaryType == 'xmp' and 
					{

						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalXmpSidecar,

					} ) or
				{}

		scheduler:addRequest( pixStreamId( 1 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

		for _, initialFileCheckState in ipairs( initialFileCheckStates ) do

			for _, docValueLocalOnlyPathExists in ipairs( { true, false } ) do

			for _, ozLinkStatus in ipairs( { false, 'valid', 'invalid', } ) do

				rxTest:logf( "========>Running test with start state %s and docValueLocalOnlyPathExists %s ozLinkStatus %s", 
								initialFileCheckState,
								docValueLocalOnlyPathExists, ozLinkStatus )

				local fileName = KSIdGenerator.uuidInLowercaseHex()
				local captureDate = "2015-05-26T17:34:23-07:00"
				local filePath = fileName
				local sha256 = KSIdGenerator.uuidInLowercaseHex()

				local docValue = 
						basicDocValue( 
							ozBinaryType, fileName, captureDate, sha256,
							filePath, docValueLocalOnlyPathExists )

				docValue._id =  uuid( 1 )

				docValuesPerId[ 1 ] = docValue

				if ozLinkStatus then

					docValue = 
						addDocValueLinks( 
							docValue, ozBinaryType, ozLinkStatus == "valid" )

				end

				local baseRenderInfo = {

					proxies = 
						ozLinkStatus and 
							{ 
								{ 
									size="2560", 
									invalid = ozLinkStatus == "invalid" and true or nil,
									href = "notAProxy" 
								} 
							} or nil,

					ozValidOriginalLink = ozLinkStatus == "valid" and "sure" or nil,
					ozInvalidOriginalLink = ozLinkStatus == "invalid" and "sure" or nil,

					localOriginalSha256 = "yes",

					ozXmpLink = ozLinkStatus == "valid" and "sure" or nil,

				}

				if docValueLocalOnlyPathExists then

					baseRenderInfo[ baseRenderInfoPathField ] = baseRenderInfoPathField

					if ozBinarySubtype == "proxy" then

						baseRenderInfo.proxyPathOzBinarySubtype = '2560'

					end

				end

				-- add the needed baseRenderInfoCache data
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache
					:pushCacheItem( localAssetId( 1 ), baseRenderInfo )

				SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 1 ), 
											initialFileCheckState )

				SchedulerTestAPI.runScheduledRequests( scheduler )
				SchedulerTestAPI.runScheduledRequests( scheduler )
				SchedulerTestAPI.runScheduledRequests( scheduler )

				PixelProviderLocalBinaryPathCheckTask.privateUnitTestExports.setSha256OverrideFunc(
					nil )

				local expectedEndState = 

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal and
							ozLinkStatus and
							PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalOriginal and
							not ozLinkStatus and
							PixelPathProviderStateMachine.SendDataStates.SendNoDownloadableOriginal ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath and
							ozLinkStatus and
							PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckOriginalForBestPossibleLocalNegativePath and
							not ozLinkStatus and
							PixelPathProviderStateMachine.EndStates.WaitForDownloadableOriginalForBestPossibleLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalProxy and
							ozLinkStatus and
							PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalProxy and
							not ozLinkStatus and
							PixelPathProviderStateMachine.SendDataStates.SendNoDownloadableOriginal ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckLocalProxyForRender and
							ozLinkStatus and
							PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForRender ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckLocalProxyForRender and
							not ozLinkStatus and
							PixelPathProviderStateMachine.SendDataStates.SendNoDownloadableBinaryForRender ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckProxyForBestPossibleLocalNegativePath and
							ozLinkStatus and
							PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForBestPossibleLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckProxyForBestPossibleLocalNegativePath and
							not ozLinkStatus and
							PixelPathProviderStateMachine.EndStates.WaitForDownloadableOriginalForBestPossibleLocalNegativePath ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalXmpSidecar and
							ozLinkStatus == "valid" and 
							PixelPathProviderStateMachine.DownloadStates.NeedXmpSidecarDownload ) or

					( initialFileCheckState ==
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckForLocalXmpSidecar and
							ozLinkStatus ~= "valid" and 
							PixelPathProviderStateMachine.SendDataStates.SendNoDownloadableXmpSidecar ) or

					"unknown expected state!"

				local actualEndState = scheduler:_maybeGetCurrState( pixStreamId( 1 ), localAssetId( 1 ) )


				rxTest:logf( "Checking start state %s leads to expected state %s ", 
								initialFileCheckState, expectedEndState )

				rxTest:expectEqual( expectedEndState, actualEndState )

				local docValueLocalOnlyPathInfo =
					KSTableUtils.derefOpt( docValuesPerId[ 1 ], "_localOnly", "files", ozBinaryType, ozBinarySubtype )

				-- Since the local file doesn't exists on disk, we expect the
				-- 	docValue._localOnly.files[ ozBinaryType ][ ozBinarySubtype ] 
				-- to be empty save for a couple of special cases.
				local expectedDocValueLocalOnlyInfoType =
					( docValueLocalOnlyPathExists and 
						( 
							( ozBinaryType == "xmp" and "table" ) or 
							( ozBinaryType == "original" and ozLinkStatus == "invalid" and "table" ) 
						)
					) or
					"nil"

				rxTest:logf( "checking type( docValue._localOnly.%s.%s ) == %s", 
								ozBinaryType, ozBinarySubtype, expectedDocValueLocalOnlyInfoType )
 
				rxTest:expectEqual( 
					expectedDocValueLocalOnlyInfoType,
					type( docValueLocalOnlyPathInfo ) )

				local localOriginalError =
					KSTableUtils.derefOpt( docValuesPerId[ 1 ], "_localOnly", "localOriginalError" )

				local expectedLocalOriginalError = 
						ozBinaryType == "original" and 
						ozLinkStatus == "invalid" and 
						docValueLocalOnlyPathExists and 
							"InvalidLocalOriginal" or nil

				rxTest:logf( "checking docValue has %s localOriginalError for %s %s", 
								expectedLocalOriginalError, ozBinaryType, ozBinarySubtype )

				rxTest:expectEqual( 
						expectedLocalOriginalError,
						localOriginalError )

			end
			end

		end

		scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache:destroyAll()
		
	end

end

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

return WFRxTestCase.wrapSuite( 'PixelProviderLocalBinaryPathCheckTaskTest', {

	rxTestCheckExpectedPathForOriginal = rxTestCheckExpectedPathForOriginal,

	rxTestCheckForBinaryAndMaybeDeleteAnnotation_Original = rxTestCheckForBinaryAndMaybeDeleteAnnotation_Original,

	rxTestCheckForBinaryAndMaybeDeleteAnnotation_Proxy = rxTestCheckForBinaryAndMaybeDeleteAnnotation_Proxy,

	rxTestOriginalSha256Checks = rxTestOriginalSha256Checks,

	rxTestResolvedLocalOnlyDocValueAfterExistingFileCheck = rxTestResolvedLocalOnlyDocValueAfterExistingFileCheck,

	rxTestNoLocalOnlyDocValueAfterMissingFileCheck = rxTestNoLocalOnlyDocValueAfterMissingFileCheck,

} )
