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

WFCatConflictResolverTest.lua

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

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

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

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

	-- WF.core.self_tests
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.sqlite.doc_store
local WFRxDocStore = require 'WFRxDocStore'

	-- WF.sqlite.index_store
local WFRxIndexStore = require 'WFRxIndexStore'

	-- WF.oz_client.managed_catalog
local WFCatConflictResolver = require 'WFCatConflictResolver'
local WFCatMockCatalog = require 'WFCatMockCatalog'

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

local WFCatConflictResolverTest = {}

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

local function rxTestThreeWayMerge( args )

	local rxTest = assert( args.rxTest )
	local rev1 = assert( args.rev1 )
	local rev2a = assert( args.rev2a )
	local rev2b = assert( args.rev2b )
	local resolution = assert( args.resolution )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue(

		docStore:setDocValue( rev1 )
			:tap( function( revId )
				firstRevId = revId
			end ),

		':ignore:' )

	-- Add another revision.

	local setRev2a = KSTableUtils.shallowcopy( rev2a )
	setRev2a._revId = firstRevId

	local secondRevId

	rxTest:expectSingleValue(

		docStore:setDocValue( setRev2a )
			:tap( function( revId )
				secondRevId = revId
			end ),

		':ignore:' )

	-- Add a conflicting revision derived from first.

	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	local setRev2b = KSTableUtils.shallowcopy( rev2b )
	setRev2b._revIds = { firstRevId, conflictingRevId }

	rxTest:expectSingleValue( docStore:setDocValue( setRev2b ), ':ignore:' )

	-- Confirm that both are present (and rev2a won).
	
	local expectedRev2a = KSTableUtils.shallowcopy( rev2a )
	expectedRev2a._changeCounter = 2
	expectedRev2a._localId = 1
	expectedRev2a._revId = secondRevId
	expectedRev2a._conflict = true
	expectedRev2a._labels = nil
	
	local expectedRev2b = KSTableUtils.shallowcopy( rev2b )
	expectedRev2b._changeCounter = 3
	expectedRev2b._conflict = true
	expectedRev2b._localId = 1
	expectedRev2b._revId = conflictingRevId
	expectedRev2b._labels = nil
	
	rxTest:expectSingleValue(

		docStore:currentRevisionsForDoc( rev1._id, true ),

		{
			expectedRev2a,
			expectedRev2b,
		} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )
	
	-- We should see the conflict be resolved shortly.

	local docValue = rxTest:observe(
		docStore:ongoingDocValue( rev1._id )
			:map( function( value )
				if value._revId == secondRevId then
					value._revId = 'rev2a'
				elseif value._revId == conflictingRevId then
					value._revId = 'rev2b'
				else
					value._revId = 'other'
				end
				return value
			end ) )
	
	docValue:expectDataWithinTime( 2, resolution )
	
	rxTest:expectOnlyCompleted(docStore:performIntegrityCheck() )
	
	return docStore
end

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

function WFCatConflictResolverTest.rxTestResolveNonConflictingKeys( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId',
			foo = 42,
			blah = 45,
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			bar = 'no conflict',
			_labels = {
				c = true
			}
		},

		-- If the changes are to different keys, we should just merge them together.

		resolution = {
			_changeCounter = 5, -- 4 is deleting the other rev, 5 is the new
			_id = 'testDocId',
			_localId = 1,
			_revId = 'other',
			blah = 45,
			bar = 'no conflict',
			-- Labels should be dropped in this case.
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveDeleteConflict( rxTest )
	
	rxTest:setTimeLimit( 5 )
	
	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId',
			foo = 42,
			bar = 43,
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			_deleted = true,
			foo = 42,
			_labels = {
				c = true
			}
		},

		-- the delete should win in the conflict and the doc should end up as deleted

		resolution = {
			_id = "testDocId",
			_revId = "other", -- this creates an additional rev because 2a was current
			_empty = true,
			-- No labels are expected because it's a conflict resolution of a delete
		},

	}
	
	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId2',
			foo = 42,
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId2',
			_deleted = true,
			foo = 42,
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId2',
			foo = 42,
			bar = 43,
			_labels = {
				c = true
			}
		},

		-- the delete should win in the conflict and the doc should end up as deleted

		resolution = {
			_id = "testDocId2",
			_revId = "other", -- a new rev is created because even though rev2a was current, it also had labels
			_empty = true,
			-- No labels are expected because it's a conflict resolution of a delete
		},

	}
	
