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

WFDiagnosticLogTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2012 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.diagnostic_log
local WFDiagnosticLog = require 'WFDiagnosticLog'

	-- WF.core.lua.threading
local WFTimer = require 'WFTimer'

	-- WF.core.reactive
local KSReactive = require 'KSReactive'

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

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

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


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

local function match( a, b )

	for k, v in pairs( a ) do

		local other = b[ k ]

		if type( v ) == 'table' then
			assert( type( other ) == 'table', "Values are not both table types" )
			match( v, other )
		else
			assert( b[ k ] == v, "Values in a, b must match for key: " .. tostring( k ) )
		end

	end

end


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

local WFDiagnosticLogTest = {}

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

function WFDiagnosticLogTest.testIsSimpleValue()

	assert( WFDiagnosticLog.isSimpleValue( "string" ), "string is simple" )
	assert( WFDiagnosticLog.isSimpleValue( true ), "bool is simple" )
	assert( WFDiagnosticLog.isSimpleValue( 42 ), "number is simple" )
	assert( not WFDiagnosticLog.isSimpleValue( nil ), "nil is not simple" )
	assert( not WFDiagnosticLog.isSimpleValue( ), "nothing is not simple" )
	assert( not WFDiagnosticLog.isSimpleValue( {} ), "table is not simple" )

	local userdataFactory = KSUserdataProxy.makeFactory( "WFDiagnosticLogTest.isSimpleValue" )
	local userdata = userdataFactory()

	assert( not WFDiagnosticLog.isSimpleValue( userdata ), "userdata is not simple" )
	assert( not WFDiagnosticLog.isSimpleValue( function()end ), "function is not simple" )

	assert( not WFDiagnosticLog.isSimpleValue( coroutine.create( function()end ) ),
		"thread is not simple" )

end

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

function WFDiagnosticLogTest.testShallowCopyOfSimpleValues()

	local userdataFactory = KSUserdataProxy.makeFactory(
		"WFDiagnosticLogTest.testShallowCopyOfSimpleValues" )

	local userdata = userdataFactory()

	local mixtureOfInputs = {
		stringValue = "value",
		boolValue = true,
		numberValue = 42,
		[ true ] = "bool key",
		[ 42 ] = "number key",

		[ {} ] = "table key",
		tableValue = {},

		[ userdata ] = "userdata key",
		userdataValue = userdata,

		[ function() end ] = "function key",
		functionValue = function() end,

		[ coroutine.create( function() end ) ] = "thread key",
		threadValue = coroutine.create( function() end ),
	}

	local copyOfValues = WFDiagnosticLog.shallowCopyOfSimpleValues( mixtureOfInputs )

	local expected = {
		stringValue = "value",
		boolValue = true,
		numberValue = 42,
		[ true ] = "bool key",
		[ 42 ] = "number key",
	}

	match( copyOfValues, expected )
	match( expected, copyOfValues )

end

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

