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

PixelProviderDownloadTaskTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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 ACR is not supported, disable test before we attempt to
	-- require WFAcr. (That will fail.)
if not require 'WFPlatformInfo'.hasACR then return {} end

	-- wf.core
local KSData = require 'KSData'
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFFileUtils = require 'WFFileUtils'
local WFFiniteStateMachine = require 'WFFiniteStateMachine'
local WFFunctionUtils = require 'WFFunctionUtils'
local WFRxTestCase = require 'WFRxTestCase'

	-- Rx
local Rx = require 'Rx'

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

local WFCatRenderSize = require 'WFCatRenderSize'
local PixelProviderSharedBlobCache = require 'PixelProviderSharedBlobCache'
local PixelPathProviderScheduler = require 'PixelPathProviderScheduler'
local PixelPathProviderRequestTypes = require 'PixelPathProviderRequestTypes'
local PixelPathProviderStateMachine = require 'PixelPathProviderStateMachine'
local PixelProviderDownloadTask = require 'PixelProviderDownloadTask'

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

local PixelPathProviderRequestTypeNames = PixelPathProviderRequestTypes.names

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

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 digest( i )

	return string.format( "digest%s", i )

end

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

--[[
local function renderSize( i )

	return 10 + i

end
--]]

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

local function localAssetId( i )

	return i

end

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

local function makeOzAssetId( i )

	return string.format( "ozAssetIdFor%s", localAssetId( i ) )

end

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

local function addBaseRenderInfoCacheEntry( 
				baseRenderInfoCache, needsDownloadState, i, renderSize,
				maybeCroppedLongSide )

	local maybeDownloadFullRenditionURI

	if renderSize == "fullsize" then

		maybeDownloadFullRenditionURI = 'fakeURI'

	end

	local renditions, invalid_renditions

	if renderSize and renderSize ~= "fullsize" then

		if needsDownloadState ~= PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload then

			renditions = { { size = renderSize, href = "fakeURI" } } 

		else
			
			invalid_renditions = { { size = renderSize, href = "fakeURI" } } 

		end

	end

	baseRenderInfoCache
		:pushCacheItem( 
			localAssetId( i ), 
			{ 
				ozAssetId = makeOzAssetId( i ), 
				changeCounter = i, 
				revId = digest( i ),
				digest = digest( i ), 
				proxies = { { size="2560", href="notAProxy" } },
				ozOriginalLink = "yeahNope",
				ozXmpDevelopLink = 'uh_huh_NOPE',
				ozXmpLink = 'justNope',
				downloadFullRenditionURI = maybeDownloadFullRenditionURI,
				croppedLongSide = maybeCroppedLongSide,
				renditions = renditions,
				invalid_renditions = invalid_renditions,
			} )

end

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

local function pixStreamId( i, maybePrefix )

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

end

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

local function justReturn( ... )

	return ...

end

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

local function getNFromTestInfoStream( rxTest, testStream, n, filterFunc, 
										maybeTransformFunc )

	local transformTestData = maybeTransformFunc or justReturn

	local i

	return testStream
			:where( filterFunc )
			:tap(
				function( ... )

					i = ( i or 0 ) + 1

					local nArgs = select( "#", ... )

					local argsFmtStr = 
							nArgs == 0 and 
								"" or 
								"%s" .. string.rep( ", %s", nArgs - 1 )

					rxTest:logf( "getNFromTestInfoStream ( %s of %s ): " .. argsFmtStr,
									i, n, ... )

				end )
			:map( transformTestData )
			:take( n )
			:toArray()

end

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

local function makeTuple( ... )

	return { ... }

end

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

local function stripTestMessageAndMakeTuple( _, ... )

	return makeTuple( ... )

end

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

local function sortArray( arr, maybeSortFn )

	local sorted = KSTableUtils.deepCopy( arr )

	table.sort( sorted, maybeSortFn )

	return sorted

end

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

local function compareArrays( a1, a2 )

	if #a1 ~= #a2 then

		return #a1 > #a2

	end

	for i = 1, math.min( #a1, #a2 ) do

		if a1[ i ] ~= a2[ i ] then

			return a1[ i ] > a2 [i ]

		end

	end

	return false

end

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

local function sortTupleArray( tupleArray )

	return sortArray( tupleArray, compareArrays )

end

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

local function isTestMessage( ... )

	local wantedTestMessages = KSTableUtils.mapArrayToSet( { ... } )

	return function( testMsg )

		return wantedTestMessages[ testMsg ]

	end

end

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

local function makeSavePath( i )

	local fname = string.format( "PixelPathProviderStateMachineTest%d", i )

	local realFileDir = KSPathUtils.getStandardFilePath( "temp" )

	return WFFileUtils.ensureFileNameIsUnique( KSPathUtils.child( realFileDir, 
																	fname ) )

end

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

local function takeFirstN( n )

	return function( t )

		local ret = {}

		for i = 1, n do

			ret[ i ] = t[ i ]

		end

		return ret

	end

end

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

--[[
local testDataPath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ),
"import-samples" )

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

local testDataFolder1 = KSPathUtils.child( testDataPath, "folder-1" )

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

local _validDownloadPath = KSPathUtils.child( testDataFolder1, "IMG_7763.jpg" )
--]]

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

local function makeMockBinaryRequester( mockDownloadStream, 
											getBinaryRequestNumFunc )

	if type( getBinaryRequestNumFunc ) ~= 'function' then

		getBinaryRequestNumFunc = 

			function( self )
				
				return #self._binaryRequests + 1

			end 

	end

	return {

		_binaryRequests = {},

		getBinaryRequestStream = 
			function() 

				return mockDownloadStream

			end,
		
		maybeRequestBinaryAtUrl = 
			function( self, ozAssetId, ozBinaryType, ozBinarySubtype, ozBinaryUrl, revisionId, priority, requestInfo ) 

				local i = getBinaryRequestNumFunc( self )

				local function cancelFn()

					self._binaryRequests[ i ] = nil

					return true

				end

				self._binaryRequests[ i ] =
					{
						ozAssetId = ozAssetId,
						ozBinaryType = ozBinaryType,
						ozBinarySize = ozBinarySubtype,
						ozBinaryUrl = ozBinaryUrl,
						ozRevisionId =  revisionId,
						requestPriority = priority,
						cancelFn = cancelFn,
						optInfo = requestInfo
					}

				return cancelFn

			end,
			
		close = 
			function( self )

				self._closed = true

			end,

		getInFlightRequests =
			function( self )

				return self._binaryRequests
				
			end,

	}
end

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

local function getRequestInfo( request )
	if request then
		return request.ozAssetId, request.ozBinaryType, request.ozBinarySize
	else
		return nil
	end
end

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

local function reverse( t )

	local reversed = {}

	local n = #t

	for i = n, 1, -1 do

		reversed[ n - i + 1 ] = t[ i ]
	end

	return reversed

end

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

local function isFullSizeRenditionDownloadForExportState( state )

	return state == "TryExistingFullSizeRenditionDownload" or 
			state == "TryJustCreatedFullSizeRenditionDownload" or
			state == "TryUpdatedFullSizeRenditionDownload"

end

--==============================================================================
-- rxTestDownloadRendition
--==============================================================================

