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

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
local KSJsonEncoder = require 'KSJsonEncoder'
local WFRxHttpClient = require 'WFRxHttpClient'
local WFHttpClientHeaderParser = require 'WFHttpClientHeaderParser'

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

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

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://sj1010120180138.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 mapAwayProgressTimings( originalProgressEvent )

	local progressEvent = KSTableUtils.deepCopy( originalProgressEvent )

	progressEvent.extraInfo = nil

	return progressEvent

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_WithOptFileName( rxTest )

	rxTest:setTimeLimit( getTimeout( 60 ) )

	local filePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	filePath = KSPathUtils.child( filePath, 'http-get-test-data.txt' )
	
	rxTest:finally( function()
		WFFileUtils.delete( filePath )
	end )
	
	rxTest:expectSingleValue(

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

			local data = KSData.memoryMappedContentsOfFile( dataOrFilename )

			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )

			-- WF-1972 need to close data here otherwise there would be an error
			-- when deleting filePath in windows
			data:close()

			rxTest:expectEqual( type(headers), 'table' )
			rxTest:expectEqual( "200", headers.status_code )
		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGet_WithOptFileName404( rxTest )

	rxTest:setTimeLimit( getTimeout( 15 ) )

	-- see https://git.corp.adobe.com/lua/wf/pull/4356

	local filePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	filePath = KSPathUtils.child( filePath, 'http-get-test-data.txt' )

	rxTest:expectSingleValue(

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

			return dataIsKSData, headerIsTable, statusIs404
		end ),

		true, true, true
	)

	rxTest:expectEqual( false, WFFileUtils.exists( filePath ) )

	rxTest:expectOnlyError( 		

		WFRxHttpClient_get( 'https://photos-stage.adobe.io',
			{
				requestMethod = "GET",
				filePath = filePath,
				timeout = getTimeout( 10 ),
			} )
			:catch( function( _, headers )
				return Rx.Observable.throw( headers.status_code )
			end ),
			'404' )

	rxTest:expectEqual( false, WFFileUtils.exists( filePath ) )

	teardown( rxTest )

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

function WFRxHttpClientTest.rxTestGet_WithOptFileName_chunked( rxTest )

	rxTest:setTimeLimit( getTimeout( 60 ) )

	local filePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	filePath = KSPathUtils.child( filePath, 'http-get-test-data.txt' )

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

	rxTest:expectSingleValue(

		WFRxHttpClient_get(
			string.format( '%s/chunked_response.php', serverAddr ),
			{
				requestMethod = "GET",
				filePath = filePath,
				timeout = getTimeout( 10 ),
			} )
		:tap( function( dataOrFilename, headers )

			rxTest:expectEqual( filePath, dataOrFilename )

			local data = KSData.memoryMappedContentsOfFile( dataOrFilename )

			rxTest:expectEqual( "Hello, Wichita!\n", tostring(data) )

			-- WF-1972 need to close data here otherwise there would be an error
			-- when deleting filePath in windows
			data:close()

			rxTest:expectEqual( type(headers), 'table' )
			rxTest:expectEqual( "200", headers.status_code )

			rxTest:expectEqual( nil, headers[ 'Content-Length' ] )

		end ),

		':ignore:', ':ignore:'

	)

	teardown( rxTest )

end

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