function WFDiagnosticLogTest.testRingBuffer()

	local ringBuffer = WFDiagnosticLog.makeRingBuffer {

		accumulate = true,

		mapFn = function( updates )

			local deletions, assets = 0, 0
			for i, update in ipairs( updates ) do
				if update.deleted then
					deletions = deletions + 1
				elseif update.asset then
					assets = assets + 1
				end
			end

			return #updates, deletions, assets

		end,

		"updates", "deletions", "assets"

	}

	local function makeFakeUpdates( other, deletions, assets )

		local result = {}

		for type, num in pairs { other = other, deleted = deletions, asset = assets } do
			for i = 1, num do
				result[ #result + 1 ] = { [ type ] = true }
			end
		end

		return result

	end

	ringBuffer:add( makeFakeUpdates( 1, 2, 3 ) )
	ringBuffer:add( makeFakeUpdates( 1, 2, 3 ) )

	local function expected( kind )

		kind = "< " .. kind .. " >"

		return {
			{
				title = 'updates',
				[ kind ] = {
					6, 12
				},
			},
			{
				title = 'deletions',
				[ kind ] = {
					2, 4
				},
			},
			{
				title = 'assets',
				[ kind ] = {
					3, 6
				},
			},
		}

	end

	match( expected "sparkline", { ringBuffer:visualize( 'sparkline' ) } )
	match( expected "box-and-whisker", { ringBuffer:visualize( 'boxAndWhisker' ) } )

end

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

function WFDiagnosticLogTest.testPIIRedaction( test )

	local redactedAdobeId = "ADOBEID"
	local redactedUsername = "USERNAME"
	local redactedAPIKey = "API_KEY=API_KEY"

	local fakeAdobeIdGUID = "A0B1C2D3E4F5G6H7I8J9K10L"
	local fakeAdobeId1 = fakeAdobeIdGUID .. "@adobe.com"
	local fakeAdobeId2 = fakeAdobeIdGUID .. "@AdobeID"
	
	local fakeAPIKey = "FakeAPIKey"
	local fakeAPIKeyHeader1 = "api_key=" .. fakeAPIKey
	local fakeAPIKeyHeader2 = "API_KEY=" .. fakeAPIKey
	local fakeAPIKeyHeader3 = "Api_Key=" .. fakeAPIKey
	
	local fakeUsername = "pixelPete1"
	
	local fakeSensitiveInformationTemplate = 
		"test/Users/%s/additionalPaths1^n" ..
		"test\\\\Users\\\\%s\\\\additionalPaths2,^n" ..
		"test\\Users\\%s\\additionalPaths3.^n" ..
		"https://fake.website1.io/V2/users/%s/url^n" ..
		"https://fake.website2.io/V2/users/%s^n" ..
		"https://fake.website3.io/V2/api/%s/url^n" ..
		"https://fake.website4.io/V2/api/%s^n" ..
		"https://fake.website5.io/V2/api/%s/"

	local fakeSensitiveInformation = string.format( fakeSensitiveInformationTemplate,
		fakeUsername, fakeUsername, fakeUsername,
		fakeAdobeId1, fakeAdobeId2,
		fakeAPIKeyHeader1, fakeAPIKeyHeader2, fakeAPIKeyHeader3 )
		
	local expectedRedactedInformation = string.format( fakeSensitiveInformationTemplate,
		redactedUsername, redactedUsername, redactedUsername,
		redactedAdobeId, redactedAdobeId,
		redactedAPIKey, redactedAPIKey, redactedAPIKey )
		
	local redactedSensitiveInformationP = WFDiagnosticLog.redactSensitiveInformationForUnitTesting( fakeSensitiveInformation )
	
	redactedSensitiveInformationP:whenResolvedDo( function( _, redactedSensitiveInformation )
		
		if redactedSensitiveInformation == expectedRedactedInformation then
			test:notifyTestDidSucceed()
		else
			test:notifyTestDidFail( "sensitive information was not redacted correctly:\n--------\n"..redactedSensitiveInformation.."\n-- ~= --\n"..expectedRedactedInformation.."\n-------" )
		end
		
	end ):runUntilDone()
	
	test:waitForTestToFinish( 5 )
	
end

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

function WFDiagnosticLogTest.testProcessLogSectionP_testNightly( test )

	WFAsyncTestCase( test, 60 )

		:waitFor( function()

			return WFDiagnosticLog.processLogSectionP {

				name = 'testing',
				id = 'testSectionId',
				weight = 100,

				body = {

					observableEmpty = Observable.empty(),
					observableJust = Observable.just( "just this" ),
					observableNever = Observable.never(),

					observableTimer = Observable.timer( 1, WFRxScheduler.sharedScheduler() )
						:map( function()
							return "timer fired"
						end ),

					observableError = Observable.throw( "expect this error" ),

					trueLaterB = KSReactive.laterB(),
					nilB = KSReactive.constantB( nil ),

					fooAfterOneSecondP = WFTimer.afterDelayP( 1 )
						:mapDataP( function()
							return 'data', 'foo'
						end ):runUntilDoneP(),

					scalar = 42,

				},

			}

		end )
		:continueWhenValueMatches( 'data',
			{

				name = 'testing',
				id = 'testSectionId',
				weight = 100,

				body = {
					trueLaterB = true,
					nilB = '(nil)',
					fooAfterOneSecondP = 'foo',
					observableEmpty = "(empty response)",
					observableError = "ERROR: expect this error",
					observableJust = "just this",
					observableNever = "(observable did not respond in time)",
					observableTimer = "timer fired",
					scalar = 42,
				},

			} )

end

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

return WFDiagnosticLogTest