local function rxTestDownloadRendition( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needRenderDownloadStates = PixelProviderSharedBlobCache.needRenderDownloadStates

	for i = 1, #needRenderDownloadStates do

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

	end

	local sizesToTest = reverse( WFCatRenderSize.defaultRenderSizes )

	table.insert( sizesToTest, 1, "fullsize" )

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

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

			for _, renderSizeStatus in ipairs( { 'available', 'not_specified', 'not_available' } ) do

				for _, targetDownloadSize in ipairs( sizesToTest ) do

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

						local mockDownloadStream = Rx.Subject()

						local mockBinaryRequester =
								makeMockBinaryRequester( mockDownloadStream )

						scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
							PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
								scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
								#needRenderDownloadStates + 1, mockBinaryRequester )

						local expectedTestDataTuples = {}

						local shouldNotFakeDownload = 
								not testWithBasicDownloadInfo or 
								not testWithActiveRenderInfo or
								renderSizeStatus ~= 'available' or 
								not hasDigest 

						local expectedTestMessage = shouldNotFakeDownload and
														"skipDownloadNeedInfo" or
														"received_download"

						local expectedState = shouldNotFakeDownload and
								PixelPathProviderStateMachine.DocstoreInfoStates.NeedAssetInfoForRender or
								PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender

						local watchPixelDownloads = rxTest:observe( 
														getNFromTestInfoStream(
																rxTest,
																scheduler._testData.testInfoStream,
																#needRenderDownloadStates,
																isTestMessage( "skipDownloadNeedInfo", "received_download" ),
																makeTuple )
															:map( sortTupleArray ) )

						local paths = {}

						local renderableSize = {}

						local ozAssetIds = {}

						for i, needDownloadState in ipairs( needRenderDownloadStates ) do

							local croppedLongSide = ( tonumber( targetDownloadSize ) or 1024 ) + i

							local downloadFullRenditionURI, renditions, invalid_renditions

							if renderSizeStatus ~= 'not_specified' then

								renderableSize[ i ] = ( tonumber( targetDownloadSize ) or 1024 ) - i + 1

								if renderSizeStatus == 'available' then

									local renditionSizeName = targetDownloadSize ~= 160 and
																targetDownloadSize or
																"thumbnail"

									rxTest:logf( "adding rendition of size %s to active renditons", renditionSizeName )

									if renditionSizeName == "fullsize" then

										downloadFullRenditionURI = "fakeURI"

									elseif needDownloadState ~= PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload then

										renditions = { { size = renditionSizeName, href = "ignored" } }

									else

										invalid_renditions = { { size = renditionSizeName, href = "ignored" } }

									end

								end

							end

							if testWithBasicDownloadInfo then

								ozAssetIds[ i ] = string.format( "ozAssetIdFor%s", localAssetId( i ) )

								scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache:pushCacheItem( 
														localAssetId( i ), 
													{ ozAssetId = ozAssetIds[ i ],
														changeCounter = i, 
														revId = digest( i ),
														digest = hasDigest and digest( i ) or nil, 
														croppedLongSide = croppedLongSide,
														downloadFullRenditionURI = downloadFullRenditionURI,
														renditions = renditions,
														invalid_renditions = invalid_renditions } )

							end

							if testWithActiveRenderInfo then

								paths[ i ] = makeSavePath( i )

								scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
									pixStreamId( i ), localAssetId( i ),
									{ renditionPath = paths[ i ], 
										digest = hasDigest and digest( i ) or nil, 
										revId = ozAssetIds[ i ],
										renderSize = renderableSize[ i ],
										croppedLongSide = croppedLongSide,
										userOrientation = 1, 
										devSettings = "<xmp />" } )

							end

							rxTest:logf( "Setting %s.%s request state to %s", 
											pixStreamId( i ), localAssetId( i ), 
											needDownloadState )

							SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
														needDownloadState )

							local isUnsupportedFullSizeDownload =
									targetDownloadSize == "fullsize" and
										not isFullSizeRenditionDownloadForExportState( 
											needDownloadState )

							local notFullSizeWhenRequireFullSize =
									targetDownloadSize ~= "fullsize" and
										isFullSizeRenditionDownloadForExportState( 
											needDownloadState )

							local expectedTestMessageForDownloadState =
									( shouldNotFakeDownload or 
											isUnsupportedFullSizeDownload or
											notFullSizeWhenRequireFullSize ) and
										"skipDownloadNeedInfo" or
										"received_download"

							rxTest:logf( "Setting %s.%s expectedTestData for %s to %s", 
											pixStreamId( i ), localAssetId( i ), 
											needDownloadState, expectedTestMessageForDownloadState )

							expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = 
								{ expectedTestMessageForDownloadState, pixStreamId( i ), localAssetId( i ) }

						end

						local testDesc = string.format( "%s ( testWithBasicDownloadInfo = %s, testWithActiveRenderInfo = %s, renderSizeStatus = %s, targetDownloadSize = %s, hasDigest = %s )", 
															expectedTestMessage,
															testWithBasicDownloadInfo,
															testWithActiveRenderInfo,
															renderSizeStatus, 
															targetDownloadSize,
															hasDigest )

						rxTest:log( "----------------------------------------Scheduling a test expecting " .. testDesc )

						SchedulerTestAPI.runScheduledRequests( scheduler )

						if not shouldNotFakeDownload then

							for _, requested in ipairs( mockBinaryRequester._binaryRequests ) do

								local ozAssetId, ozBinaryType, ozBinarySubtype = 
										getRequestInfo( requested )

								local revId = ozAssetId 

								rxTest:logf( "sending mockDownload of %s %s", ozBinaryType, ozBinarySubtype )

								mockDownloadStream:onNext( 
													true, 
													KSData.make( "mockDownload" ),
													revId, ozAssetId, ozBinaryType,
													ozBinarySubtype )

							end

						end

						rxTest:log( "waiting for downloads" )

						watchPixelDownloads:expectData( sortTupleArray( expectedTestDataTuples ) )

						rxTest:log( "----------------------------------------Finishing a test expecting " .. testDesc )

						for _, state in ipairs( { 
								PixelPathProviderStateMachine.DocstoreInfoStates.NeedDownloadInfo,
								PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
								} ) do

							local expectedTuples = state == expectedState and 
														expectedTestDataTuples or {}

							rxTest:log( "Checking if '" .. state .. "' tuples are correct for expected state '" .. expectedState .. "'" )

							rxTest:expectEqual( 
								sortTupleArray( expectedTuples ),
								sortTupleArray( 
									scheduler:_mapAllRequestsWithState( state,
									takeFirstN( 2 ) ) ) )

						end


						if expectedTestMessage == "received" then

							-- check to make sure all the render blobs are cleaned up
							for i = 1, #needRenderDownloadStates do

								local renderBlob =
									scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:popBlob(
										localAssetId( i ), 'rendition', targetDownloadSize,
										digest( i ) )

								rxTest:expectEqual( true, renderBlob ~= nil )

								renderBlob:close()

								renderBlob = nil

							end

						end

						if testWithActiveRenderInfo then

							for i = 1, #needRenderDownloadStates do

								scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:destroyCacheItem(
									pixStreamId( i ), localAssetId( i ) )

							end

						end

					end

					scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

				end

			end

		end

	end

end

--==============================================================================
-- rxTestDownloadNegative
--==============================================================================

