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

WFBinaryRequesterTest.lua

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

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

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

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

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

	-- WF.core.lua
local KSData = require 'KSData'
local KSIdGenerator = require 'KSIdGenerator'
local WFCloseSupport = require 'WFCloseSupport'
local WFFunctionUtils = require 'WFFunctionUtils'
local WFFileUtils = require 'WFFileUtils'
local KSPathUtils = require 'KSPathUtils'

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

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

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

local MockBinaryDownloader = {
	WFCloseSupport.addCloseSupportMethods,	
}

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

local function downloadOneBinary( _, docId, linksKey )

	-- The 'binary' returned from MockBinaryDownloader is just the linksKey + the
	-- doc Id. It's unique for each binary, so it works great for testing.
	
	local delay = 0.1
	
	if docId == 'in_flight_test' then
		delay = 1.5
	end
	
	local revId = KSIdGenerator.uuidInLowercaseHex()
	
	return Observable.timer( delay, WFRxScheduler.sharedScheduler() )
		:flatMapLatest( function()
			if docId == 'error_id' then
				return Observable.throw( 'ERROR' )
			else
				return Observable.just( KSData.make( linksKey .. docId ), revId )
			end
		end )
	
end

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

function MockBinaryDownloader:getRendition( docId, renditionType )

	return downloadOneBinary( self, docId, '/rels/rendition_type/' .. renditionType )

end

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

function MockBinaryDownloader:getProxy( docId, proxyType )

	return downloadOneBinary( self, docId, '/rels/proxy_type/' .. proxyType )

end

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

function MockBinaryDownloader:getMaster( docId )

	return downloadOneBinary( self, docId, '/rels/master' )

end

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

MockBinaryDownloader = Rx.Class 'MockBinaryDownloader'( MockBinaryDownloader )


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

local WFBinaryRequesterTest = {}

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

local function mapKSDataToString( okay, maybeKSData )

	if okay and not KSData.isData( maybeKSData ) then
		return string.format( "Expected KSData, got %s instead", type( maybeKSData ) ),
			   maybeKSData
	elseif not okay then
		assert( okay == false )
		assert( maybeKSData == 'ERROR' )
		return tostring( maybeKSData )
	else
		return tostring( maybeKSData )
	end

end

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

local expectedOzLinkPaths = {
	rendition_t2X 		= '/rels/rendition_type/thumbnail2x',
	rendition_640 		= '/rels/rendition_type/640',
	rendition_1280 		= '/rels/rendition_type/1280',
	rendition_2048		= '/rels/rendition_type/2048',
	rendition_1080p 		= '/rels/rendition_type/1080p',
	rendition_1080p709		= '/rels/rendition_type/1080p709',
	rendition_720p		= '/rels/rendition_type/720p',
	rendition_360p			= '/rels/rendition_type/360p',
	rendition_pano		= '/rels/rendition_type/panorama',
	proxy_2560 			= '/rels/proxy_type/2560',
	proxy_fullsize 		= '/rels/proxy_type/fullsize',
	proxy_1080p 		= '/rels/proxy_type/1080p',
	proxy_1080p709		= '/rels/proxy_type/1080p709',
	proxy_720p			= '/rels/rendition_type/720p',
	proxy_360p			= '/rels/proxy_type/360p',
	proxy_panorama 		= '/rels/proxy_type/panorama',
	master				= '/rels/master',
}

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

local function setup( rxTest )

	local downloader = MockBinaryDownloader()
	rxTest:addCloseTarget( downloader )
	
	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local rootPath = KSPathUtils.getStandardFilePath( 'temp' )
	
	local binaryRequester = WFBinaryRequester( downloader )
	rxTest:addCloseTarget( binaryRequester )

	rxTest:finally( function()
	
		local catalogPath = rootPath .. '/' .. catalogId
		WFFileUtils.delete( catalogPath )
		
	end )

	return binaryRequester, catalogId

end

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

function WFBinaryRequesterTest.rxTestSingleRequest( rxTest )

	local binaryRequester = setup( rxTest )
	
	local binaryRequestStream = binaryRequester:getBinaryRequestStream()
	
	local listener = rxTest:observe( binaryRequestStream:map( mapKSDataToString ) )
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	binaryRequester:requestBinary( assetId,
								'rendition',
								'thumbnail2x',
								1 )

	listener:expectDataWithinTime( 1, expectedOzLinkPaths.rendition_t2X .. assetId )

