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

WFRxHttpClientTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2011 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 WFWorkerThread = require 'WFWorkerThread'

	-- WF.core.os
local KSDate = require 'KSDate'
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFFileUtils = require 'WFFileUtils'

	-- WF.core.rxlua
local WFRxPromiseBridge = require 'WFRxPromiseBridge'

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

	-- WF.core.utils
local WFProgressReporter = require 'WFProgressReporter'

	-- WF.web.http_client
local WFRxHttpClient = require 'WFRxHttpClient'
local WFHttpClientHeaderParser = require 'WFHttpClientHeaderParser'

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

local asKSPromise = WFRxPromiseBridge

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

-- Infer how far we are from the server by examing a local time.

local localTimezone = KSDate.formatDateTimeAsLocal( 0 )
local multiplier = 1

if localTimezone == "2000-12-31T18:00:00" then
	multiplier = 2
end

local function getTimeout( nearbyTimeout )
	return nearbyTimeout * multiplier
end

local serverAddr = "http://kibler.corp.adobe.com:8888"

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

local function progressEntry( description, errorCount, percentComplete, processedCount, totalCount )

	return {
		description = description,
		errorCount = errorCount,
		percentComplete = percentComplete,
		processedCount = processedCount,
		totalCount = totalCount
	}
	
end

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

local WFRxHttpClientTest = {}

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

local sCoveragePath

function WFRxHttpClientTest.setUp( coveragePath )
	sCoveragePath = coveragePath
end

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

local function teardown( rxTest )

	if sCoveragePath then

		rxTest:finally( function()
		
			local WFWorkerThread = require "WFWorkerThread"
		
			local HttpClientEP = WFWorkerThread( "WFRxHttpClientImp.lua" )
		
			return HttpClientEP:request( 'DEBUG_dumpCoverageP', sCoveragePath )
			
				:tapOnCompleted( function()
				
					HttpClientEP:close()
				
				end )
		
		end )

	end

end

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

local function WFRxHttpClient_get( serverAddr, options, ... )

	if options.notifyWhenHttpRequestIsInProgress == nil then
		options.notifyWhenHttpRequestIsInProgress = true
	end
	
	local requestInProgress = false

	local function uploadHasStartedHandler( messageType )
		
		if not requestInProgress then
		
			assert( messageType == 'requestInProgress', "Was not notified that the get request was in progress." )
		
			requestInProgress = true
			
		end
	
	end

	return WFRxHttpClient.get( serverAddr, options, ... )
	
		:tap( uploadHasStartedHandler )
	
		:skip( 1 )

end

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

local function WFRxHttpClient_put( url, data, options, ... )

	if options.notifyWhenHttpRequestIsInProgress == nil then
		options.notifyWhenHttpRequestIsInProgress = true
	end

	local requestInProgress = false

	local function uploadHasStartedHandler( messageType )
		
		if not requestInProgress then
		
			assert( messageType == 'requestInProgress', "Was not notified that the put request was in progress." )
		
			requestInProgress = true
			
		end
	
	end

	return WFRxHttpClient.put( url, data, options, ... )
	
		:tap( uploadHasStartedHandler )
	
		:skip( 1 )

end

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

function WFRxHttpClientTest.rxTestGetHead( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get( serverAddr,
			{
				requestMethod = "HEAD",
				returnHttpErrorsAsData = true,
				timeout = getTimeout( 10 ),
			} )
		:map( function( data, headers )
			local dataIsNil = type(data) == 'nil'
			local headerIsTable = type(headers) == 'table'
			local statusIs200 = headers.status_code == '200'
			return dataIsNil, headerIsTable, statusIs200
		end ),

		true, true, true

	)

	teardown( rxTest )

end

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

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

function WFRxHttpClientTest.rxTestGetHead_404( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get( 'https://photos-stage.adobe.io',
			{
				requestMethod = "HEAD",
				returnHttpErrorsAsData = true,
				timeout = getTimeout( 10 ),
			} )
		:map( function( data, headers )
			local dataIsNil = type(data) == 'nil'
			local headerIsTable = type(headers) == 'table'
			local statusIs404 = headers.status_code == '404'
			return dataIsNil, headerIsTable, statusIs404
		end ),

		true, true, true

	)

	teardown( rxTest )

end
--------------------------------------------------------------------------------