local function rxTestDownloadBinary( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needDownloadOzBinaryStates = 
					{ PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForBestPossibleLocalNegativePath,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedXmpDevelopDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedXmpDevelopDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedXmpSidecarDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath, }

	for _, needDownloadOzBinaryState in ipairs( needDownloadOzBinaryStates ) do

		local needsDownloadStates =
				{ WFFiniteStateMachine.makeRetryableStates(
						PixelPathProviderStateMachine.DownloadRetryCount,
						needDownloadOzBinaryState ) }

		for i = 1, #needsDownloadStates do

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

		end

		local mockDownloadStream = Rx.Subject()

		local mockBinaryRequester =
				makeMockBinaryRequester( mockDownloadStream )

		scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
			PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
				scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
				#needsDownloadStates + 1, mockBinaryRequester )

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

			local expectedTestDataTuples = {}

			local expectedTestMessage = not testWithBasicDownloadInfo and
											"skipDownloadNeedInfo" or
											"received_download"

			local expectedState = not testWithBasicDownloadInfo and
					PixelPathProviderStateMachine.DocstoreInfoStates.NeedAssetInfoForRender or
					PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender

			local watchPixelDownloads = rxTest:observe( 
											getNFromTestInfoStream(
													rxTest,
													scheduler._testData.testInfoStream,
													#needsDownloadStates,
													isTestMessage( expectedTestMessage ),
													stripTestMessageAndMakeTuple )
												:map( sortTupleArray ) )

			local ozAssetIds = {}

			for i, needDownloadState in ipairs( needsDownloadStates ) do

				if testWithBasicDownloadInfo then

					ozAssetIds[ i ] = string.format( "ozAssetIdFor%s", localAssetId( i ) )

					local renditions, invalid_renditions

					if needDownloadState ~= PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload then

						renditions = { { size = 640, href = "fakeURI" } } 

					else
						
						invalid_renditions = { { size = 640, href = "fakeURI" } } 

					end

					scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache:pushCacheItem( 
											localAssetId( i ), 
										{ ozAssetId = ozAssetIds[ i ],
											changeCounter = i, 
											revId = digest( i ),
											digest = digest( i ), 
											croppedLongSide = 1234,
											proxies = { { size="2560", href="notAProxy" } },
											ozOriginalLink = "yeahNope",
											ozXmpDevelopLink = 'uh_huh_NOPE',
											ozXmpLink = 'justNope',
											downloadFullRenditionURI = "fakeURI",
											renditions = renditions,
											invalid_renditions = invalid_renditions,
										} )

				end

				SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
											needDownloadState )

				expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

			end

			local testDesc = string.format( "%s (%s)", expectedTestMessage,
												needDownloadOzBinaryState )

			rxTest:log( "Scheduling a test expecting " .. testDesc )

			SchedulerTestAPI.runScheduledRequests( scheduler )

			if testWithBasicDownloadInfo then

				for _, requested in ipairs( mockBinaryRequester._binaryRequests ) do

					local ozAssetId, ozBinaryType, ozBinarySubtype = 
							getRequestInfo( requested )

					local revId = ozAssetId 

					mockDownloadStream:onNext( 
										true, 
										KSData.make( "mockDownload" ),
										revId, ozAssetId, ozBinaryType,
										ozBinarySubtype )

				end

			end

			watchPixelDownloads:expectData( sortTupleArray( expectedTestDataTuples ) )

			rxTest:log( "Finishing a test expecting " .. testDesc )

			for _, state in ipairs( { 
					PixelPathProviderStateMachine.DocstoreInfoStates.NeedDownloadInfo,
					PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
					} ) do

				local expectedTuples = state == expectedState and 
											expectedTestDataTuples or {}

				rxTest:log( "Checking if '" .. state .. "' tuples are correct for expected state '" .. expectedState .. "'" )

				rxTest:expectEqual( 
					sortTupleArray( expectedTuples ),
					sortTupleArray( 
						scheduler:_mapAllRequestsWithState( state,
						takeFirstN( 2 ) ) ) )

			end

			if expectedTestMessage == "received" then

				-- check to make sure all the render blobs are cleaned up
				for i = 1, #needsDownloadStates do

					local _, ozBinaryType, ozBinarySubtype = 
							getRequestInfo( mockBinaryRequester._binaryRequests[ i ] or {} )

					local renderBlob =
						scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:popBlob(
							localAssetId( i ), ozBinaryType, ozBinarySubtype )

					rxTest:expectEqual( true, renderBlob ~= nil )

					renderBlob:close()

					renderBlob = nil

				end

			end

		end

		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

		for i = 1, #needsDownloadStates do

			scheduler:abandonAllPixStreamRequests( pixStreamId( i ) )

		end

	end

end
--==============================================================================
-- rxTestDownloadFail
--==============================================================================

local function downloadStateDoesNotHaveRetry( downloadState )

	return
		downloadState == PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload or
		downloadState == PixelPathProviderStateMachine.DownloadStates.NeedLoupeRenditionDownload or
		downloadState == PixelPathProviderStateMachine.DownloadStates.TryExistingFullSizeRenditionDownload or
		downloadState == PixelPathProviderStateMachine.DownloadStates.TryJustCreatedFullSizeRenditionDownload or
		downloadState == PixelPathProviderStateMachine.DownloadStates.TryUpdatedFullSizeRenditionDownload or
		downloadState == PixelPathProviderStateMachine.DownloadStates.TryBiggestPossibleRenditionDownload

end

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