function WFRxHttpClientTest.rxTestGetWithOptFileNameWithIndeterminentProgressReporter( rxTest )

	rxTest:setTimeLimit( getTimeout( 60 ) )
	
	local filePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	filePath = KSPathUtils.child( filePath, 'http-get-test-data.txt' )
	rxTest:finally( function()
		WFFileUtils.delete( filePath )
	end )
	
	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.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

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

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:' )

	-- NOTE 2022-04-04 @sewillia: If there is an update on the sj1010120180138 (previously kibler) server of MAMP - the length of the error response _might_ change.
	-- In that case, curl a non-existent endpoint and update this constant with the new byte length
	local byteLengthOfKiblerErrorResponse = 196

	progress:expectData( progressEntry( title, 0, 0, 0, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 0, 1, byteLengthOfKiblerErrorResponse, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 1, 1, byteLengthOfKiblerErrorResponse, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 1, 0, 0, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 1, 1, byteLengthOfKiblerErrorResponse, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 2, 1, byteLengthOfKiblerErrorResponse, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 2, 0, 0, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 2, 1, byteLengthOfKiblerErrorResponse, byteLengthOfKiblerErrorResponse ) )
	progress:expectData( progressEntry( title, 3, 1, byteLengthOfKiblerErrorResponse, byteLengthOfKiblerErrorResponse ) )
	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"

	)

	rxTest:expectOnlyError(

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

		"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://sj1010120180138.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

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

function WFRxHttpClientTest.rxTestMultipartRequestAndResponse( rxTest )

	rxTest:setTimeLimit( getTimeout( 20 ) )

	local echoServer = "http://sj1010120180138.corp.adobe.com:8124"

	local requestOptions = {
		returnHttpErrorsAsData = true,
		multipartRequest = true, -- Critical
	}

	-- Form the multipart request body

	local helloPart = {
		hello = 'world'
	}

	local smilePart = {
		smile = 'everyone'
	}

	local multipartRequestData = {
		{
			name = 'hello',
			contentType = 'application/json',
			value = KSJsonEncoder.prettyPrint( helloPart, { pretty = true } )
		},
		{
			name = 'smile',
			contentType = 'application/json',
			value = KSJsonEncoder.prettyPrint( smilePart, { pretty = true } )
		}
	}

	-- Because we are issuing our requests to an echo server, we expect
	-- a multipart response.

	local expectedParts = {
		{
			data = helloPart,
			headers = {
				[ 'Content-Disposition' ] = 'form-data; name="hello"',
				[ 'Content-Type' ] = 'application/json'
			}
		},
		{
			data = smilePart,
			headers = {
				[ 'Content-Disposition' ] = 'form-data; name="smile"',
				[ 'Content-Type' ] = 'application/json'
			}
		}
	}

	rxTest:expectSingleValue(

		WFRxHttpClient_put(
			echoServer,
			multipartRequestData,
			requestOptions ), expectedParts, ':ignore:'

	)

	teardown( rxTest )

end

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

-- SPAM [jandrew 2023-08-28]: The following test was written to verify the multipart
-- implementation works against a known-multipart endpoint at Adobe. This test calls
-- out to the Sensei auto-tags service. By default, this test is disabled as it requires
-- a custom user token which can be obtained from: https://senseiui.cloud.adobe.io/

function WFRxHttpClientTest._DISABLED_rxTestGetSenseiDerivedMetadataForFile_testNightly( rxTest )

	rxTest:setTimeLimit( 20 )

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

	local fileName = "small.JPG"
	local filePath = KSPathUtils.child( testDataFolder1, fileName )

	local catalogTestSession = WFCatTestSession( rxTest )

	local wfCatalog = rxTest:getFirstValue( catalogTestSession:catalog() )

	if not filePath or not WFFileUtils.exists( filePath ) then
		return Observable.just( false )
	end

	local requestUrl = "https://senseicore.adobe.io/services/v2/predict/"

	local userToken = [[USER TOKEN GOES HERE]] -- THE TEST WILL FAIL UNLESS THIS IS SET
	
	local requestHeaders = {
		{
			header = 'X-Api-Key',
			value = 'AdobeSenseiPredictServiceProdKey'
		},
		{
			header = 'Prefer',
			value = 'respond-sync, wait=1'
		},
		{
			header = 'Cache-Control',
			value = 'no-cache'
		},
		{
			header = 'Authorization',
			value = string.format( "Bearer %s", userToken )
		}
	}

	local requestOptions = {
		requestMethod = 'POST',
		returnHttpErrorsAsData = true,
		requestHeaders = requestHeaders,
		withoutAdobeAuthorization = true,
		multipartRequest = true,
		requestCost = WFRxHttpClient.requestCostLow,
		requestPriority = WFRxHttpClient.requestPriorityLowest
	}

	local kSenseiService = 'Classification:autotags:Service-011d3ac6049246c9b54d2bcd456c1264'

	local contentAnalyzerRequests = {
		[ "sensei:invocation_mode" ] = "synchronous",
		[ "sensei:invocation_batch" ] = false,
		[ "sensei:name" ] = kSenseiService,
		[ "sensei:engines" ] = {
			{
				[ "sensei:execution_info" ] = {
					[ "sensei:engine" ] = kSenseiService
				},
				[ "sensei:params" ] = {
					["num_search_hits"] = 100,
					[ "service"] = "tagger" -- "category" works too for different results
				},
				[ "sensei:inputs"] = {
					["image_input"] = {
						["dc:format"] = "image/jpg",
						["sensei:multipart_field_name"] = "infile"
					}
				},
				[ "sensei:outputs"] = {
					["result"] = {
						["dc:format"] = "application/json",
						["sensei:multipart_field_name"] = "classifications"
					}
				}
			}
		}
	}

	local multipartRequestData = {
		{
			name = 'contentAnalyzerRequests',
			contentType = 'application/json',
			value = KSJsonEncoder.prettyPrint( contentAnalyzerRequests, { pretty = true } )
		},
		{
			name = 'infile',
			filePath = filePath,
			fileName = fileName
		}
	}

	-- The image we are classifying is a picture of the water, and the first generated
	-- auto-tag should be 'sea'. This may change as the ML model changes, but it's
	-- a fine check for now.

	rxTest:expectSingleValue( wfCatalog:ozCatalogSpec():put( requestUrl, multipartRequestData, requestOptions )

		:map( function( parts, headers )

			if headers and headers.status_code and
					tonumber( headers.status_code ) == 200 then

				local senseiEngine = KSTableUtils.derefOpt( parts, 1, 'data', 'statuses', 1, 'sensei:engine' ) 

				local tag = KSTableUtils.derefOpt(
						parts, 2, 'data', 'classifications', 1, 'classification_entries', 1, 'class_label' ) or {}

				return senseiEngine, tag

			end

		end ), kSenseiService, 'sea' )

end

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

return WFRxTestCase.wrapSuite( 'WFRxHttpClientTest', WFRxHttpClientTest )