function WFRxHttpClientTest.rxTestGet( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( type(headers), 'table' )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGet_404( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get( 'https://photos-stage.adobe.io',
			{
				requestMethod = "GET",
				returnHttpErrorsAsData = true,
				timeout = getTimeout( 10 ),
			} )
		:map( function( data, headers )
			local dataIsNil = type(data) == 'userdata'
			local headerIsTable = type(headers) == 'table'
			local statusIs404 = headers.status_code == '404'
			return dataIsNil, headerIsTable, statusIs404
		end ),

		true, true, true
	)
	
	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetWithProgressReporter( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	local progressReporter = WFProgressReporter("Downloading image")

	local progressMessages = {}

	local disposable 
	
	local disposeFn = function()
		
		disposable:dispose()
		disposable = nil
		
	end
	
	disposable = progressReporter:status():subscribe( function( status )

		progressMessages[ #progressMessages + 1 ] = status

		if next( status ) then

			local msg = string.format( "%s %s of %s", status.description, status.processedCount, status.totalCount )
			
			if status.errorCount > 0 then
				msg = string.format( "%s retry %s", msg, status.errorCount )
			end

			debug.print( msg )
			
			-- All requests should have a start time and a time stamp
			
			local startTime = KSTableUtils.derefOpt( status, 'extraInfo', 'startTime' )
			rxTest:expectEqual( true, not not startTime )
			
			local timestamp = KSTableUtils.derefOpt( status, 'extraInfo', 'timestamp' )
			rxTest:expectEqual( true, not not timestamp )

		end

	end,
	disposeFn,
	disposeFn )

	rxTest:expectSingleValue(

-- This URL is better for debugging: 
-- 'https://helpx.adobe.com/content/dam/help/en/photoshop/how-to/photoshop-merge-to-hdr_1408x792.jpg',

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
			},
			nil,
			progressReporter )
			
		:tap( function( data, headers )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	rxTest:expectEqual( true, #progressMessages >= 2 )

	rxTest:expectEqual( 0, progressMessages[ 1 ].percentComplete )
	
	rxTest:expectEqual( true, progressMessages[ 1 ].totalCount > 0 )
		-- first progress message from WFRXHttpClient has size of transfer,
		-- with nothing received yet.

	local lastProgressEvent = progressMessages[ #progressMessages ]

	rxTest:expectEqual( lastProgressEvent.totalCount, lastProgressEvent.processedCount )
	rxTest:expectEqual( 1, lastProgressEvent.percentComplete )
		-- last message should be 100% complete
	
	local endTime = KSTableUtils.derefOpt( lastProgressEvent, 'extraInfo', 'endTime' )
	rxTest:expectEqual( true, not not endTime )
		-- The last request should an end time.
		
	teardown( rxTest )

end

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


local function mapAwayProgressTimings( originalProgressEvent )

	local progressEvent = KSTableUtils.deepCopy( originalProgressEvent )

	progressEvent.extraInfo = nil

	return progressEvent

end

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

function WFRxHttpClientTest.rxTestGetWithIndeterminentProgressReporter( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	local title = "Downloading image"
	local progressReporter = WFProgressReporter(title)

	local progress = rxTest:observe( progressReporter:status():map( mapAwayProgressTimings ) )

	rxTest:waitFor(
		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
				forceIndeterminentProgress = true,
			},
			nil,
			progressReporter )

		:tap( function( data, headers )
			rxTest:expectEqual( "200", headers.status_code )
		end ) )

	progress:expectData( progressEntry( title, 0, 0, 0, -1 ) )
	progress:expectData( progressEntry( title, 0, 0, 16, -1 ) )
	progress:expectCompletedWithinTime( 1 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestRetriesOnErrorWithProgressReporter( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:makeVerbose()
		-- This test had a strange failure that was not reproducable. Marking as
		-- verbose so that if it happens again, we'll have better log data.
		
	local title = "Downloading image"
	local progressReporter = WFProgressReporter(title)

	local disposable 
	
	local disposeFn = function()
		
		disposable:dispose()
		disposable = nil
		
	end
	
	disposable = progressReporter:status():subscribe( function(status)

		if next( status ) then

			local msg = string.format("%s %s of %s", status.description, status.processedCount, status.totalCount)
			if status.errorCount > 0 then
				msg = string.format("%s retry %s", msg, status.errorCount)
			end

			debug.print( msg )

		end

	end,
	disposeFn,
	disposeFn )

	local progress = rxTest:observe( progressReporter:status():map( mapAwayProgressTimings ) )
	local lastStatusCode
	
	local observer = rxTest:observe(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txtx', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
				retryDelay = 0.1, -- set small to make test quicker
				maxAttempts = 3,
			},
			nil,
			progressReporter )
			
		:skip( 1 ) -- Skip the in-progress event.
			
		:tap( nil, function( data, headers )
			lastStatusCode = headers.status_code
		end )

	)

	observer:expectErrorWithinTime( 10, ':ignore:', ':ignore:', ':ignore:' )

	progress:expectData( progressEntry( title, 0, 0, 0, 221 ) )
	progress:expectData( progressEntry( title, 0, 1, 221, 221 ) )
	progress:expectData( progressEntry( title, 1, 1, 221, 221 ) )
	progress:expectData( progressEntry( title, 1, 0, 0, 221 ) )
	progress:expectData( progressEntry( title, 1, 1, 221, 221 ) )
	progress:expectData( progressEntry( title, 2, 1, 221, 221 ) )
	progress:expectData( progressEntry( title, 2, 0, 0, 221 ) )
	progress:expectData( progressEntry( title, 2, 1, 221, 221 ) )
	progress:expectData( progressEntry( title, 3, 1, 221, 221 ) )
	progress:expectCompleted()

	rxTest:expectEqual( '404', lastStatusCode )
	
	teardown( rxTest )
	
end

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

function WFRxHttpClientTest.rxTestPutWithInvalidHeaderInfo( rxTest )

	rxTest:setTimeLimit( getTimeout( 20 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_put(
			string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
			"Post Data",
			{
				requestMethod = "POST",
				timeout = getTimeout( 10 ),
				requestHeaders = {
					{ header = { invalid=true }, value = "invalid key, should log bug not fail", },
					{ header = "Header Has Invalid Value Will Log But Won't Fail", value = { invalid=true }, },
					{ header = "X-Unit-Test-Ignore-This", value = "ignore", },
				},
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "<p>Thanks. You said: Post Data</p>", tostring(data) )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

-- Re-enable when AgHttpTimeoutTest.post.cgi exists
function WFRxHttpClientTest.DISABLED_rxTestPutWithTimeout( rxTest )

	rxTest:setTimeLimit( getTimeout( 30 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_put(
			string.format( '%s/AgHttpTimeoutTest.post.cgi?timeout=180', serverAddr ),
			"Post Data",
			{
				requestMethod = "POST",
				timeout = getTimeout( 10 ), -- deliberately short timeout
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "<p>Thanks. You said: Post Data</p>", tostring(data) )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

local function testPut( rxTest, putData, offset, length, dataFormat, forceLegacy )

	rxTest:setTimeLimit( getTimeout( 20 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_put(
			string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
			putData,
			{
				requestMethod = "POST",
				timeout = getTimeout( 10 ),
				offset = offset,
				length = length,
				dataFormat = dataFormat,
				forceLegacyFileAndOffset = forceLegacy,
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "<p>Thanks. You said: Post Data</p>", tostring(data) )
		end ),

		':ignore:', ':ignore:'

	)

end

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

function WFRxHttpClientTest.rxTestPutWithProgressReporter( rxTest )

	rxTest:setTimeLimit( getTimeout( 20 ) )

	local putData = '0123456789abcdef'
	for x = 5, 17 do -- 2^17 = 128k
		putData = putData .. putData
	end

	local progressReporter = WFProgressReporter("Uploading image")

	local progressMessages = {}

	local disposable 
	
	local disposeFn = function()
		
		disposable:dispose()
		disposable = nil
		
	end
	
	disposable = progressReporter:status():subscribe( function( status )

		progressMessages[ #progressMessages + 1 ] = status

		if next( status ) then

			local msg = string.format("%s %s of %s", status.description, status.processedCount, status.totalCount)
			if status.errorCount > 0 then
				msg = string.format("%s retry %s", msg, status.errorCount)
			end

			debug.print( msg )
			
			-- All requests should have a start time.
			
			local startTime = KSTableUtils.derefOpt( status, 'extraInfo', 'startTime' )
			rxTest:expectEqual( true, not not startTime )
			
			local timestamp = KSTableUtils.derefOpt( status, 'extraInfo', 'timestamp' )
			rxTest:expectEqual( true, not not timestamp )

		end

	end,
	disposeFn,
	disposeFn )

	rxTest:expectSingleValue(

		WFRxHttpClient_put(
			string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
			putData,
			{
				requestMethod = "POST",
				timeout = getTimeout( 10 ),
			},
			progressReporter
			)

		:takeLast( 1 )

		:tap( function( data, headers )
		
			rxTest:expectEqual( 'userdata', type( data ) )
			rxTest:expectEqual( true, #progressMessages >= 2 )
			rxTest:expectEqual( 0, progressMessages[ 1 ].percentComplete )
			rxTest:expectEqual( true, progressMessages[ 1 ].totalCount == #putData )
			rxTest:expectEqual( 1, progressMessages[ #progressMessages ].percentComplete )
			rxTest:expectEqual( "<p>Thanks. You said: " .. putData.. "</p>", tostring( data ) )
			
			local lastProgressEvent = progressMessages[ #progressMessages ]
			
			rxTest:expectEqual( lastProgressEvent.totalCount, lastProgressEvent.processedCount )
			rxTest:expectEqual( true, lastProgressEvent.totalCount == #putData )
			
			local endTime = KSTableUtils.derefOpt( lastProgressEvent, 'extraInfo', 'endTime' )
			rxTest:expectEqual( true, not not endTime )

		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutWithIndeterminentProgressReporter( rxTest )

	rxTest:setTimeLimit( getTimeout( 20 ) )

	local putData = '0123456789abcdef'

	local title = "Uploading image"
	local progressReporter = WFProgressReporter( title )

	local progress = rxTest:observe( progressReporter:status():map( mapAwayProgressTimings ) )
	
	rxTest:waitFor(
		WFRxHttpClient_put(
			string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
			putData,
			{
				requestMethod = "POST",
				timeout = getTimeout( 10 ),
				forceIndeterminentProgress = true,
			},
			progressReporter
			)
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "<p>Thanks. You said: " .. putData.. "</p>", tostring(data) )
		end ) )

	progress:expectData( progressEntry( title, 0, 0, 0, -1 ) )
	progress:expectData( progressEntry( title, 0, 0, #putData, -1 ) )
	progress:expectCompletedWithinTime( 1 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutString( rxTest )

	testPut( rxTest, "Post Data" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutKSData( rxTest )

	testPut( rxTest, KSData.make( "Post Data" ) )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutStringLengthOffset( rxTest )

	testPut( rxTest, "Twenty Ignored CharsPost DataThirty More Ignored Characters", 21, 9 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutStringLengthCappedWithOffset( rxTest )

	testPut( rxTest, "Twenty Ignored CharsPost Data", 21, 5000 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutKSDataLengthOffset( rxTest )

	testPut( rxTest, KSData.make( "Twenty Ignored CharsPost DataThirty More Ignored Characters" ), 21, 9 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutKSDataLengthCappedWithOffset( rxTest )

	testPut( rxTest, KSData.make( "Twenty Ignored CharsPost Data" ), 21, 5000 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutStringOffset( rxTest )

	testPut( rxTest, "Twenty Ignored CharsPost Data", 21 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutKSDataOffset( rxTest )

	testPut( rxTest, KSData.make( "Twenty Ignored CharsPost Data" ), 21 )

	teardown( rxTest )

end

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

local function testPutNoRetry( putData, offset, length, dataFormat, forceLegacy )

	return WFRxHttpClient_put(
		string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
		putData,
		{
			requestMethod = "POST",
			timeout = getTimeout( 10 ),
			offset = offset,
			length = length,
			dataFormat = dataFormat,
			forceLegacyFileAndOffset = forceLegacy,
			maxAttempts = 1,				
		} )

end

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

function WFRxHttpClientTest.rxTestPutStringWithInvalidOffset( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectOnlyError(

		testPutNoRetry("Twenty Ignored CharsPost Data", 5000 ),

		"Error, offset greater than length"

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutKSDataWithInvalidOffset( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectOnlyError(

		testPutNoRetry(KSData.make( "Twenty Ignored CharsPost Data" ), 5000 ),

		"Error, offset greater than length"

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutFileWithInvalidOffset( rxTest )

	rxTest:setTimeLimit( getTimeout( 20 ) )

    local data = "Twenty Ignored CharsPost Data"

	local testFile = KSPathUtils.child( KSPathUtils.getStandardFilePath( "temp" ),
										"WFHttpClientTest.testFilenamePutP.txt" )

	testFile = WFFileUtils.ensureFileNameIsUnique( testFile )

	local file = io.open( testFile, 'w' )
	file:write( data )
	file:close()

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


	rxTest:expectOnlyError(

		testPutNoRetry(testFile, 5000, nil, "filePath" ),

		"Error, offset greater than length"

	)

	rxTest:expectOnlyError(

		testPutNoRetry(testFile, 5000, nil, "filePath", true ),

		"Error, offset greater than length"

	)

	teardown( rxTest )

end

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

local function testFilenamePut( rxTest, data, offset, length, forceLegacy )

	local testFile = KSPathUtils.child( KSPathUtils.getStandardFilePath( "temp" ),
										"WFHttpClientTest.testFilenamePutP.txt" )

	testFile = WFFileUtils.ensureFileNameIsUnique( testFile )

	local file = io.open( testFile, 'w' )
	file:write( data )
	file:close()

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

	testPut( rxTest, testFile, offset, length, "filePath", forceLegacy )

end

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

function WFRxHttpClientTest.rxTestPutFilename( rxTest )

	testFilenamePut( rxTest, "Post Data" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutFilenameLengthOffset( rxTest )

	testFilenamePut( rxTest, "Twenty Ignored CharsPost DataThirty More Ignored Characters", 21, 9 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutLegacyFilenameLengthOffset( rxTest )

	testFilenamePut( rxTest, "Twenty Ignored CharsPost DataThirty More Ignored Characters", 21, 9, true )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutFilenameLengthCappedOffset( rxTest )

	testFilenamePut( rxTest, "Twenty Ignored CharsPost Data", 21, 5000 )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutLegacyFilenameLengthCappedWithOffset( rxTest )

	testFilenamePut( rxTest, "Twenty Ignored CharsPost Data", 21, 5000, true )

	teardown( rxTest )

end

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

local function testPutWithInvalidFile( rxTest, forceLegacy )

	rxTest:setTimeLimit( getTimeout( 20 ) )

	local missingFile = "WFHttpClientTest.missingFile.txt"

	local testFile = KSPathUtils.child( KSPathUtils.getStandardFilePath( "temp" ),
										missingFile )

	rxTest:expectOnlyError(

		WFRxHttpClient_put(
			string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
			testFile,
			{
				requestMethod = "POST",
				dataFormat = "filePath",
				timeout = getTimeout( 10 ),
				maxAttempts = 1,				
				forceLegacyFileAndOffset = forceLegacy,
			} ),

		'fileio', ':ignore:'

	)

end

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

function WFRxHttpClientTest.rxTestPutWithInvalidFileNative( rxTest )

	testPutWithInvalidFile( rxTest )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestPutWithInvalidFileLegacy( rxTest )

	testPutWithInvalidFile( rxTest, true )
	
	teardown( rxTest )
	
end

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

function WFRxHttpClientTest.rxTestGetConverts404ToError( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectOnlyError(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txtx', serverAddr ),
			{
				timeout = getTimeout( 10 ),
				maxAttempts = 1,
			}
		)
		:tap( nil, function( data, headers )
			rxTest:expectEqual( 'userdata', type(data) )
			rxTest:expectEqual( 'table', type(headers) )
			rxTest:expectEqual( "404", headers.status_code )
		end ),

		':ignore:', ':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestRetriesOnError( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectOnlyError(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txtx', serverAddr ),
			{
				timeout = getTimeout( 10 ),
				maxAttempts = 3,
				retryDelay = 0.1, -- set small to make test quicker
				notifyWhenHttpRequestIsInProgress = false,
			}
		)
		:tap( nil, function( data, headers )
			rxTest:expectEqual( 'userdata', type(data) )
			rxTest:expectEqual( 'table', type(headers) )
			rxTest:expectEqual( "404", headers.status_code )
			rxTest:expectEqual( 2, headers.retryCount )
		end ),

		':ignore:', ':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetDisableErrorConversion( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txtx', serverAddr ),
			{
				timeout = getTimeout( 10 ),
				maxAttempts = 1,
				returnHttpErrorsAsData = true,
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( 'table', type(headers) )
			rxTest:expectEqual( "404", headers.status_code )
		end ),

		':ignore:', ':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetWithRequestHeader( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				timeout = getTimeout( 10 ),
				requestHeaders = {
					{ header = 'Accept', value = 'application/json' },
				}
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( 'table', type(headers) )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetWithUserAgentInHeader( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	local customUserAgent = 'wf_xxx_self_tests(1.0.0.Dev;xxx;xxx;xxx;10.10.)'

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				timeout = getTimeout( 10 ),
				requestHeaders = {
					{ header = 'User-Agent', value = customUserAgent },
				}
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( 'table', type(headers) )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetWithReadResponseHeaders( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				timeout = getTimeout( 10 ),
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( 'table', type(headers) )
			rxTest:expectEqual( "200", headers.status_code )
			rxTest:expectEqual( "text/plain", headers[ 'Content-Type' ] )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetWithInvalidURL( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			'Invalid you are, L.',
			{
				timeout = getTimeout( 10 ),
				maxAttempts = 1,
			} )
		:catch( function( errorMessage )
			return Observable.just("caught error")
				-- Currently there isn't a consistent error for an invalid URL
				-- across platforms.
		end ),

		"caught error"

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestSetProxyCredentials( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:finally( function()

		WFRxHttpClient.setProxyCredentials( nil, nil )

	end )

	rxTest:expectSingleValue(

		WFRxHttpClient.setProxyCredentials( "joe", "p" ),

		true )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( type(headers), 'table' )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestSetFollowRedirects( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
				followRedirects = false,
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( type(headers), 'table' )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestSetManuallyManageCookies( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
				manuallyManageCookies = true,
			} )
		:tap( function( data, headers )
			rxTest:expectEqual( 'userdata', type(data))
			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )
			rxTest:expectEqual( type(headers), 'table' )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestResponseMatchesContentType( rxTest )

	local f = WFRxHttpClient.responseMatchesContentType
	local k = "Content-Type"

	rxTest:expectEqual( true, f( { [ k ] = "video/quicktime" }, "video/quicktime" ) )
	rxTest:expectEqual( true, f( { [ k ] = "application/binary;params=params33" }, "application/binary" ) )
	rxTest:expectEqual( false, f( { [ k ] = "application/binaryJosh" }, "application/binary" ) )
	rxTest:expectEqual( false, f( { }, "application/binary" ) )
	rxTest:expectEqual( false, f( nil, "xapplication/binary" ) )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestResponseContentTypeIsJson( rxTest )

	local f = WFRxHttpClient.responseContentTypeIsJson
	local k = "Content-Type"

	rxTest:expectEqual( true, f( { [ k ] = "application/json" } ) )
	rxTest:expectEqual( true, f( { [ k ] = "application/json;params=params33" } ) )
	rxTest:expectEqual( false, f( { [ k ] = "application/jsonHaHaJustKiddingNoImNotJSON" } ) )
	rxTest:expectEqual( false, f( { } ) )
	rxTest:expectEqual( false, f( nil ) )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestHeaderParser( rxTest )

	local parseHeaderString = WFHttpClientHeaderParser.parseHeaderString

	local headers = parseHeaderString(
		"Header-Name: Header value\r\nHeader2-Name:Header 2 Value\r\nHeader3-Name:		Header 3\r\n gots value\r\nSet-Cookie: cookie1=One;cookie2=Two\r\nSet-Cookie: cookie3=Three\r\n\r\n"
	)

	rxTest:expectEqual( "Header value", headers[ "Header-Name" ] )
		-- Basic space separated one-line header value

	rxTest:expectEqual( "Header 2 Value", headers[ "Header2-Name" ] )
		--  Basic no-whitespace separated one-line header value

	rxTest:expectEqual( "Header 3 gots value", headers[ "Header3-Name" ] )
		--  Multiple whitespaces, including tabs, with a multi-line header value

	rxTest:expectEqual( {
			"cookie1=One;cookie2=Two",
			"cookie3=Three"
		},
		headers[ "Set-Cookie" ] )
		--  handles multiple Set-Cookie header values

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestWindowsStyleStandardizeHeaders( rxTest )

	local windowsStyleRawHeaderString = [[
HTTP/1.1 404 Not Found
Date: Wed, 02 Sep 2015 21:58:47 GMT
Server: Apache/2.2.22 (Ubuntu)
Vary: Accept-Encoding
Content-Type: text/html; charset=iso-8859-1
Content-Length: 310
Age: 81
Via: 1.1 wsg.sanjose09
]]

	local kRawHeaderKey = "< raw : headers >"
	local rawHeaders = {}
	rawHeaders[ kRawHeaderKey ] = windowsStyleRawHeaderString

	local standardizeHeaders = WFHttpClientHeaderParser.standardizeHeaders

	local headers = standardizeHeaders( rawHeaders )

	rxTest:expectEqual( 'table', type( headers ) )
	rxTest:expectEqual( "Wed, 02 Sep 2015 21:58:47 GMT", headers.Date )
	rxTest:expectEqual( "Apache/2.2.22 (Ubuntu)", headers.Server )
	rxTest:expectEqual( "Accept-Encoding", headers.Vary )
	rxTest:expectEqual( "text/html; charset=iso-8859-1", headers["Content-Type"] )
	rxTest:expectEqual( "310", headers["Content-Length"] )
	rxTest:expectEqual( "81", headers.Age )
	rxTest:expectEqual( "1.1 wsg.sanjose09", headers.Via )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestWindowsInjected429Header( rxTest )

	local injected429HeaderString = "HTTP/1.1 429 Too Many Requests\nRetry-After: 30"

	local kRawHeaderKey = "< raw : headers >"
	local rawHeaders = {}
	rawHeaders[ kRawHeaderKey ] = injected429HeaderString

	local standardizeHeaders = WFHttpClientHeaderParser.standardizeHeaders

	local headers = standardizeHeaders( rawHeaders )

	rxTest:expectEqual( {
		[ "Retry-After" ] = "30",
	}, headers )

end

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

function WFRxHttpClientTest.rxTestAppNumber( rxTest )

	local httpClientImp = WFWorkerThread( 'WFRxHttpClientImp.lua' )

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

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_normalizeAppNumber', "" ),
		"0.0." )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_normalizeAppNumber', "Dev" ),
		"0.0.Dev" )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_normalizeAppNumber', "1" ),
		"1.0.0" )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_normalizeAppNumber', "1.Dev" ),
		"1.0.0.Dev" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestCompletionHandlerDataWithHeaderAndProgress( rxTest )

	local httpClientImp = WFWorkerThread( 'WFRxHttpClientImp.lua' )

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

	local mockHttpGetOrPut = rxTest:observe( httpClientImp:request( 'DEBUG_completionHandler', true ) )
	
	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'header', { status_code = '200' } ),
		true )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'progress', 0, 300 ),
		true )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'progress', 300, 300 ),
		true )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', 'testing' ),
		true )

	mockHttpGetOrPut:expectData( 'progress', 0, 300 )
	mockHttpGetOrPut:expectData( 'progress', 300, 300 )
	mockHttpGetOrPut:expectData( 'result', 'data', 'testing',  { status_code = '200' } )
	mockHttpGetOrPut:expectCompleted()

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', "testing" ),
		"Completion handler got called for a request that we don't recognize" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestCompletionHandlerDataNoHeader( rxTest )

	local httpClientImp = WFWorkerThread( 'WFRxHttpClientImp.lua' )

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

	local mockHttpGetOrPut = rxTest:observe( httpClientImp:request( 'DEBUG_completionHandler', true ) )
	
	-- don't send headers so we hit another log line

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', 'testing' ),
		true )

	mockHttpGetOrPut:expectData( 'result', 'error', 'testing', false )
	mockHttpGetOrPut:expectCompleted()

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', "moredata" ),
		"Completion handler got called for a request that we don't recognize" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestCompletionHandlerDataHeaderError500( rxTest )

	local httpClientImp = WFWorkerThread( 'WFRxHttpClientImp.lua' )

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

	local mockHttpGetOrPut = rxTest:observe( httpClientImp:request( 'DEBUG_completionHandler', true ) )
	
	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'header', { status_code = '500' } ),
		true )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', 'testing' ),
		true )

	mockHttpGetOrPut:expectData( 'result', 'error', 'testing', { status_code = '500' } )
	mockHttpGetOrPut:expectCompleted()

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', "moredata" ),
		"Completion handler got called for a request that we don't recognize" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestCompletionHandlerError( rxTest )

	local httpClientImp = WFWorkerThread( 'WFRxHttpClientImp.lua' )

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

	local mockHttpGetOrPut = rxTest:observe( httpClientImp:request( 'DEBUG_completionHandler', true ) )
	
	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'error', 'something bad happened' ),
		true )

	mockHttpGetOrPut:expectData( 'result', 'error', 'something bad happened', nil, nil )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', "testing" ),
		"Completion handler got called for a request that we don't recognize" )

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestCompletionHandlerBogus( rxTest )

	local httpClientImp = WFWorkerThread( 'WFRxHttpClientImp.lua' )

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

	local mockHttpGetOrPut = rxTest:observe( httpClientImp:request( 'DEBUG_completionHandler', true ) )
	
	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'bogus', "testing" ),
		"Bogus status from native code: bogus" )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'header', { status_code = '200' } ),
		true )

	rxTest:expectSingleValue(
		httpClientImp:request( 'DEBUG_completionHandler', false, 'data', 'testing' ),
		true )

	mockHttpGetOrPut:expectData( 'result', 'data', 'testing',  { status_code = '200' } )
	mockHttpGetOrPut:expectCompleted()

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestRegisterCompletionHandler( rxTest )

	local WFHttpClientImp_native = require 'WFHttpClientImp_native'
	
	local function completionHandler( request, status, data, optData )
		error("Shouldn't get here")
    end
	
	local success = pcall(WFHttpClientImp_native.registerCompletionHandler, completionHandler )

	assert(success)

	success = pcall(WFHttpClientImp_native.registerCompletionHandler, completionHandler )
	
	assert(not success)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.testDiagnosticLog_testNightly( test )

	local reportP

	return WFAsyncTestCase( test, 45 )

	:waitFor( function()
	
		return WFRxHttpClient_get(
			string.format( '%s/http-get-test-data.txt', serverAddr ),
			{
				requestMethod = "GET",
				timeout = getTimeout( 10 ),
			} )
		:call( asKSPromise )

	end )
	:continueWhenValueMatches( 'data', ':ignore:', ':ignore:' )

	:waitFor( function()
	
		return WFRxHttpClient_put(
			string.format( '%s/AgHttpClientTest3.post.cgi', serverAddr ),
			"Post Data",
			{
				requestMethod = "POST",
				timeout = getTimeout( 10 ),
			} )

		:call( asKSPromise )

	end )
	:continueWhenValueMatches( 'data', ':ignore:', ':ignore:' )

	:waitFor( function()

		local WFDiagnosticLog = require 'WFDiagnosticLog'

		local progressB
		reportP, progressB = WFDiagnosticLog.generateHtmlReportP {
		}

		return progressB

	end )

	:continueWhenValueMatches( 1 )
	:waitFor( function()

		return reportP
		:mapDataP_new( function( report )
			return not not ( string.find( report, "totalDownloadTime" ) )
		end )

	end )

	:continueWhenValueMatches( 'data', true )

end

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

function WFRxHttpClientTest.rxTestWatchHttpErrors_WF_1294( rxTest )

	-- The following code demonstrates a bug in WFRxHttpClient.
	-- When WFRxHttpClient.watchHttpErrors, a ReplaySubject is initialized.

	rxTest:setTimeLimit( 30 )

	local observer = rxTest:observe( WFRxHttpClient.watchHttpErrors()
	
		:map( function( ksDataOrString, headers, errorData )

			local ok = pcall( tostring, ksDataOrString )

			return ok

		end ) )

	local addr = "http://kibler.corp.adobe.com:8189/"
	
	-- We then issue a get request to a custom server that returns a 404,
	-- which causes the underlying HTTP completion handler to issue an
	-- onNext w/ a closed KSData object.
	
	rxTest:expectOnlyError( WFRxHttpClient_get( addr, {} ), ':ignore:', ':ignore:',':ignore:' )

	observer:expectDataWithinTime( 3, true )
	
	observer:dispose()
	
	-- This bug has been fixed, which is why the test now passes.

end

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

return WFRxTestCase.wrapSuite( WFRxHttpClientTest )