end


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

function WFCatConflictResolverTest.rxTestResolveChangeSameKey( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId',
			foo = 45,
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			foo = 49,
			_labels = {
				c = true
			}
		},

		-- If the changes are to the same key, we should just pick one (the local one).

		resolution = {
			_changeCounter = 4,
			_id = 'testDocId',
			_localId = 1,
			_revId = 'rev2a',
			foo = 45,
			_labels = {
				b = true
			}
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveArrayDiff( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			arr = { 1, 2, 3 },
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId',
			foo = 42,
			arr = { 4, 5, 6 },
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			foo = 42,
			arr = { 7, 8, 9, 10 },
			_labels = {
				c = true
			}
		},

		-- If the changes are to the same key, we should just pick one (the local one).

		resolution = {
			_changeCounter = 4,
			_id = 'testDocId',
			_localId = 1,
			_revId = 'rev2a',
			foo = 42,
			arr = { 4, 5, 6 },
			_labels = {
				b = true
			}
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveArrayWithOtherConflict( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			arr = { 1, 2, 3 },
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId',
			foo = 45,
			arr = { 1, 2, 3 },
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			foo = 49,
			arr = { 7, 8, 9, 10 },
			_labels = {
				c = true
			}
		},

		-- If the changes are to the same key, we should just pick one (the local one).

		resolution = {
			_changeCounter = 5,
			_id = 'testDocId',
			_localId = 1,
			_revId = 'other',
			foo = 45,
			arr = { 7, 8, 9, 10 },
			-- Label should be removed in this case.
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveSubtableDiffs( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'b',
			},
			_labels = {
				a = true
			}
		},

		rev2a = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blah',
			},
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'boy',
				c = 'copy',
			},
			_labels = {
				c = true
			}
		},

		-- If the changes are to the same key, we should just pick one (the local one).

		resolution = {
			_changeCounter = 5,
			_id = 'testDocId',
			_localId = 1,
			_revId = 'other',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blah',
				c = 'copy',
			},
			-- There should be no labels.
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveSameChange( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'b',
			},
		},

		rev2a = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blah',
			},
		},

		rev2b = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blah',
			},
			_labels = {
				b = true
			}
		},

		-- If the changes are the same between local and remote we should choose
		-- the remote version since it prevents us getting stuck trying to send 
		-- an empty diff to Oz

		resolution = {
			_changeCounter = 4,
			_id = 'testDocId',
			_localId = 1,
			_revId = 'rev2b',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blah',
			},
			_labels = {
				b = true
			}
		},

	}
	
end

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

function WFCatConflictResolverTest.rxTestResolveChangeSameKey_LocalDontSendToOz( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
			},
		},

		rev2a = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blubber'
			},
			_labels = {
				_dontsendtooz = true
			},
		},

		rev2b = {
			_id = 'testDocId',
			foo = 42,
			sub = {
				a = 'a',
				b = 'tipper',
			},
		},

		-- If the changes are the same between local and remote we should choose
		-- the remote version since it prevents us getting stuck trying to send 
		-- an empty diff to Oz

		resolution = {
			_changeCounter = 5,
			_id = 'testDocId',
			_localId = 1,
			_revId = 'other',
			foo = 42,
			sub = {
				a = 'a',
				b = 'blubber',
			},
		},

	}
	