local function rxTestDownloadFail( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needDownloadOzBinaryStates = 
					{ 
						{ PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload,
							PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckAnyLocalNegativeForRender },

						{ PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload,
							PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckAnyLocalNegativeForRender },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadError },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadError },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadErrorForBestPossibleLocalNegativePath },

							{ PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadError },

							{ PixelPathProviderStateMachine.DownloadStates.NeedLoupeRenditionDownload,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadError },

							{ PixelPathProviderStateMachine.DownloadStates.TryExistingFullSizeRenditionDownload,
								PixelPathProviderStateMachine.CheckFullRenditionDownloadLinkStatusStates.CheckForCreateFullRenditionLink },

							{ PixelPathProviderStateMachine.DownloadStates.TryJustCreatedFullSizeRenditionDownload,
								PixelPathProviderStateMachine.EndStates.WaitForFullRenditionLinkUpdate },

							{ PixelPathProviderStateMachine.DownloadStates.TryUpdatedFullSizeRenditionDownload,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportFullRenditionDownloadFail },

							{ PixelPathProviderStateMachine.DownloadStates.TryBiggestPossibleRenditionDownload,
								PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportBiggestPossibleRenditionDownloadFail },

					}

	for _, needDownloadOzBinaryStatePair in ipairs( needDownloadOzBinaryStates ) do

		local needDownloadOzBinaryState, expectedFinalState = 
				unpack( needDownloadOzBinaryStatePair )

		local needsDownloadStates =
				downloadStateDoesNotHaveRetry( needDownloadOzBinaryState ) and
					{ needDownloadOzBinaryState } or
					{ WFFiniteStateMachine.makeRetryableStates(
							PixelPathProviderStateMachine.DownloadRetryCount,
							needDownloadOzBinaryState ) }

		local expectedTestDataTuples = {}

		for i = 1, #needsDownloadStates do

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

			for _ = i, #needsDownloadStates do

				expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

			end

		end

		local mockDownloadStream = Rx.Subject()

		local mockBinaryRequester =
				makeMockBinaryRequester( mockDownloadStream )

		scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
			PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
				scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
				#needsDownloadStates + 1, mockBinaryRequester )

		local expectedErrorStateTuples = {}

		local expectedTestMessage = "retry_download"
		
		local watchPixelDownloads = rxTest:observe( 
										getNFromTestInfoStream(
												rxTest,
												scheduler._testData.testInfoStream,
												#expectedTestDataTuples,
												isTestMessage( expectedTestMessage ),
												stripTestMessageAndMakeTuple )
											:map( sortTupleArray ) )

		for i, needDownloadState in ipairs( needsDownloadStates ) do

			local croppedLongSide = 2000 + i

			local renderSize = 
				isFullSizeRenditionDownloadForExportState( needDownloadState ) and
						"fullsize" or 640

			addBaseRenderInfoCacheEntry( 
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
				needDownloadState, i, renderSize, croppedLongSide )

			scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
				pixStreamId( i ), localAssetId( i ),
				{ renditionPath = makeSavePath( i ),
					digest = digest( i ), 
					revId = 'revId', 
					renderSize = renderSize,
					croppedLongSide = croppedLongSide,
					userOrientation = 1, 
					devSettings = "<xmp />" } )

			SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
										needDownloadState )

			expectedErrorStateTuples[ #expectedErrorStateTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

		end

		rxTest:logf( "Scheduling a test expecting expecting %s %s test messages for state %s",
						#needsDownloadStates, expectedTestMessage,
						needDownloadOzBinaryState )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		for i, binaryRequest in ipairs( mockBinaryRequester._binaryRequests ) do

			local ozAssetId, ozBinaryType, ozBinarySubtype = 
										getRequestInfo( binaryRequest )

			local errStr = string.format( "fale wail #%s %s, %s, %s)", i,
											ozAssetId, ozBinaryType,
											ozBinarySubtype )

			rxTest:logf( "sending mock download failure on %s.%s request",
											ozBinaryType, ozBinarySubtype )

			mockDownloadStream:onNext( false, errStr, 'revId', ozAssetId,
											ozBinaryType, ozBinarySubtype, { url = 'something' } )

		end

		watchPixelDownloads:expectData( sortTupleArray( expectedTestDataTuples ) )

		rxTest:logf( "Finishing a test expecting expecting %s %s test messages for state %s",
						#needsDownloadStates, expectedTestMessage,
						needDownloadOzBinaryState )

		rxTest:expectEqual( 
			sortTupleArray( expectedErrorStateTuples ),
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							expectedFinalState,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginal,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxy,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

		for i = 1, #needsDownloadStates do

			scheduler:abandonAllPixStreamRequests( pixStreamId( i ) )

		end

	end

end

--==============================================================================
-- rxTestDownloadCanceledByNewRequest
--==============================================================================

local function rxTestDownloadCanceledByNewRequest( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needDownloadOzBinaryStates = 
					{ 
							{ PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingRender },

							{ PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingInvalidRender },
						
							{ PixelPathProviderStateMachine.DownloadStates.NeedLoupeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingLoupeRendition },
						
							{ PixelPathProviderStateMachine.DownloadStates.TryExistingFullSizeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingExistingFullRenditionLinkForExport },
						
							{ PixelPathProviderStateMachine.DownloadStates.TryJustCreatedFullSizeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingJustCreatedFullRenditionLinkForExport },
						
							{ PixelPathProviderStateMachine.DownloadStates.TryUpdatedFullSizeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingUpdatedFullRenditionLinkForExport },
						
							{ PixelPathProviderStateMachine.DownloadStates.TryBiggestPossibleRenditionDownload ,
								PixelPathProviderStateMachine.DownloadStates.DownloadingBiggestPossibleRenditionLinkForExport },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingOriginal },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,
								PixelPathProviderStateMachine.DownloadStates.DownloadingOriginalForRender },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath,
								PixelPathProviderStateMachine.DownloadStates.DownloadingOriginalForBestPossibleLocalNegativePath },

							{ PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingProxy },

					}

	for _, needDownloadOzBinaryStatePair in ipairs( needDownloadOzBinaryStates ) do

		local needDownloadOzBinaryState, expectedFinalState = 
				unpack( needDownloadOzBinaryStatePair )

		local needsDownloadStates =
				( needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.NeedLoupeRenditionDownload or
					needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload or
					needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.TryBiggestPossibleRenditionDownload or
						isFullSizeRenditionDownloadForExportState( needDownloadOzBinaryState ) ) and
					{ needDownloadOzBinaryState } or
					{ WFFiniteStateMachine.makeRetryableStates(
							PixelPathProviderStateMachine.DownloadRetryCount,
							needDownloadOzBinaryState ) }

		local expectedTestDataTuples = {}

		for i = 1, #needsDownloadStates do

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

			expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

		end

		local mockDownloadStream = Rx.Subject()

		local mockBinaryRequester =
				makeMockBinaryRequester( mockDownloadStream )

		scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
			PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
				scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
				#needsDownloadStates + 1, mockBinaryRequester )

		local expectedFinalTuples = {}

		local expectedTestMessage = "reschedule_download"
		
		local watchPixelDownloads = rxTest:observe( 
										getNFromTestInfoStream(
												rxTest,
												scheduler._testData.testInfoStream,
												#expectedTestDataTuples,
												isTestMessage( expectedTestMessage ),
												stripTestMessageAndMakeTuple )
											:map( sortTupleArray ) )

		for i, needDownloadState in ipairs( needsDownloadStates ) do

			local croppedLongSide = 2000 + i

			local renderSize = 
					isFullSizeRenditionDownloadForExportState( needDownloadState ) and
						"fullsize" or 640

			addBaseRenderInfoCacheEntry( 
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
				needDownloadState, i, renderSize, croppedLongSide )

			scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
				pixStreamId( i ), localAssetId( i ),
				{ renditionPath = makeSavePath( i ),
					digest = digest( i ), 
					revId = 'revId', 
					renderSize = renderSize,
					croppedLongSide = croppedLongSide,
					userOrientation = 1, 
					devSettings = "<xmp />" } )

			SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
										needDownloadState )

			expectedFinalTuples[ #expectedFinalTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

		end

		local testDesc = string.format( "expecting %s for %s",
											expectedTestMessage,
											needDownloadOzBinaryState )

		rxTest:log( "Scheduling a test expecting " .. testDesc )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		for i = 1, #needsDownloadStates do

			local binaryRequest =  mockBinaryRequester._binaryRequests[ i ]

			local ozAssetId, ozBinaryType, ozBinarySubtype = 
										getRequestInfo( binaryRequest )

			local errStr = "canceledByNewRequest"

			mockDownloadStream:onNext( false, errStr, 'revId', ozAssetId,
											ozBinaryType, ozBinarySubtype, { url = 'something' } )

		end

		rxTest:log( "Waiting for test data tuples" )

		watchPixelDownloads:expectData( sortTupleArray( expectedTestDataTuples ) )

		rxTest:log( "Finishing a test expecting " .. testDesc )

		rxTest:logf( "Checking how many requests have state: %s", expectedFinalState )

		rxTest:expectEqual( 
			sortTupleArray( expectedFinalTuples ),
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							expectedFinalState,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginal,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxy,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

		for i = 1, #needsDownloadStates do

			scheduler:abandonAllPixStreamRequests( pixStreamId( i ) )

		end

	end

end

--==============================================================================
-- rxTestAbandonedDownload
--==============================================================================

local function rxTestAbandonedDownload( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needDownloadOzBinaryStates = 
					{ 
						PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForBestPossibleLocalNegativePath,
					}

	for _, needDownloadOzBinaryState in ipairs( needDownloadOzBinaryStates ) do

		local needsDownloadStates =
				needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload and
					{ PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload } or
					{ WFFiniteStateMachine.makeRetryableStates(
							PixelPathProviderStateMachine.DownloadRetryCount,
							needDownloadOzBinaryState ) }

		local unusedBlobs = {}

		for i = 1, #needsDownloadStates do

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

			unusedBlobs[ #unusedBlobs + 1 ] = KSData.make( "mockDownload" )

		end

		local mockDownloadStream = Rx.Subject()

		local mockBinaryRequester =
				makeMockBinaryRequester( mockDownloadStream )

		scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
			PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
				scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
				#needsDownloadStates + 1, mockBinaryRequester )

		local expectedResetDownloadTuples = {}
		
		for i, needDownloadState in ipairs( needsDownloadStates ) do

			local croppedLongSide = 2000 + i

			local renderSize = 
					isFullSizeRenditionDownloadForExportState( needDownloadState ) and
						"fullsize" or 640

			addBaseRenderInfoCacheEntry( 
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
				needDownloadState, i, renderSize, croppedLongSide )

			scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
				pixStreamId( i ), localAssetId( i ),
				{ renditionPath = makeSavePath( i ),
					digest = digest( i ), 
					revId = 'revId', 
					renderSize = renderSize,
					croppedLongSide = croppedLongSide,
					userOrientation = 1, 
					devSettings = "<xmp />" } )

			SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
										needDownloadState )

			expectedResetDownloadTuples[ #expectedResetDownloadTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

		end

		local testDesc = string.format( "restart on abandon for %s",
											needDownloadOzBinaryState )

		rxTest:log( "Scheduling a test expecting " .. testDesc )

		-- start a bunch of pending downloads
		SchedulerTestAPI.runScheduledRequests( scheduler )

		-- abandon them
		for i = 1, #needsDownloadStates do

			scheduler:abandonAllPixStreamRequests( pixStreamId( i ) )

		end

		-- now send the right binary blob for all of them
		local abandonedData = {}
		for _, binaryRequest in ipairs( mockBinaryRequester._binaryRequests ) do

			local ozAssetId, ozBinaryType, ozBinarySubtype = 
										getRequestInfo( binaryRequest )

			abandonedData[ #abandonedData + 1 ] = { ozAssetId, ozBinaryType,
														ozBinarySubtype }

		end

		for i, data in ipairs( abandonedData ) do

			local ozAssetId, ozBinaryType, ozBinarySubtype = unpack( data )

			mockDownloadStream:onNext( true, unusedBlobs[ i ], 'revId', ozAssetId,
											ozBinaryType, ozBinarySubtype )
		end

		-- now step through the runLoop again
		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "Finishing a test expecting " .. testDesc )

		for i, blob in ipairs( unusedBlobs ) do

			rxTest:logf( "Checking if blob %s is closed", i )

			rxTest:expectEqual( 
				true,
				require 'WFCloseSupport'.hasBeenOrWillBeClosed( blob ) ) 

		end

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginal,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxy,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

	end

end

--==============================================================================
-- rxTestExtraDownloads
--==============================================================================

local function rxTestExtraDownloads( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needDownloadOzBinaryStates = 
					{ 
						PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForRender,
						PixelPathProviderStateMachine.DownloadStates.NeedProxyDownloadForBestPossibleLocalNegativePath,
					}

	for _, needDownloadOzBinaryState in ipairs( needDownloadOzBinaryStates ) do

		local needsDownloadStates =
				needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload and
					{ PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload } or
					{ WFFiniteStateMachine.makeRetryableStates(
							PixelPathProviderStateMachine.DownloadRetryCount,
							needDownloadOzBinaryState ) }

		local unusedBlobs = {}

		for i = 1, #needsDownloadStates do

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

			unusedBlobs[ #unusedBlobs + 1 ] = KSData.make( "mockDownload" )

		end

		local mockDownloadStream = Rx.Subject()

		local mockBinaryRequester =
				makeMockBinaryRequester( mockDownloadStream )

		scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
			PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
				scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
				#needsDownloadStates + 1, mockBinaryRequester )

		local expectedResetDownloadTuples = {}
		
		for i, needDownloadState in ipairs( needsDownloadStates ) do

			local croppedLongSide = 2000 + i

			local renderSize = 
					isFullSizeRenditionDownloadForExportState( needDownloadState ) and
						"fullsize" or 640

			addBaseRenderInfoCacheEntry( 
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
				needDownloadState, i, renderSize, croppedLongSide )

			scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
				pixStreamId( i ), localAssetId( i ),
				{ renditionPath = makeSavePath( i ),
					digest = digest( i ), 
					revId = 'revId', 
					renderSize = renderSize,
					croppedLongSide = croppedLongSide,
					userOrientation = 1, 
					devSettings = "<xmp />" } )

			SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
										needDownloadState )

			expectedResetDownloadTuples[ #expectedResetDownloadTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

		end

		rxTest:expectEqual( 
			sortTupleArray( expectedResetDownloadTuples ),
			sortTupleArray( 
				WFFunctionUtils.map(
					takeFirstN( 2 ),
						scheduler:_getRequestsWithStatesInPriorityOrder( 
									unpack( needsDownloadStates ) ) ) ) )

		local testDesc = string.format( "restart on abandon for %s",
											needDownloadOzBinaryState )

		rxTest:log( "Scheduling a test expecting " .. testDesc )

		-- start a bunch of pending downloads
		SchedulerTestAPI.runScheduledRequests( scheduler )

		-- now send an unusable binary blob for all of them
		for i, binaryRequest in ipairs( mockBinaryRequester._binaryRequests ) do

			local _, ozBinaryType, ozBinarySubtype = 
										getRequestInfo( binaryRequest )

			mockDownloadStream:onNext( true, unusedBlobs[ i ], 'revId', 'bogusOzAssetId',
											ozBinaryType, ozBinarySubtype )

		end

		-- now step through the runLoop again
		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "Finishing a test expecting " .. testDesc )

		for _, blob in ipairs( unusedBlobs ) do

			rxTest:expectEqual( 
				true,
				require 'WFCloseSupport'.hasBeenOrWillBeClosed( blob ) ) 

		end

		--

		rxTest:expectEqual( 
			sortTupleArray( expectedResetDownloadTuples ),
			sortTupleArray( 
				WFFunctionUtils.map(
					takeFirstN( 2 ),
						scheduler:_getRequestsWithStatesInPriorityOrder( 
							unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginal,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForRender,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxy,
							takeFirstN( 2 ) ) ) )

		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForRender,
							takeFirstN( 2 ) ) ) )


		rxTest:expectEqual( 
			{},
			sortTupleArray( 
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
							takeFirstN( 2 ) ) ) )

		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

		for i = 1, #needsDownloadStates do

			scheduler:abandonAllPixStreamRequests( pixStreamId( i ) )

		end

	end