end

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

function WFBinaryRequesterTest.rxTestErrorResponse( rxTest )

	local binaryRequester = setup( rxTest )
	
	local binaryRequestStream = binaryRequester:getBinaryRequestStream()
	
	local listener = rxTest:observe( binaryRequestStream:map( mapKSDataToString ) )
	
	local assetId = 'error_id'
	
	binaryRequester:requestBinary( assetId,
								'rendition',
								'thumbnail2x',
								1 )

	listener:expectDataWithinTime( 1, 'ERROR' ) -- Errors are mapped to data and sent over the stream.

end

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

function WFBinaryRequesterTest.rxTestFIFOOrdering( rxTest )

	local binaryRequester = setup( rxTest )
	
	local binaryRequestStream = binaryRequester:getBinaryRequestStream()
	
	local requestCount = 0
	
	local listener = rxTest:observe( binaryRequestStream
		
		:map( function( ... )
		
			requestCount = requestCount + 1
			return mapKSDataToString( ... )
			
		end ) )
	
	local assetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	binaryRequester:requestBinary( assetId1,
								'original',
								'master', 1 )

	local assetId2 = KSIdGenerator.uuidInLowercaseHex()

	binaryRequester:requestBinary( assetId2,
								'rendition',
								'2048', 1 )

	local assetId3 = KSIdGenerator.uuidInLowercaseHex()

	binaryRequester:requestBinary( assetId3,
								'rendition',
								'thumbnail2x', 1 )

	local assetId4 = KSIdGenerator.uuidInLowercaseHex()

	binaryRequester:requestBinary( assetId4,
								'rendition',
								'2048', 1 )
								
	local assetId5 = KSIdGenerator.uuidInLowercaseHex()

	binaryRequester:requestBinary( assetId5,
								'original',
								'master', 1 )

	listener:expectData( expectedOzLinkPaths.master .. assetId1 )

	rxTest:expectEqual( 1, requestCount )

	listener:expectData( expectedOzLinkPaths.rendition_2048 .. assetId2 )

	rxTest:expectEqual( 2, requestCount )

	listener:expectData( expectedOzLinkPaths.rendition_t2X .. assetId3 )

	rxTest:expectEqual( 3, requestCount )

	listener:expectData( expectedOzLinkPaths.rendition_2048 .. assetId4 )

	rxTest:expectEqual( 4, requestCount )

	listener:expectData( expectedOzLinkPaths.master .. assetId5 )

	rxTest:expectEqual( 5, requestCount )
end

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

function WFBinaryRequesterTest.rxTestRequestCancellation( rxTest )

	rxTest:setTimeLimit( 10 )

	local binaryRequester = setup( rxTest )
	
	local binaryRequestStream = binaryRequester:getBinaryRequestStream()
	
	local requestCount = 0
	
	rxTest:observe( binaryRequestStream
		
		:tap( function()
			requestCount = requestCount + 1
		end ) )
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	-- basic cancel should work
	
	local cancelFunc = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )

	cancelFunc()

	rxTest:expectEqual( requestCount, 0 )

	-- if there are two requests for a given binary, canceling one request
	-- shouldn't affect the other one, both need to be canceled to fully remove the request
	
	 binaryRequester:requestBinary( 
							assetId,
							'original',
							'master', 1 )
								
	local cancelFunc2 = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )
	cancelFunc2()

	rxTest:sleep( 2 )

	rxTest:expectEqual( requestCount, 1 )

end

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