end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue(

		docStore:setDocValue {
			_id = 'testDocId',
			foo = 42,
		}
		:tap( function( revId )
			firstRevId = revId
		end ),

		':ignore:' )

	-- Add a conflicting revision NOT derived from first.

	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue(

		docStore:setDocValue {
			_id = 'testDocId',
			_revIds = { conflictingRevId },
			blap = 47,
		},

		':ignore:' )

	-- Confirm that both are present (and that the rev with
	-- the higher _changeCounter won).

	rxTest:expectSingleValue(

		docStore:currentRevisionsForDoc( 'testDocId' ),

		{
			{
				_id = 'testDocId',
				_changeCounter = 1,
				_conflict = true,
				_localId = 1,
				_revId = firstRevId,
				foo = 42,
			},
			{
				_id = 'testDocId',
				_changeCounter = 2,
				_conflict = true,
				_localId = 1,
				_revId = conflictingRevId,
				blap = 47,
			},
		} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.

	local docValue = rxTest:observe(
	docStore:ongoingDocValue( 'testDocId' )
		:map( function( value )
				value._revId = 'other'
			return value
		end ) )


	docValue:expectDataWithinTime( 2,
		{
			_id = 'testDocId',
			_changeCounter = 4 ,
			_localId = 1,
			_revId = 'other',
			blap = 47,
		} )
	
	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )	
	
end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMergeProvokeRace( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue(

		docStore:setDocValue {
			_id = 'testDocId',
			foo = 42,
		}
		:tap( function( revId )
			firstRevId = revId
		end ),

		':ignore:' )

	-- Add a conflicting revision NOT derived from first.

	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue(

		docStore:setDocValue {
			_id = 'testDocId',
			_revIds = { conflictingRevId },
			blap = 47,
		},

		':ignore:' )

	-- Confirm that both are present (and that the rev with
	-- the higher _changeCounter won).

	rxTest:expectSingleValue(

		docStore:currentRevisionsForDoc( 'testDocId' ),

		{
			{
				_id = 'testDocId',
				_changeCounter = 1,
				_conflict = true,
				_localId = 1,
				_revId = firstRevId,
				foo = 42,
			},
			{
				_id = 'testDocId',
				_changeCounter = 2,
				_conflict = true,
				_localId = 1,
				_revId = conflictingRevId,
				blap = 47,
			},
		} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- Provoke a race as seen by similiar constructs in CT tests
	local waitMultiplier = 30 -- typical value used in CT tests
	-- Wait for a conflict to be resolved
	rxTest:observe( docStore:ongoingDocValue( 'testDocId' ):pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

	-- This test will fail if the conflict resolver does not do the
	-- resolution in a single docStore transaction
	rxTest:expectSingleValue( docStore:docValue( 'testDocId' )
		:map( function( docValue )
				docValue._revId = 'other'
			return docValue
		end ),
		{
			_id = 'testDocId',
			_changeCounter = 4 ,
			_localId = 1,
			_revId = 'other',
			blap = 47,
		} )

end

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

local function testResolveBaselessMerge_UserUpdated( rxTest, firstUserUpdated, secondUserUpdated )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Resolving via userUpdated was made specific to assets affected
	-- by trash so set up a trash related localOnly change.

	local localOnly = {}
	KSTableUtils.setNested( localOnly, true, 'universal', 'trash', 'uploaded', '/rels/master' )

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue( docStore:setDocValue {
			_id = 'testDocId',
			type = 'asset',
			foo = 42,
			userUpdated = firstUserUpdated,
			_localOnly = localOnly,
		}
		:tap( function( revId )
			firstRevId = revId
		end )
	, ':ignore:' )

	-- Add a conflicting revision NOT derived from first.
	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue( docStore:setDocValue {
		_id = 'testDocId',
		type = 'asset',
		_revIds = { conflictingRevId },
		blap = 47,
		userUpdated = secondUserUpdated,
	}, ':ignore:' )

	-- Confirm that both are present (and that the rev with
	-- the higher _changeCounter won).


	rxTest:expectSingleValue( docStore:currentRevisionsForDoc( 'testDocId' )
	, {
		{
			_id = 'testDocId',
			_changeCounter = 1,
			_conflict = true,
			_localId = 1,
			_revId = firstRevId,
			type = 'asset',
			foo = 42,
			userUpdated = firstUserUpdated,
			_localOnly = localOnly,
		},
		{
			_id = 'testDocId',
			_changeCounter = 2,
			_conflict = true,
			_localId = 1,
			_revId = conflictingRevId,
			type = 'asset',
			blap = 47,
			userUpdated = secondUserUpdated,
			_localOnly = localOnly,
		},
	} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.

	if firstUserUpdated == nil then
	
		rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )
		:expectDataWithinTime( 2, {
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = firstRevId,
			type = 'asset',
			foo = 42,
			userUpdated = secondUserUpdated,
			_localOnly = localOnly,
		} )
		:dispose()
	
	elseif secondUserUpdated and secondUserUpdated >= firstUserUpdated then

		rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )
		:expectDataWithinTime( 2, {
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = conflictingRevId,
			type = 'asset',
			blap = 47,
			userUpdated = secondUserUpdated,
			_localOnly = localOnly,
		} )
		:dispose()

	else

		rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )
		:expectDataWithinTime( 2, {
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = firstRevId,
			type = 'asset',
			foo = 42,
			userUpdated = firstUserUpdated,
			_localOnly = localOnly,
		} )
		:dispose()

	end

	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )

end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_UserUpdated_LessThanFirst( rxTest )
	local initUserUpdated = "2019-07-07T07:07:07.007Z"
	local newUserUpdated = "2018-01-01T01:01:01.001Z"
	testResolveBaselessMerge_UserUpdated( rxTest, initUserUpdated, newUserUpdated )
end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_UserUpdated_GreaterThanFirst( rxTest )
	local initUserUpdated = "2019-07-07T07:07:07.007Z"
	local newUserUpdated = "2019-08-15T15:15:15.015Z"
	testResolveBaselessMerge_UserUpdated( rxTest, initUserUpdated, newUserUpdated )
end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_UserUpdated_EqualFirst( rxTest )
	local initUserUpdated = "2019-07-07T07:07:07.007Z"
	testResolveBaselessMerge_UserUpdated( rxTest, initUserUpdated, initUserUpdated )
end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_UserUpdated_NilFirst( rxTest )
	local userUpdated = "2019-07-07T07:07:07.007Z"
	testResolveBaselessMerge_UserUpdated( rxTest, nil, userUpdated )
end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_ChangeCounter( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Resolving via userUpdated was made specific to assets affected
	-- by trash so set up a trash related localOnly change.

	local localOnly = {}
	KSTableUtils.setNested( localOnly, true, 'files', 'master' )

	local firstUserUpdated = "2019-07-07T07:07:07.007Z"
	local secondUserUpdated = "2019-08-15T15:15:15.015Z"

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue( docStore:setDocValue {
			_id = 'testDocId',
			foo = 42,
			userUpdated = firstUserUpdated,
			_localOnly = localOnly,
		}
		:tap( function( revId )
			firstRevId = revId
		end )
	, ':ignore:' )

	-- Add a conflicting revision NOT derived from first.
	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue( docStore:setDocValue {
		_id = 'testDocId',
		_revIds = { conflictingRevId },
		blap = 47,
		userUpdated = secondUserUpdated,
	}, ':ignore:' )

	-- Confirm that both are present (and that the rev with
	-- the higher _changeCounter won).


	rxTest:expectSingleValue( docStore:currentRevisionsForDoc( 'testDocId' )
	, {
		{
			_id = 'testDocId',
			_changeCounter = 1,
			_conflict = true,
			_localId = 1,
			_revId = firstRevId,
			foo = 42,
			userUpdated = firstUserUpdated,
			_localOnly = localOnly,
		},
		{
			_id = 'testDocId',
			_changeCounter = 2,
			_conflict = true,
			_localId = 1,
			_revId = conflictingRevId,
			blap = 47,
			userUpdated = secondUserUpdated,
			_localOnly = localOnly,
		},
	} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.

	rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )
	:expectDataWithinTime( 2, {
		_id = 'testDocId',
		_changeCounter = 4,
		_localId = 1,
		_revId = conflictingRevId,
		blap = 47,
		userUpdated = secondUserUpdated,
		_localOnly = localOnly,
	} )
	:dispose()

	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )

end


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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_TypeSubtypeRevMismatch( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	local firstUserUpdated = "2019-07-07T07:07:07.007Z"
	local secondUserUpdated = "2019-08-15T15:15:15.015Z"

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue( docStore:setDocValue {
			_id = 'testDocId',
			foo = 42,
			userUpdated = firstUserUpdated,
			type = 'asset',
			subtype = 'image'
		}
		:tap( function( revId )
			firstRevId = revId
		end )
	, ':ignore:' )

	-- Add a conflicting revision NOT derived from first.
	
	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue( docStore:setDocValue {
		_id = 'testDocId',
		_revIds = { conflictingRevId },
		blap = 47,
		userUpdated = secondUserUpdated,
		type = 'asset',
		subtype = 'video'
	}, ':ignore:' )

	-- Confirm that both are present and that the remote rev won.

	rxTest:expectSingleValue( docStore:currentRevisionsForDoc( 'testDocId' )
	, {
		{
			_id = 'testDocId',
			_changeCounter = 1,
			_conflict = true,
			_localId = 1,
			_revId = firstRevId,
			foo = 42,
			subtype = "image",
			type = "asset",
			userUpdated = firstUserUpdated,
		},
		{
			_id = 'testDocId',
			_changeCounter = 2,
			_conflict = true,
			_localId = 1,
			_revId = conflictingRevId,
			blap = 47,
			subtype = "video",
			type = "asset",
			userUpdated = secondUserUpdated,
		},
	} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.

	rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )
	:expectDataWithinTime( 2, {
		_id = 'testDocId',
		_changeCounter = 3,
		_localId = 1,
		_revId = conflictingRevId,
		blap = 47,
		subtype = "video",
		type = "asset",
		userUpdated = secondUserUpdated,
	} )
	:dispose()

	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )

end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_TypeSubtypeRevMismatch_Trash( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	local firstUserUpdated = "2019-07-07T07:07:07.007Z"
	local secondUserUpdated = "2019-08-15T15:15:15.015Z"

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue( docStore:setDocValue {
			_id = 'testDocId',
			foo = 42,
			userUpdated = firstUserUpdated,
			type = 'asset',
			subtype = 'image'
		}
		:tap( function( revId )
			firstRevId = revId
		end )
	, ':ignore:' )

	-- Add a conflicting revision NOT derived from first.
	-- Make sure this revision has a trash annotation.
	
	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue( docStore:setDocValue {
		_id = 'testDocId',
		_revIds = { conflictingRevId },
		blap = 47,
		userUpdated = secondUserUpdated,
		type = 'asset',
		subtype = 'video',
		_localOnly = {
			universal = {
				trash = {
					createFrom = {

						lastRevisionId = "d4a62b52683d171ee6f0197f359a4af0",
						lastTrashPurgeDate = "2019-07-12T21:32:40.415Z",
						lastTrashUserCreated = "2019-05-13T21:32:40.415Z",
						revision_id = "d4a62b52683d171ee6f0197f359a4af0",
						subtype = "deleted_image",
					}
				}
			}
		}
	}, ':ignore:' )

	-- Confirm that both are present and that the remote rev won.

	rxTest:expectSingleValue( docStore:currentRevisionsForDoc( 'testDocId' )
	, {
		{
			_id = 'testDocId',
			_changeCounter = 1,
			_conflict = true,
			_localId = 1,
			_localOnly = {
				universal = {
					trash = {
						createFrom = {
							lastRevisionId = "d4a62b52683d171ee6f0197f359a4af0",
							lastTrashPurgeDate = "2019-07-12T21:32:40.415Z",
							lastTrashUserCreated = "2019-05-13T21:32:40.415Z",
							revision_id = "d4a62b52683d171ee6f0197f359a4af0",
							subtype = "deleted_image",
						},
					},
				},
			},
			_revId = firstRevId,
			foo = 42,
			subtype = "image",
			type = "asset",
			userUpdated = firstUserUpdated,
		},
		{
			_id = 'testDocId',
			_changeCounter = 2,
			_conflict = true,
			_localId = 1,
			_localOnly = {
				universal = {
					trash = {
						createFrom = {
							lastRevisionId = "d4a62b52683d171ee6f0197f359a4af0",
							lastTrashPurgeDate = "2019-07-12T21:32:40.415Z",
							lastTrashUserCreated = "2019-05-13T21:32:40.415Z",
							revision_id = "d4a62b52683d171ee6f0197f359a4af0",
							subtype = "deleted_image",
						},
					},
				},
			},
			_revId = conflictingRevId,
			blap = 47,
			subtype = "video",
			type = "asset",
			userUpdated = secondUserUpdated,
		},
	} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.

	rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )
	:expectDataWithinTime( 2, {
		_id = 'testDocId',
		_changeCounter = 3,
		_localId = 1,
		_localOnly = {
			universal = {
				trash = {
					createFrom = {
						lastRevisionId = "d4a62b52683d171ee6f0197f359a4af0",
						lastTrashPurgeDate = "2019-07-12T21:32:40.415Z",
						lastTrashUserCreated = "2019-05-13T21:32:40.415Z",
						revision_id = "d4a62b52683d171ee6f0197f359a4af0",
						subtype = "deleted_image",
					},
				},
			},
		},
		_revId = conflictingRevId,
		blap = 47,
		subtype = "video",
		type = "asset",
		userUpdated = secondUserUpdated,
	} )
	:dispose()

	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )

end

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

function WFCatConflictResolverTest.rxTestResolveLocalRevDeletedConflictOnSpace( rxTest )

	rxTest:setTimeLimit( 120 )

	local optArgs = {}
	
	if WFPlatformInfo.isMobile() then
		optArgs.indexDefinitions = 'LrmCatalogIndexDefinitions.lua'
	end
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest, nil, {}, optArgs )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	-- Create a sharedWithYou album and asset
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( docStore:setDocValue( albumDoc ), ':ignore:' )
	
	-- add comment 
	
	local spaceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local commentId = KSIdGenerator.uuidInLowercaseHex()
	local commentRevId = KSIdGenerator.uuidInLowercaseHex()
	
	local commentDoc = {
		_id = commentId,
		_newRevId = commentRevId,
		type = 'comment',
		subtype = 'user',
		_spaceId = spaceId,
		asset = { id = spaceAssetId },
		body = 'foo',
	}
	
	rxTest:expectSingleValue( docStore:setDocValue( commentDoc ), ':ignore:' )
	
	-- delete comment
	
	rxTest:expectSingleValue( docStore:deleteDoc( commentId ), ':ignore:' )
	
	rxTest:expectSingleValue( docStore:docValue( commentId )
		:pluck( '_deleted' )
		, true )
	
	-- get the same comment response from Oz
	
	local ozUpdateOverlay = {
		_id = commentId,
		type = 'comment',
		subtype = 'user',
		_spaceId = spaceId,
		_revIds = { commentRevId },
		_serverProvidedMetadata = {
			revision_ids = { commentRevId },
			links = {
				self = 'href'
			}
		},
		asset = { id = spaceAssetId },
		body = 'foo',
		updated = '2019-08-08T20:28:43.170276Z',
	}
	
	rxTest:expectSingleValue( docStore:bulkSetDocValue( { label = 'oz', ozUpdateOverlay } ), ':ignore:' )
	
	rxTest:observe( docStore:ongoingDocValue( commentId )
		:pluck( '_conflict' ) )
		:expectDataWithinTime( 2, true )
		:dispose()
	
	-- Attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )
	
	-- check the result comment docValue

	rxTest:observe( docStore:ongoingDocValue( commentId )
		:pluck( '_conflict' ) )
		:expectDataWithinTime( 2, nil )
		:dispose()

	rxTest:expectSingleValue( docStore:docValue( commentId )
		:map( function( commentDocValue )
			
			return commentDocValue._id == commentId
				and commentDocValue._deleted == true
				and commentDocValue._empty == true
		end )
		, true )

end

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