end

--==============================================================================
-- rxTestDownloadNewerRendition
--==============================================================================

local function rxTestDownloadNewerRendition( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needsDownloadStates =
			{ WFFiniteStateMachine.makeRetryableStates(
					PixelPathProviderStateMachine.DownloadRetryCount,
					PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload ) }

	local unusedBlobs = {}

	for i = 1, #needsDownloadStates do

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

		unusedBlobs[ #unusedBlobs + 1 ] = KSData.make( "mockDownload" )

	end

	local mockDownloadStream = Rx.Subject()

	local mockBinaryRequester =
			makeMockBinaryRequester( mockDownloadStream )

	scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
		PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
			scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
			#needsDownloadStates + 1, mockBinaryRequester )

	local expectedResetDownloadTuples = {}
	
	for i, needDownloadState in ipairs( needsDownloadStates ) do

		local croppedLongSide = 2000 + i

		local renderSize = 
				isFullSizeRenditionDownloadForExportState( needDownloadState ) and
					"fullsize" or 640

		addBaseRenderInfoCacheEntry( 
			scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
			needDownloadState, i, renderSize, croppedLongSide )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i ), localAssetId( i ),
			{ renditionPath = makeSavePath( i ),
				renderSize = renderSize,
				revId = digest( i ),
				digest = digest( i ), 
				croppedLongSide = croppedLongSide,
				userOrientation = 1, 
				devSettings = "<xmp />" } )

		SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
									needDownloadState )

		expectedResetDownloadTuples[ #expectedResetDownloadTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

	end


	-- start a bunch of pending downloads
	SchedulerTestAPI.runScheduledRequests( scheduler )

	-- abandon them
	for i = 1, #needsDownloadStates do

		scheduler:abandonAllPixStreamRequests( pixStreamId( i ) )

	end

	-- now send the right binary blob for all of the abandoned download requests

	local abandonedData = {}

	for _, binaryRequest in ipairs( mockBinaryRequester._binaryRequests ) do

		local ozAssetId, ozBinaryType, ozBinarySubtype = 
									getRequestInfo( binaryRequest )

		abandonedData[ #abandonedData + 1 ] = { ozAssetId, ozBinaryType,
													ozBinarySubtype }

	end

	for i, data in ipairs( abandonedData ) do

		local ozAssetId, ozBinaryType, ozBinarySubtype = unpack( data )

		mockDownloadStream:onNext( true, unusedBlobs[ i ], 'revId', ozAssetId,
										ozBinaryType, ozBinarySubtype )

	end

	-- now step through the runLoop again and verify:
	--
	-- 1) all the unused blobs were closed
	-- 2) no save rendition blob requests exist
	SchedulerTestAPI.runScheduledRequests( scheduler )

	for _, blob in ipairs( unusedBlobs ) do

		rxTest:expectEqual( 
			true,
			require 'WFCloseSupport'.hasBeenOrWillBeClosed( blob ) ) 

	end

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginal,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForRender,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveOriginalForBestPossibleLocalNegativePath,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxy,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForRender,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SaveDownloadStates.NeedToSaveProxyForBestPossibleLocalNegativePath,
						takeFirstN( 2 ) ) ) )

	scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:destroyAll()

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