function WFBinaryRequesterTest.rxTestInFlightRequestCancellation( rxTest )

	local binaryRequester = setup( rxTest )
	
	rxTest:setTimeLimit( 15 )
	
	local binaryRequestStream = binaryRequester:getBinaryRequestStream()
	
	local requestCount = 0
	
	rxTest:observe( binaryRequestStream
		
		:tap( function()
			requestCount = requestCount + 1
		end ) )
	
	local assetId = 'in_flight_test'
	
	local cancelFunc = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )

	rxTest:sleep( 0.1 )

	cancelFunc()

	rxTest:sleep( 2 )

	rxTest:expectEqual( requestCount, 0 )

	-- if there are two requests open for a single binary that's in-flight, canceling one request
	-- shouldn't affect the other one. Both need to be canceled to stop the in-flight request.
	
	local cancelFunc1 = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )
								
	local cancelFunc2 = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )

	rxTest:sleep( 0.1 )
		
	cancelFunc2()
	
	rxTest:sleep( 2 )

	rxTest:expectEqual( 1, requestCount )

	-- Canceling both requests should cancel the in-flight request and our
	-- completed request count should not increment.
	
	cancelFunc1 = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )
								
	cancelFunc2 = binaryRequester:requestBinary( 
								assetId,
								'original',
								'master', 1 )

	rxTest:sleep( 0.1 )
		
	cancelFunc2()
	cancelFunc1()
	
	rxTest:sleep( 2 )

	rxTest:expectEqual( 1, requestCount )

end

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

function WFBinaryRequesterTest.rxTestAddAlreadyInFlightRequest( rxTest )

	local binaryRequester = setup( rxTest )
	
	rxTest:setTimeLimit( 5 )
	
	local binaryRequestStream = binaryRequester:getBinaryRequestStream()
	
	local requestCount = 0
	
	rxTest:observe( binaryRequestStream
		
		:tap( function()
			requestCount = requestCount + 1
		end ) )
	
	local assetId = 'in_flight_test'
	
	binaryRequester:requestBinary( assetId,
								'original',
								'master',
								1 )

	rxTest:sleep( 0.1 )

	binaryRequester:requestBinary( assetId,
								'original',
								'master',
								1 )

	rxTest:sleep( 2 )

	rxTest:expectEqual( requestCount, 1 )

end

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

local proxySizes = {
	'panorama',
	'fullsize',
	'2560',
	'1080p',
	'1080p709',
	'720p',
	'360p'
}

for _, proxySize in ipairs( proxySizes ) do

	local testName = string.format( "rxTestSingleRequest_proxy_%s", proxySize ) 

	WFBinaryRequesterTest[ testName ] = function( rxTest )

		local binaryRequester = setup( rxTest )
		
		local binaryRequestStream = binaryRequester:getBinaryRequestStream()
		
		local listener = rxTest:observe( binaryRequestStream:map( mapKSDataToString ) )
		
		local assetId = KSIdGenerator.uuidInLowercaseHex()
		
		binaryRequester:requestBinary( assetId,
									'proxy',
									proxySize,
									1 )

		local expectedLinkKey = string.format( 'proxy_%s', proxySize )

		listener:expectDataWithinTime( 1, expectedOzLinkPaths[ expectedLinkKey ] .. assetId )

	end

end

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

local MockBinaryDownloaderToFile = {
	WFCloseSupport.addCloseSupportMethods,	
}

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

local function fakeDownloadToPathStream( destPath )

	return Observable.timer( 0.1, WFRxScheduler.sharedScheduler() )
			:take( 1 )
			:flatMapLatest( function()

				if destPath == "throwError" then

					return Observable.throw( "ERROR" )

				end

				return Observable.just( destPath )

			end )

end

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

MockBinaryDownloaderToFile.downloadProxyToLocalFileStream = 
function( _, _, _, _, destPath )

	return fakeDownloadToPathStream( destPath )

end

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

MockBinaryDownloaderToFile.downloadOriginalToFileStream =
function( _, _, _, destPath )

	return fakeDownloadToPathStream( destPath )

end

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

MockBinaryDownloaderToFile.downloadRenditionToLocalFileStream =
function( _, _, _, _, destPath )

	return fakeDownloadToPathStream( destPath )

end

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

MockBinaryDownloaderToFile = Rx.Class 'MockBinaryDownloaderToFile'( MockBinaryDownloaderToFile )

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

local function setupRequesterWithMockDownloadToFile( rxTest )

	local mockDownloaderFile = MockBinaryDownloaderToFile()

	rxTest:addCloseTarget( mockDownloaderFile )
	
	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local rootPath = KSPathUtils.getStandardFilePath( 'temp' )
	
	local binaryRequester = WFBinaryRequester( mockDownloaderFile )

	rxTest:addCloseTarget( binaryRequester )

	rxTest:finally( 
		function()
	
			local catalogPath = rootPath .. '/' .. catalogId

			WFFileUtils.delete( catalogPath )
			
		end )

	return binaryRequester