function WFCatConflictResolverTest.rxTestApplyLocalOnlyWhenAssetNotExist( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Start with a document only with _localOnly.

	local firstRevId

	rxTest:expectSingleValue(

		docStore:applyOverlay {
			_id = 'testDocId',
			_localOnly = {
				files = "foo"
			}
		}
		:tap( function( revId )
			firstRevId = revId
		end ),

		':ignore:' )

	-- Add a conflicting revision NOT derived from first.
	-- It's a update from Oz that creates the doc

	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue(

		docStore:setDocValue {
			_id = 'testDocId',
			_revIds = { conflictingRevId },
			type = 'asset',
			subtype = 'image',
			userUpdated = '2019-08-08T20:28:43.170276Z',
		},
		':ignore:' )

	-- Confirm that both are present (and that the rev with
	-- the higher _changeCounter won).

	rxTest:expectSingleValue(

		docStore:currentRevisionsForDoc( 'testDocId' ),

		{
			{
				_id = 'testDocId',
				_changeCounter = 1,
				_conflict = true,
				_localId = 1,
				_revId = firstRevId,
				_localOnly = {
					files = "foo"
				}
			},
			{
				_id = 'testDocId',
				_changeCounter = 2,
				_conflict = true,
				_localId = 1,
				_revId = conflictingRevId,
				_localOnly = {
					files = "foo"
				},
				type = 'asset',
				subtype = 'image',
				userUpdated = '2019-08-08T20:28:43.170276Z',
			},
		} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.
	-- conflictingRev should win and keep the revId
	
	local docValue = rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )

	docValue:expectDataWithinTime( 3,
		{
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = conflictingRevId,
			_localOnly = {
				files = "foo"
			},
			type = 'asset',
			subtype = 'image',
			userUpdated = '2019-08-08T20:28:43.170276Z',
		} )
		
end

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

function WFCatConflictResolverTest.rxTestLocalTrashAssetWithoutUserUpdated( rxTest )

	local docStore = WFRxDocStore( ':memory:' )
	rxTest:addCloseTarget( docStore )

	local indexStore = WFRxIndexStore( docStore, 'WFCatConflictResolverTestIndexDefinitions.lua' )
	rxTest:addCloseTarget( indexStore )

	-- Start with a document only with _localOnly.

	local firstRevId

	rxTest:expectSingleValue(

		docStore:applyOverlay {
			_id = 'testDocId',
			type = 'asset',
			_localOnly = {
				universal = {
					trash = {}
				}
			},
			develop = "foo"
		}
		:tap( function( revId )
			firstRevId = revId
		end ),

		':ignore:' )

	-- Add a conflicting revision NOT derived from first.
	-- It's a update from Oz that creates the doc

	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue(

		docStore:setDocValue {
			_id = 'testDocId',
			type = 'asset',
			_revIds = { conflictingRevId },
			userUpdated = '2019-08-08T20:28:43.170276Z',
			purgeDate = '2019-08-08T20:28:43.170276Z',
		},
		':ignore:' )

	-- Confirm that both are present (and that the rev with
	-- the higher _changeCounter won).

	rxTest:expectSingleValue(

		docStore:currentRevisionsForDoc( 'testDocId' ),

		{
			{
				_id = 'testDocId',
				_changeCounter = 1,
				_conflict = true,
				_localId = 1,
				_revId = firstRevId,
				type = 'asset',
				_localOnly = {
					universal = {
						trash = {}
					}
				},
				develop = "foo"
			},
			{
				_id = 'testDocId',
				_changeCounter = 2,
				_conflict = true,
				_localId = 1,
				_revId = conflictingRevId,
				type = 'asset',
				_localOnly = {
					universal = {
						trash = {}
					}
				},
				
				userUpdated = '2019-08-08T20:28:43.170276Z',
				purgeDate = '2019-08-08T20:28:43.170276Z',
			},
		} )

	-- Now attach conflict resolver.

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )
	rxTest:addCloseTarget( conflictResolver )

	-- We should see the conflict be resolved shortly.
	-- conflictingRev should win and keep the revId
	
	local docValue = rxTest:observe( docStore:ongoingDocValue( 'testDocId' ) )

	docValue:expectDataWithinTime( 3,
		{
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = firstRevId,
			type = 'asset',
			_localOnly = {
				universal = {
					trash = {}
				}
			},
			develop = "foo",
			userUpdated = '2019-08-08T20:28:43.170276Z',
			purgeDate = '2019-08-08T20:28:43.170276Z',
		} )
		
end

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

return WFRxTestCase.wrapSuite( WFCatConflictResolverTest )