end

--==============================================================================
-- rxTestDownloadTaskBacklog
--==============================================================================

local function rxTestDownloadTaskBacklog( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needsDownloadStates = PixelProviderSharedBlobCache.needsDownloadStates

	local mockDownloadStream = Rx.Subject()

	local mockBinaryRequester =
			makeMockBinaryRequester( mockDownloadStream )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
		PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
			scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
			#needsDownloadStates, mockBinaryRequester )

	for i = 1, #needsDownloadStates + 1 do

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


		local renderSize = 
				i <= #needsDownloadStates and
				isFullSizeRenditionDownloadForExportState( needsDownloadStates[ i ] ) and
					"fullsize" or 640

		local ozAssetId = string.format( "ozAssetIdFor%s", localAssetId( i ) )

		addBaseRenderInfoCacheEntry( 
			scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
			needsDownloadStates[ i ], i, renderSize, 1234 )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i ), localAssetId( i ),
			{ renditionPath = makeSavePath( i ), 
				digest = digest( i ), 
				revId = ozAssetId,
				renderSize = 640,
				croppedLongSide = 1024,
				userOrientation = 1, 
				devSettings = "<xmp />" } )

		if i <= #needsDownloadStates then

			SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
										needsDownloadStates[ i ] )

		end

	end

	SchedulerTestAPI.runScheduledRequests( scheduler )

	local watchPixelDownloads = rxTest:observe( 
									getNFromTestInfoStream(
											rxTest,
											scheduler._testData.testInfoStream,
											1,
											isTestMessage( "skipDownloadDueToBacklog" ) ) )

	SchedulerTestAPI.setState( scheduler, 
								pixStreamId( #needsDownloadStates + 1 ), 
								localAssetId( #needsDownloadStates + 1 ), 
								PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	watchPixelDownloads:expectData( { "skipDownloadDueToBacklog" } )

end

--==============================================================================
-- rxTestDownloadTaskRescheduling
--==============================================================================
	
local function extractLocalIdsFromRequests( requests )

	local localIds = {}
	
	for _, request in pairs( requests ) do
		localIds[ #localIds + 1 ] = { request[ 2 ] }
	end
	
	return localIds

end

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

local function rxTestDownloadTaskRescheduling( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	local needDownloadOzBinaryStates = 
					{ 

							{ PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingRender },

							{ PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingInvalidRender },

							{ PixelPathProviderStateMachine.DownloadStates.NeedLoupeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingLoupeRendition },

							{ PixelPathProviderStateMachine.DownloadStates.TryExistingFullSizeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingExistingFullRenditionLinkForExport },

							{ PixelPathProviderStateMachine.DownloadStates.TryJustCreatedFullSizeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingJustCreatedFullRenditionLinkForExport },

							{ PixelPathProviderStateMachine.DownloadStates.TryUpdatedFullSizeRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingUpdatedFullRenditionLinkForExport },

							{ PixelPathProviderStateMachine.DownloadStates.TryBiggestPossibleRenditionDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingBiggestPossibleRenditionLinkForExport },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingOriginal },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForRender,
								PixelPathProviderStateMachine.DownloadStates.DownloadingOriginalForRender },

							{ PixelPathProviderStateMachine.DownloadStates.NeedOriginalDownloadForBestPossibleLocalNegativePath,
								PixelPathProviderStateMachine.DownloadStates.DownloadingOriginalForBestPossibleLocalNegativePath },

							{ PixelPathProviderStateMachine.DownloadStates.NeedProxyDownload,
								PixelPathProviderStateMachine.DownloadStates.DownloadingProxy },

					}

	for _, needDownloadOzBinaryStatePair in ipairs( needDownloadOzBinaryStates ) do

		local function pixStreamIdForDownloadState( i )

			return pixStreamId( i )

		end

		local needDownloadOzBinaryState, downloadingOzBinaryState = 
				unpack( needDownloadOzBinaryStatePair )

		local needsDownloadStates = 
				( needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.NeedLoupeRenditionDownload or
					needDownloadOzBinaryState == PixelPathProviderStateMachine.DownloadStates.NeedInvalidRenderDownload or
						isFullSizeRenditionDownloadForExportState( needDownloadOzBinaryState ) ) and
					{ needDownloadOzBinaryState } or
					{ WFFiniteStateMachine.makeRetryableStates(
							PixelPathProviderStateMachine.DownloadRetryCount,
							needDownloadOzBinaryState ) }

		local mockDownloadStream = Rx.Subject()

		local mockBinaryRequester =
				makeMockBinaryRequester( mockDownloadStream )

		scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
			PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
				scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
				#needsDownloadStates, mockBinaryRequester )

		local maxDownloads = #needsDownloadStates + 5

		local function getRequestPriority( i )

			if i == 1 then

				return PixelPathProviderRequestTypeNames.loupe_next

			elseif i == maxDownloads then

				return PixelPathProviderRequestTypeNames.idle

			elseif i > #needsDownloadStates then

				return PixelPathProviderRequestTypeNames.highest

			elseif i == 2 then

				return PixelPathProviderRequestTypeNames.grid_prefetch

			elseif i == 3 then

				return PixelPathProviderRequestTypeNames.download_soon

			end

			return PixelPathProviderRequestTypeNames.grid_visible

		end

		local expectedLocalIds = {}

		for i = 1, maxDownloads do

			local requestPriority = getRequestPriority( i )

			if i <= #needsDownloadStates then

				expectedLocalIds[ #expectedLocalIds + 1 ] = { localAssetId( i ) }

			end

			scheduler:addRequest( pixStreamIdForDownloadState( i ), localAssetId( i ), requestPriority, 1 )

			local ozAssetId = string.format( "ozAssetIdFor%s", localAssetId( i ) )

			local renderSize = 
					isFullSizeRenditionDownloadForExportState( needsDownloadStates[ i ] ) and
						"fullsize" or 640

			addBaseRenderInfoCacheEntry( 
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
				needsDownloadStates[ i ], i, renderSize, 1234 )

			scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
				pixStreamIdForDownloadState( i ), localAssetId( i ),
				{ renditionPath = makeSavePath( i ), 
					digest = digest( i ), 
					revId = ozAssetId,
					renderSize = 640,
					croppedLongSide = 1024,
					userOrientation = 1, 
					devSettings = "<xmp />" } )

			if i <= #needsDownloadStates then

				SchedulerTestAPI.setState( 
					scheduler, pixStreamIdForDownloadState( i ), localAssetId( i ), needDownloadOzBinaryState )

			end

		end
		
		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:logf( "Checking that we have %s local ids in the %s state when starting from %s",
						#expectedLocalIds, downloadingOzBinaryState, needDownloadOzBinaryState )

		rxTest:expectEqual( 
			sortTupleArray( expectedLocalIds, compareArrays ),
			sortTupleArray( 
				extractLocalIdsFromRequests( 
					SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( scheduler,
						downloadingOzBinaryState ),
				compareArrays ) ) )

		-- preempt the lowest priority in flight download:
		-- 1 is the lowest, 2 second lowest, 3 third lowest

		for i = 1,#needsDownloadStates do

			local renderSize = 
					isFullSizeRenditionDownloadForExportState( needsDownloadStates[ i ] ) and
						"fullsize" or 640

			local extraRequestNum = #needsDownloadStates + 1

			addBaseRenderInfoCacheEntry( 
				scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
				needsDownloadStates[ i ], extraRequestNum, renderSize, 1234 )

			rxTest:logf( "Testing preempt request priority %s (i=%s)", getRequestPriority( i ), i )

			expectedLocalIds[ i ] = { localAssetId( #needsDownloadStates + i ) }

			local watchPixelDownloads = rxTest:observe( 
											getNFromTestInfoStream(
													rxTest,
													scheduler._testData.testInfoStream,
													1,
													isTestMessage( "reschedulingDownload" ) ) )

			SchedulerTestAPI.setState( scheduler, 
										pixStreamIdForDownloadState( #needsDownloadStates + i ), 
										localAssetId( #needsDownloadStates + i ), 
										needDownloadOzBinaryState )

			SchedulerTestAPI.runScheduledRequests( scheduler )

			watchPixelDownloads:expectData( { "reschedulingDownload" } )

			rxTest:expectEqual( 
				sortTupleArray( 
					expectedLocalIds, 
					compareArrays ),
				sortTupleArray( 
					extractLocalIdsFromRequests( 
						SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
							scheduler, downloadingOzBinaryState ),
					compareArrays ) ) )

		end

		-- don't preempt a saturated download queue with a request that is of lower priority
		
		local watchPixelDownloads = rxTest:observe( 
										getNFromTestInfoStream(
												rxTest,
												scheduler._testData.testInfoStream,
												1,
												isTestMessage( "skipDownloadDueToBacklog" ) ) )

		SchedulerTestAPI.setState( scheduler, 
									pixStreamIdForDownloadState( maxDownloads ), 
									localAssetId( maxDownloads ), 
									PixelPathProviderStateMachine.DownloadStates.NeedRenderDownload )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		watchPixelDownloads:expectData( { "skipDownloadDueToBacklog" } )

		for i = 1, maxDownloads do

			scheduler:abandonAllLocalAssetIdRequests( localAssetId ( i ) )
	
		end

	end

end

--==============================================================================
-- rxTestDeletedPixelRequestsCauseCancelation
--==============================================================================

local function rxTestDeletedPixelRequestsCauseCancelation( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needsDownloadStates = PixelProviderSharedBlobCache.needsDownloadStates

	local mockDownloadStream = Rx.Subject()

	local binaryRequestNum 

	local function generateNextBinaryRequestNum()

		binaryRequestNum = ( binaryRequestNum or 0 ) + 1

		return binaryRequestNum 

	end

	local mockBinaryRequester =
			makeMockBinaryRequester( mockDownloadStream,
										generateNextBinaryRequestNum )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
		PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
			scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
			1, mockBinaryRequester )


	local function setupPixProviderCacheForLocalAssetId( i, needsDownloadState )

		local renditionSizeName = 
				isFullSizeRenditionDownloadForExportState( needsDownloadState ) and
								"fullsize" or 640
	
		addBaseRenderInfoCacheEntry( 
			scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
			needsDownloadState, i, renditionSizeName, 1234 )

	end

	local function setupPixProviderCacheForRequest( i, j, needsDownloadState )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i ), localAssetId( j ),
			{ renditionPath = makeSavePath( j ), 
				digest = digest( j ), 
				revId = makeOzAssetId( j ),
				renderSize = isFullSizeRenditionDownloadForExportState( needsDownloadState ) and
								"fullsize" or 640,
				croppedLongSide = 1024,
				userOrientation = 1, 
				devSettings = "<xmp />" } )

	end

	for i, needsDownloadState in ipairs( needsDownloadStates ) do

		rxTest:logf( "--------------------------------------------------Run with state: %s", needsDownloadState )

		rxTest:log( "add two requests for same localAssetId with highest and lowest priorities" )

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

		scheduler:addRequest( pixStreamId( 2 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.idle, 1 )

		setupPixProviderCacheForLocalAssetId( 1, needsDownloadState )

		setupPixProviderCacheForRequest( 1, 1, needsDownloadState )

		setupPixProviderCacheForRequest( 2, 1, needsDownloadState )

		rxTest:log( "add a request for a different localAssetId at grid_visible" )

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

		setupPixProviderCacheForLocalAssetId( 2, needsDownloadState )

		setupPixProviderCacheForRequest( 1, 2 )

		rxTest:log( "start downloading at highest priority" )

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

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:logf( "verify scheduler thinks it is downloading %s.%s",
						pixStreamId( 1 ), localAssetId( 1 ) )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 1 ) }, } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 1 )" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 3*i - 2 ].ozAssetId,
			makeOzAssetId( 1 ) )
		
		rxTest:logf( "other request now should join download for download state: %s", needsDownloadState )

		SchedulerTestAPI.setState( scheduler, pixStreamId( 2 ), localAssetId( 1 ), 
									needsDownloadState )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify scheduler thinks both are downloading now" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 1 ) },
								{ pixStreamId( 2 ), localAssetId( 1 ) } } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 1 )" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 3*i - 2 ].ozAssetId,
			makeOzAssetId( 1 ) )

		rxTest:log( "add another assetId, which should be backlogged" )
	
		local watchPixelDownloads = rxTest:observe( 
										getNFromTestInfoStream(
												rxTest,
												scheduler._testData.testInfoStream,
												1,
												isTestMessage( "skipDownloadDueToBacklog" ) ) )

		SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 2 ), 
									needsDownloadState )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify this causes backlog" )

		watchPixelDownloads:expectData( { "skipDownloadDueToBacklog" } )

		rxTest:log( "verify scheduler thinks both are downloading now" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 1 ) },
								{ pixStreamId( 2 ), localAssetId( 1 ) } } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify scheduler thinks other assetId is still queued" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 2 ) }, } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler, needsDownloadState ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 1 )" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[  3*i - 2 ].ozAssetId,
			makeOzAssetId( 1 ) )

		rxTest:log( "delete highest request" )

		watchPixelDownloads = rxTest:observe( 
										getNFromTestInfoStream(
												rxTest,
												scheduler._testData.testInfoStream,
												1,
												isTestMessage( "reschedulingDownload" ) ) )

		scheduler:removeRequest( pixStreamId( 1 ), localAssetId( 1 ) )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify this causes preemption" )

		watchPixelDownloads:expectData( { "reschedulingDownload" } )

		rxTest:log( "verify scheduler thinks localAssetId( 2 ) is downloading" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 2 ) }, } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify scheduler thinks localAssetId( 1 ) is queued" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 2 ), localAssetId( 1 ) }, } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler, unpack( needsDownloadStates ) ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 2 )" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 3*i - 1 ].ozAssetId,
			makeOzAssetId( 2 ) )

		rxTest:log( "verify mockDownloader has discarded original download" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[  3*i - 2 ],
			nil )

		rxTest:log( "delete grid_visible request" )

		scheduler:removeRequest( pixStreamId( 1 ), localAssetId( 2 ) )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify scheduler thinks localAssetId( 1 ) is downloading" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 2 ), localAssetId( 1 ) }, } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify scheduler thinks nothing is queued" )

		rxTest:expectEqual( 
			sortTupleArray( {} ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler, unpack( needsDownloadStates ) ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 1 ) again" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 3*i ].ozAssetId,
			makeOzAssetId( 1 ) )

		rxTest:log( "verify mockDownloader has discarded second download" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 3*i - 1 ],
			nil )

		rxTest:log( "delete idle request" )

		scheduler:removeRequest( pixStreamId( 2 ), localAssetId( 1 ) )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify scheduler thinks nothing is downloading" )

		rxTest:expectEqual( 
			{},
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify scheduler thinks nothing is queued" )

		rxTest:expectEqual( 
			sortTupleArray( {} ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler, needsDownloadState ) ) ) )

		rxTest:log( "verify mockDownloader is not downloding" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 3*i ],
			nil )

	end