end

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

local downloadToFileTypePairs =
	WFFunctionUtils.map( 
		function( proxySize )
			return { 'proxy', proxySize } 
		end,
		proxySizes )

downloadToFileTypePairs[ #downloadToFileTypePairs + 1 ] = { 'original', 'master' }

downloadToFileTypePairs[ #downloadToFileTypePairs + 1 ] = { 'rendition', 'thumbnail2x' }

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

for _, fileTypePair in ipairs( downloadToFileTypePairs ) do

	-- For each proxy size, original, and thumbnail2x rendition: 
	-- 		Test 1 mocks binary requester download to file success 
	-- 		Test 2 mocks binary requester download to file failure
	--
	-- For the proxy and orig download tests: 
	-- 		Test 1 should result in a local file path
	-- 		Test 2 should result in an error
	--
	-- For thumbnail2x, since we do not yet support rendition download to file:
	-- 		Test 1 should result in an error
	-- 		Test 2 should result in an error

	for _, filePath in ipairs( { "fakePath", "throwError" } ) do

		local expectedOzBinaryType, expectedOzBinarySizeName = unpack( fileTypePair )

		local testName = string.format( "rxTestDownloadToFile_%s_%s_%s",
											expectedOzBinaryType, expectedOzBinarySizeName,
											filePath )
											
		WFBinaryRequesterTest[ testName ] = 
			function( rxTest )

				rxTest:setTimeLimit( 10 )

				local binaryRequester = setupRequesterWithMockDownloadToFile( rxTest )

				local sawBinaryRequestStreamResult = false

				local mostRecentDidSucceed, mostRecentPath,
						mosRecentOzAssetId, mostRecentOzBinaryType,
						mostRecentOzBinarySizeName

				local binaryRequestStreamSubscr = 
						binaryRequester
							:getBinaryRequestStream()
							:subscribe(
								function()

									sawBinaryRequestStreamResult = true

								end )

				local listener = rxTest:observe( 
						binaryRequester
							:getBinaryRequestToFileStream()
							:map(
								function( okay, maybePath, ozAssetId, ozBinaryType, ozBinarySizeName )

									rxTest:logf( "Received binary request to file stream result: %s, %s", okay, maybePath )

									mostRecentDidSucceed = okay

									mostRecentPath = maybePath

									mosRecentOzAssetId = ozAssetId

									mostRecentOzBinaryType = ozBinaryType

									mostRecentOzBinarySizeName = ozBinarySizeName

									return okay

								end ) )

				rxTest:logf( "Beginning download to path %s for %s %s", filePath, expectedOzBinaryType, expectedOzBinarySizeName )

				binaryRequester
					:maybeRequestBinaryAtUrlToFile(
						filePath,
						KSIdGenerator.uuidInLowercaseHex(),
						expectedOzBinaryType,
						expectedOzBinarySizeName,
						nil,
						1 )

				local expectedSuccess =
						filePath ~= "throwError" and 
						( expectedOzBinaryType == "original" or expectedOzBinaryType == "proxy" )

				listener:expectDataWithinTime( 1, expectedSuccess )

				rxTest:logf( "Checking download to path %s for %s %s", filePath, expectedOzBinaryType, expectedOzBinarySizeName )

				rxTest:expectEqual( mostRecentDidSucceed, expectedSuccess )

				if expectedSuccess then

					rxTest:expectEqual( filePath, mostRecentPath )

					rxTest:expectEqual( true, mosRecentOzAssetId ~= nil )

					rxTest:expectEqual( expectedOzBinaryType, mostRecentOzBinaryType )

					rxTest:expectEqual( expectedOzBinarySizeName, mostRecentOzBinarySizeName )

				end

				rxTest:expectEqual( sawBinaryRequestStreamResult, false )

				binaryRequestStreamSubscr:dispose()

			end

	end

end

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

return WFRxTestCase.wrapSuite( 'WFBinaryRequesterTest', WFBinaryRequesterTest )