end

--==============================================================================
-- rxTestHigherHTTPPrioritySameDownload
--==============================================================================

local function rxTestHigherHTTPPrioritySameDownload( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDownloadTask" )

	local needsDownloadStates = PixelProviderSharedBlobCache.needsDownloadStates

	local mockDownloadStream = Rx.Subject()

	local binaryRequestNum 

	local function generateNextBinaryRequestNum()

		binaryRequestNum = ( binaryRequestNum or 0 ) + 1

		return binaryRequestNum 

	end

	local mockBinaryRequester =
			makeMockBinaryRequester( mockDownloadStream,
										generateNextBinaryRequestNum )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
		PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
			scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
			1, mockBinaryRequester )

	local function setupPixProviderCacheForLocalAssetId( i, needsDownloadState )

		local renditionSizeName = 
				isFullSizeRenditionDownloadForExportState( needsDownloadState ) and
								"fullsize" or 640
	
		addBaseRenderInfoCacheEntry( 
			scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache,
			needsDownloadState, i, renditionSizeName, 1234 )

	end

	local function setupPixProviderCacheForRequest( i, j )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i ), localAssetId( j ),
			{ renditionPath = makeSavePath( j ), 
				digest = digest( j ), 
				revId = makeOzAssetId( j ),
				renderSize = 640,
				croppedLongSide = 1024,
				userOrientation = 1, 
				devSettings = "<xmp />" } )

	end

	for i, needsDownloadState in ipairs( needsDownloadStates ) do

		rxTest:logf( "--------------------------------------------------Run with state: %s", needsDownloadState )

		rxTest:log( "add two requests for same localAssetId with highest and lowest priorities" )

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

		scheduler:addRequest( pixStreamId( 2 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.highest, 1 )

		setupPixProviderCacheForLocalAssetId( 1, needsDownloadState )

		setupPixProviderCacheForRequest( 1, 1 )

		setupPixProviderCacheForRequest( 2, 1 )

		rxTest:log( "start downloading at idle priority" )

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

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify scheduler thinks it is downloading" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 1 ) }, } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 1 )" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 2*i - 1 ].ozAssetId,
			makeOzAssetId( 1 ) )
		
		rxTest:log( "other higher priority should cancel download and restart it at higher priority" )

		SchedulerTestAPI.setState( scheduler, pixStreamId( 2 ), localAssetId( 1 ), 
									needsDownloadState )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify scheduler thinks both are downloading now" )

		rxTest:expectEqual( 
			sortTupleArray( { { pixStreamId( 1 ), localAssetId( 1 ) },
								{ pixStreamId( 2 ), localAssetId( 1 ) } } ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify mockDownloader is downloading localAssetId( 1 ) on different request" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 2*i ].ozAssetId,
			makeOzAssetId( 1 ) )

		rxTest:log( "verify mockDownloader orig request is gone" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 2*i - 1 ],
			nil )

		rxTest:log( "delete highest requests" )

		scheduler:removeRequest( pixStreamId( 1 ), localAssetId( 1 ) )

		scheduler:removeRequest( pixStreamId( 2 ), localAssetId( 1 ) )

		SchedulerTestAPI.runScheduledRequests( scheduler )

		rxTest:log( "verify scheduler thinks nothing is downloading" )

		rxTest:expectEqual( 
			{},
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler,
									unpack( PixelProviderSharedBlobCache.downloadingBlobStates ) ) ) ) )

		rxTest:log( "verify scheduler thinks nothing is queued" )

		rxTest:expectEqual( 
			sortTupleArray( {} ),
			sortTupleArray( WFFunctionUtils.map(
								takeFirstN( 2 ),
								SchedulerTestAPI.getRequestsWithStatesInPriorityOrder( 
									scheduler, needsDownloadState ) ) ) )

		rxTest:log( "verify mockDownloader is not downloding" )

		rxTest:expectEqual( 
			mockBinaryRequester._binaryRequests[ 2*i ],
			nil )

	end

end

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

return WFRxTestCase.wrapSuite {

	rxTestDownloadRendition = rxTestDownloadRendition,

--	rxTestDownloadInvalidRendition = rxTestDownloadInvalidRendition,

	rxTestDownloadBinary = rxTestDownloadBinary,

	rxTestDownloadFail = rxTestDownloadFail,

	rxTestDownloadCanceledByNewRequest = rxTestDownloadCanceledByNewRequest,

	rxTestAbandonedDownload = rxTestAbandonedDownload,

	rxTestExtraDownloads = rxTestExtraDownloads,

	rxTestDownloadNewerRendition = rxTestDownloadNewerRendition,

	rxTestDownloadTaskBacklog = rxTestDownloadTaskBacklog,

	rxTestDownloadTaskRescheduling = rxTestDownloadTaskRescheduling,

	rxTestDeletedPixelRequestsCauseCancelation = 
		rxTestDeletedPixelRequestsCauseCancelation,

	rxTestHigherHTTPPrioritySameDownload = 
		rxTestHigherHTTPPrioritySameDownload,

}

