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

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.core.os
local KSDate = require "KSDate"

	-- 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 WFCatAuxTestUtils = require 'WFCatAuxTestUtils'

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

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( 5, 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 )
	rxTest:setTimeLimit( 10 )

	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

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

-- @mark Asset Specific Tests

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

function WFCatConflictResolverTest.rxTestDEBUG_maybeMergeAuxDocSection( rxTest )
	-- Test the case of conflicting aux key

	local defaultOverlay = { aux = {} }

	local aux1ID, aux1, maskRef1, maskRefWithReplace1 = WFCatAuxTestUtils.createAuxAndMaskRef( "first" )
	local aux2ID, aux2, maskRef2, maskRefWithReplace2 = WFCatAuxTestUtils.createAuxAndMaskRef( "second" )
	
	local notFromSource = true
	local aux3ID, aux3, filterRef1, filterRefWithReplace1 = WFCatAuxTestUtils.createAuxAndFilterRef( "third" )
	local aux4ID, aux4, filterRef2, filterRefWithReplace2 = WFCatAuxTestUtils.createAuxAndFilterRef( "forth", nil, nil, notFromSource )
	
	local filterRefSharedGroup = KSTableUtils.merge( {}, filterRef1, filterRef2 )
	local filterRefSharedGroupWithReplace = KSTableUtils.merge( {}, filterRefWithReplace1, filterRefWithReplace2 )
	
	local aux5ID, aux5, filterRefAnotherGroup, filterRefAnotherGroupWithReplace = WFCatAuxTestUtils.createAuxAndFilterRef( "fifth", nil, "anotherGroup" )

	local assetDoc1
	local assetDoc2
	local mergedOverlay

	-- Test with only aux subsections

	assetDoc1 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1
		},
	}

	assetDoc2 = {
		type = 'asset',
		aux = {
			[ aux2ID ] = aux2
		},
	}

	-- This is silly an only exists to surface an internal function in the conflict resolver for test

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

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

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )

	-- end of sillyness

	local aux1WinOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( aux1WinOverlay, { winner = assetDoc1, loser = assetDoc2 } )


	local aux2WinOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( aux2WinOverlay, { winner = assetDoc2, loser = assetDoc1 } )

	-- assert that both directions result in the same state

	local expectedOverlay = {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2
		}
	}

	rxTest:expectEqual(expectedOverlay, aux1WinOverlay)
	rxTest:expectEqual(expectedOverlay, aux2WinOverlay)

	-- Case where aux1 has masks

	assetDoc1 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1,
			[ aux3ID ] = aux3
		},
		develop = {
			masks = maskRef1,
			filters = filterRef1
		},
	}

	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc1, loser = assetDoc2 } )

	-- assert that mergedOverlay has both aux documents references but ONLY has the masks from aux1

	rxTest:expectEqual({
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux3ID ] = aux3
		},
		develop = {
			masks = maskRefWithReplace1,
			filters = filterRefWithReplace1
		}
	}, mergedOverlay )

	-- case where aux2 wins and aux1 has masks
	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc2, loser = assetDoc1 } )

	-- assert that mergedOverlay has both aux documents and no mask references

	rxTest:expectEqual( {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux3ID ] = aux3
		},
	}, mergedOverlay )

	-- case where both documents have aux and mask sections

	assetDoc1 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1,
			[ aux3ID ] = aux3,
			[ aux4ID ] = aux4
		},
		develop = {
			masks = maskRef1,
			filters = filterRefSharedGroup
		},
	}

	assetDoc2 = {
		type = 'asset',
		aux = {
			[ aux2ID ] = aux2,
			[ aux5ID ] = aux5
		},
		develop = {
			masks = maskRef2,
			filters = filterRefAnotherGroup
		},
	}

	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc1, loser = assetDoc2 } )

	-- assert that mergedOverlay has both aux documents and aux1 mask references

	rxTest:expectEqual( {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux3ID ] = aux3,
			[ aux4ID ] = aux4,
			[ aux5ID ] = aux5
		},
		develop = {
			masks = maskRefWithReplace1,
			filters = filterRefSharedGroupWithReplace
		}
	}, mergedOverlay )

	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc2, loser = assetDoc1 } )

	-- assert that mergedOverlay has both aux documents and aux2 mask references

	rxTest:expectEqual( {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux3ID ] = aux3,
			[ aux4ID ] = aux4,
			[ aux5ID ] = aux5
		},
		develop = {
			masks = maskRefWithReplace2,
			filters = filterRefAnotherGroupWithReplace
		}
	}, mergedOverlay )

	-- Case where overlay is populated with masks

	assetDoc1 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1
		},
	}

	assetDoc2 = {
		type = 'asset',
		aux = {
			[ aux2ID ] = aux2,
			[ aux5ID ] = aux5,
		},
		develop = {
			masks = maskRef2,
			filters = filterRefAnotherGroup
		},
	}

	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	mergedOverlay.develop = { masks = maskRef2, filters = filterRefAnotherGroup }
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc1, loser = assetDoc2 } )

	rxTest:expectEqual( {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux5ID ] = aux5
		},
		develop = { _delete = true }
	}, mergedOverlay )

	-- Case where overlay is populated with masks AND other stuff

	assetDoc1 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1
		},
	}

	assetDoc2 = {
		type = 'asset',
		aux = {
			[ aux2ID ] = aux2,
			[ aux5ID ] = aux5,
		},
		develop = {
			masks = maskRef2,
			filters = filterRefAnotherGroup
		},
	}

	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	mergedOverlay.develop = { masks = maskRef2, filters = filterRefAnotherGroup, foo = 'bar', baz = 'boo' }
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc1, loser = assetDoc2 } )

	rxTest:expectEqual( {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux5ID ] = aux5,
		},
		develop = {
			masks = { _delete = true },
			filters = { _delete = true },
			foo = 'bar',
			baz = 'boo'
		}
	}, mergedOverlay )

	-- Case where loser rev has filter resolution that is not included in winner rev

	assetDoc1 = {
		type = 'asset',
		aux = {
			[ aux3ID ] = aux3
		},
		develop = {
			filters = filterRef1
		}
	}

	assetDoc2 = {
		type = 'asset',
		aux = {
			[ aux3ID ] = aux3,
			[ aux4ID ] = aux4,
		},
		develop = {
			filters = filterRefSharedGroup
		},
	}

	mergedOverlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( mergedOverlay, { winner = assetDoc1, loser = assetDoc2 } )

	rxTest:expectEqual( {
		aux = {
			[ aux3ID ] = aux3,
			[ aux4ID ] = aux4,
		},
		develop = {
			filters = filterRefSharedGroupWithReplace
		}
	}, mergedOverlay )

end

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

function WFCatConflictResolverTest.rxTestDEBUG_maybeMergeAuxDocSection_CompressedAux( rxTest )

	-- This is silly an only exists to surface an internal function in the conflict resolver for test

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

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

	local conflictResolver = WFCatConflictResolver( docStore, indexStore )

	-- end of sillyness

	local defaultOverlay = { aux = {} }

	local aux1ID, aux1 = WFCatAuxTestUtils.createAuxAndMaskRef( "first" )
	local aux2ID, aux2 = WFCatAuxTestUtils.createAuxAndMaskRef( "second" )
	local aux3ID, aux3 = WFCatAuxTestUtils.createAuxAndMaskRef( "third" )
	local aux4ID, aux4 = WFCatAuxTestUtils.createAuxAndMaskRef( "fourth" )
	local aux5ID, aux5 = WFCatAuxTestUtils.createAuxAndMaskRef( "fifth" )
	
	local notFromSource = true
	local aux6ID, aux6, filterRef1 = WFCatAuxTestUtils.createAuxAndFilterRef( "sixth" )
	local aux7ID, aux7, filterRef2 = WFCatAuxTestUtils.createAuxAndFilterRef( "seventh", nil, nil, notFromSource )
	
	local filterRefSharedGroup = KSTableUtils.merge( {}, filterRef1, filterRef2 )
	
	local aux8ID, aux8, filterRefAnotherGroup = WFCatAuxTestUtils.createAuxAndFilterRef( "eighth", nil, "anotherGroup" )

	local rev1, rev2, base

	base = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux3ID ] = aux3,
			[ aux6ID ] = aux6
		},
		develop = {
			masks = {
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2,
				[ aux3ID ] = aux3
			},
			filters = filterRef1
		}
	}

	-- rev1 compresses away revisions auxId2 and auxId3

	rev1 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1,
			[ aux5ID ] = aux5,
			[ aux6ID ] = aux6,
			[ aux7ID ] = aux7
		},
		develop = {
			masks = {
				[ aux1ID ] = aux1,
				[ aux5ID ] = aux5
			},
			filters = filterRefSharedGroup
		}
	}

	rev2 = {
		type = 'asset',
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux3ID ] = aux3,
			[ aux4ID ] = aux4,
			[ aux8ID ] = aux8
		},
		develop = {
			masks = {
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2,
				[ aux4ID ] = aux4
			},
			filters = filterRefAnotherGroup
		}
	}

	-- Rev1 as winner. We can compress away both auxID2 & auxID3, since they are not
	-- used by the winning masks section. In addition, auxID4 and auxID5 are new and
	-- must be added.

	local overlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( overlay, { winner = rev1, loser = rev2 }, base )

	local filterRefSharedGroupWithReplace = KSTableUtils.deepCopy( filterRefSharedGroup )
	filterRefSharedGroupWithReplace._replace = true

	local expectedOverlay = {
		aux = {
			[ aux1ID ] = aux1,
			[ aux4ID ] = aux4,
			[ aux5ID ] = aux5,
			[ aux6ID ] = aux6,
			[ aux7ID ] = aux7,
			[ aux8ID ] = aux8
		},
		develop = {
			masks = {
				_replace = true,
				[ aux1ID ] = aux1,
				[ aux5ID ] = aux5
			},
			filters = filterRefSharedGroupWithReplace
		}
	}

	rxTest:expectEqual( expectedOverlay, overlay )

	-- Rev2 as winner. We can only compress away auxID3, since auxID2 is still active.
	-- In addition, auxID4 and auxID5 are new and must be added.

	overlay = KSTableUtils.deepCopy( defaultOverlay )
	conflictResolver:DEBUG_maybeMergeAuxDocSection( overlay, { winner = rev2, loser = rev1 }, base )

	local filterRefAnotherGroupWithReplace = KSTableUtils.deepCopy( filterRefAnotherGroup )
	filterRefAnotherGroupWithReplace._replace = true
	
	expectedOverlay = {
		aux = {
			[ aux1ID ] = aux1,
			[ aux2ID ] = aux2,
			[ aux4ID ] = aux4,
			[ aux5ID ] = aux5,
			[ aux7ID ] = aux7,
			[ aux8ID ] = aux8
		},
		develop = {
			masks = {
				_replace = true,
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2,
				[ aux4ID ] = aux4
			},
			filters = filterRefAnotherGroupWithReplace
		}
	}

	rxTest:expectEqual( expectedOverlay, overlay )

end

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

function WFCatConflictResolverTest.rxTestAssetAuxThreeWayMerge( rxTest )

	rxTest:setTimeLimit(10)

	-- Test the case of conflicting aux key

	local aux1ID, aux1, _ = WFCatAuxTestUtils.createAuxAndMaskRef( "first" )
	local aux2ID, aux2, maskRef2 = WFCatAuxTestUtils.createAuxAndMaskRef( "second" )
	
	local notFromSource = true
	local aux3ID, aux3, filterRef1 = WFCatAuxTestUtils.createAuxAndFilterRef( "third" )
	local aux4ID, aux4, filterRef2 = WFCatAuxTestUtils.createAuxAndFilterRef( "forth", nil, nil, notFromSource )
	
	local filterRefSharedGroup = KSTableUtils.merge( {}, filterRef1, filterRef2 )
	
	local aux5ID, aux5, filterRefAnotherGroup = WFCatAuxTestUtils.createAuxAndFilterRef( "fifth", nil, "anotherGroup" )
--
--	rxTestThreeWayMerge {
--
--		rxTest = rxTest,
--
--		rev1 = {
--			_id = 'ConflictAuxMerge',
--			type = 'asset',
--		},
--
--		rev2a = {
--			_id = 'ConflictAuxMerge',
--			type = 'asset',
--			aux = {
--				[ aux1ID ] = aux1
--			},
--		},
--
--		rev2b = {
--			_id = 'ConflictAuxMerge',
--			type = 'asset',
--			aux = {
--				[ aux2ID ] = aux2
--			},
--		},
--
--		-- The aux section on conflict should be merged
--
--		resolution = {
--			_changeCounter = 5,
--			_id = 'ConflictAuxMerge',
--			type = 'asset',
--			_localId = 1,
--			_revId = 'other',
--			aux = {
--				[ aux1ID ] = aux1,
--				[ aux2ID ] = aux2
--			},
--		},
--
--	}
--
--	-- Test the case of same auxID
--
--	rxTestThreeWayMerge {
--
--		rxTest = rxTest,
--
--		rev1 = {
--			_id = 'ConflictAuxNOOP',
--			type = 'asset',
--		},
--
--		rev2a = {
--			_id = 'ConflictAuxNOOP',
--			type = 'asset',
--			aux = {
--				[ aux1ID ]= aux1
--			},
--		},
--
--		rev2b = {
--			_id = 'ConflictAuxNOOP',
--			type = 'asset',
--			aux = {
--				[ aux1ID ] = aux1
--			},
--		},
--
--		-- The aux section on conflict should be merged
--
--		resolution = {
--			_changeCounter = 4,
--			_id = 'ConflictAuxNOOP',
--			type = 'asset',
--			_localId = 1,
--			_revId = 'rev2b', -- FIXME: is this correct
--			aux = {
--				[ aux1ID ] = aux1,
--			},
--		},
--
--	}
--
--	-- Test the case of develop aux with other develop key
--
--	rxTestThreeWayMerge {
--
--		rxTest = rxTest,
--
--		rev1 = {
--			_id = 'ConflictAuxMatchMasks',
--			type = 'asset',
--		},
--
--		rev2a = {
--			_id = 'ConflictAuxMatchMasks',
--			type = 'asset',
--			aux = {
--				[ aux1ID ] = aux1
--			},
--			develop = {
--				masks = maskRef1
--			},
--		},
--
--		rev2b = {
--			_id = 'ConflictAuxMatchMasks',
--			type = 'asset',
--			aux = {
--				[ aux2ID ] = aux2
--			},
--			develop = {
--				masks = maskRef2
--			},
--		},
--
---- The develop section should only consist of masks referenced by the winning Document - eg local in this case
--
--		resolution = {
--			_changeCounter = 5,
--			_id = 'ConflictAuxMatchMasks',
--			type = 'asset',
--			_localId = 1,
--			_revId = 'other',
--			aux = {
--				[ aux1ID ] = aux1,
--				[ aux2ID ] = aux2
--			},
--			develop = {
--				masks = maskRef1
--			},
--		},
--
--	}

	-- Test the case of develop filter aux with another group

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'ConflictAuxMismatchFilters',
			type = 'asset',
		},

		rev2a = {
			_id = 'ConflictAuxMismatchFilters',
			type = 'asset',
			aux = {
				[ aux3ID ] = aux3,
				[ aux4ID ] = aux4
			},
			develop = {
				filters = filterRefSharedGroup
			},
		},

		rev2b = {
			_id = 'ConflictAuxMismatchFilters',
			type = 'asset',
			aux = {
				[ aux5ID ] = aux5
			},
			develop = {
				filters = filterRefAnotherGroup
			},
		},

-- The develop section should only consist of filters referenced by the winning Document - eg local in this case

		resolution = {
			_changeCounter = 5,
			_id = 'ConflictAuxMismatchFilters',
			type = 'asset',
			_localId = 1,
			_revId = 'other',
			aux = {
				[ aux3ID ] = aux3,
				[ aux4ID ] = aux4,
				[ aux5ID ] = aux5
			},
			develop = {
				filters = filterRefSharedGroup
			},
		},

	}
	
	-- Test the case of develop section should be deleted

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'ConflictAuxEmptyDevelop',
			type = 'asset',
		},

		rev2a = {
			_id = 'ConflictAuxEmptyDevelop',
			type = 'asset',
			aux = {
				[ aux1ID ] = aux1
			},
			develop = {
			},
		},

		rev2b = {
			_id = 'ConflictAuxEmptyDevelop',
			type = 'asset',
			aux = {
				[ aux2ID ] = aux2
			},
			develop = {
				masks = maskRef2
			},
		},

-- The develop section should only consist of filters referenced by the winning Document - eg local in this case

		resolution = {
			_changeCounter = 5,
			_id = 'ConflictAuxEmptyDevelop',
			type = 'asset',
			_localId = 1,
			_revId = 'other',
			aux = {
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2
			}
		},

	}
	
end

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

function WFCatConflictResolverTest.rxTestAssetAuxLocalRevByUserUpdated( rxTest )
	-- Test the case of conflicting aux key

	local aux1ID, aux1, maskRef1 = WFCatAuxTestUtils.createAuxAndMaskRef( "first" )
	local aux2ID, aux2, maskRef2 = WFCatAuxTestUtils.createAuxAndMaskRef( "second" )
	
	local notFromSource = true
	local aux3ID, aux3, filterRef1 = WFCatAuxTestUtils.createAuxAndFilterRef( "third" )
	local aux4ID, aux4, filterRef2 = WFCatAuxTestUtils.createAuxAndFilterRef( "forth", nil, nil, notFromSource )
	
	local filterRefSharedGroup = KSTableUtils.merge( {}, filterRef1, filterRef2 )
	
	local aux5ID, aux5, filterRefAnotherGroup = WFCatAuxTestUtils.createAuxAndFilterRef( "fifth", nil, "anotherGroup" )

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

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

	local assetID = 'AssetAuxUserUpdated'

	local editDay = 06
	local firstUserUpdated = KSDate.politeDate( 2022, 03, editDay, 'long')
	local secondUserUpdated = KSDate.politeDate( 2022, 03, editDay + 1, 'long')

	local firstRevId

	rxTest:expectSingleValue( docStore:setDocValue {
			_id = assetID,
			type = 'asset',
			userUpdated = firstUserUpdated,
			aux = {
				[ aux1ID ] = aux1,
				[ aux3ID ] = aux3,
				[ aux4ID ] = aux4
			},
			develop = {
				masks = maskRef1,
				filters = filterRefSharedGroup
			}
		}
		:tap( function( revId )
			firstRevId = revId
		end )
	, ':ignore:' )

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

	rxTest:expectSingleValue( docStore:setDocValue {
		_id = assetID,
		type = 'asset',
		_revIds = { conflictingRevId },
		aux = {
			[ aux2ID ] = aux2,
			[ aux5ID ] = aux5
		},
		develop = {
			masks = maskRef2,
			filters = filterRefAnotherGroup
		},
		userUpdated = secondUserUpdated,
	}, ':ignore:' )

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


	rxTest:expectSingleValue( docStore:currentRevisionsForDoc( assetID )
	, {
		{
			_id = assetID,
			_changeCounter = 1,
			_conflict = true,
			_localId = 1,
			_revId = firstRevId,
			type = 'asset',
			aux = {
				[ aux1ID ] = aux1,
				[ aux3ID ] = aux3,
				[ aux4ID ] = aux4
			},
			develop = {
				masks = maskRef1,
				filters = filterRefSharedGroup
			},
			userUpdated = firstUserUpdated,
		},
		{
			_id = assetID,
			_changeCounter = 2,
			_conflict = true,
			_localId = 1,
			_revId = conflictingRevId,
			type = 'asset',
			aux = {
				[ aux2ID ] = aux2,
				[ aux5ID ] = aux5
			},
			develop = {
				masks = maskRef2,
				filters = filterRefAnotherGroup
			},
			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( assetID ) )
		:expectDataWithinTime( 2, {
			_id = assetID,
			_changeCounter = 4,
			_localId = 1,
			_revId = conflictingRevId,
			type = 'asset',
			userUpdated = secondUserUpdated,
			aux = {
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2,
				[ aux3ID ] = aux3,
				[ aux4ID ] = aux4,
				[ aux5ID ] = aux5
			},
			develop = {
				masks = maskRef2,
				filters = filterRefAnotherGroup
			}
		} )
		:dispose()


	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )

end

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

function WFCatConflictResolverTest.rxTestResolveArrayWithOtherConflict_AssetAux( rxTest )

	local aux1ID, aux1, maskRef1 = WFCatAuxTestUtils.createAuxAndMaskRef( "first" )
	local aux2ID, aux2, maskRef2 = WFCatAuxTestUtils.createAuxAndMaskRef( "second" )
	
	local aux3ID, aux3, filterRef1 = WFCatAuxTestUtils.createAuxAndFilterRef( "third" )

	rxTestThreeWayMerge {

		rxTest = rxTest,

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

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			foo = 45,
			aux = {
				[ aux1ID ] = aux1
			},
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			foo = 49,
			aux = {
				[ aux2ID ] = aux2,
				[ aux3ID ] = aux3
			},
			develop = {
				masks = maskRef2,
				filters = filterRef1,
				xmpCameraRaw = { sha256 = 'externalizedSettings' },
			},
			_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',
			type = 'asset',
			foo = 45,
			aux = {
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2,
				[ aux3ID ] = aux3
			},
			develop = {
				masks = maskRef2,
				filters = filterRef1,
				xmpCameraRaw = { sha256 = 'externalizedSettings' }
			},
			-- develop section should be removed since rev2a won
		},

	}

	-- Case where develop masks wins loses

	rxTestThreeWayMerge {

		rxTest = rxTest,

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

		rev2a = {
			_id = 'testDocId',
			foo = 45,
			aux = {
				[ aux1ID ] = aux1,
				[ aux3ID ] = aux3
			},
			develop = {
				masks = maskRef1,
				filters = filterRef1
			},
			_labels = {
				b = true
			}
		},

		rev2b = {
			_id = 'testDocId',
			foo = 49,
			aux = {
				[ aux2ID ] = aux2
			},

			_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,
			aux = {
				[ aux1ID ] = aux1,
				[ aux2ID ] = aux2,
				[ aux3ID ] = aux3
			},
			develop = {
				masks = maskRef1,
				filters = filterRef1
			},
			-- Label should be removed in this case.
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveBaselessMerge_AssetAuxMismatch( rxTest )


	rxTest:setTimeLimit( 5 )

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

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

	local docWithAux1 = {
			_id = "AssetWithAux",
			type = "asset",
			subtype = "image",
			develop = {
				masks = {
					one = {
						digest = "mask-1-digest"
					}
				}
			},
			aux = {
				one = {
					type = 'mask',
					fileSize = 100,
					digest = "mask-1-digest",
					sha256 = "mask-1-sha256"
				}

			}
		}

	local docWithAux2 = {
			_id = "AssetWithAux",
			type = "asset",
			subtype = "image",
			develop = {
				masks = {
					two = {
						['digest'] = "mask-2-digest"
					}
				}
			},
			aux = {
				two = {
					type = 'mask',
					fileSize = 100,
					digest = "mask-2-digest",
					sha256 = "mask-2-sha256"
				}

			}
		}

	-- Start with a simple document.

	local firstRevId

	rxTest:expectSingleValue( docStore:setDocValue( docWithAux1 )
		:tap( function( revId )
			firstRevId = revId
		end )
	, ':ignore:' )

	-- Add a conflicting revision NOT derived from first.

	local conflictingRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:expectSingleValue( docStore:setDocValue(	KSTableUtils.merge({}, {_revIds = { conflictingRevId }}, docWithAux2) ), ':ignore:' )

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

	local expectedAux1 = KSTableUtils.merge({}, {
				_id = docWithAux1.id,
				_changeCounter = 1,
				_revId = firstRevId,
				_conflict = true,
				_localId = 1,
			},
			docWithAux1)

	local expectedAux2 = KSTableUtils.merge({}, {
				_id = docWithAux2.id,
				_changeCounter = 2,
				_revId = conflictingRevId ,
				_conflict = true,
				_localId = 1,
			},
			docWithAux2)

	rxTest:expectSingleValue(
		docStore:currentRevisionsForDoc( 'AssetWithAux' )
		, {
			expectedAux1,
			expectedAux2
		} )

	-- Now attach conflict resolver.

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

	-- We should see the conflict be resolved shortly.
	-- The higher changeCounter should win (docWithAux2)

	rxTest:observe( docStore:ongoingDocValue( 'AssetWithAux' ) )
	:expectDataWithinTime( 5, {
		_id = 'AssetWithAux',
		_changeCounter = 4,
		_localId = 1,
		_revId = conflictingRevId,
		type = "asset",
		subtype = "image",
		develop = {
			masks = {
				two = {
					['digest'] = "mask-2-digest"
				}
			}
		},
		aux = {
			one = {
				type = 'mask',
				fileSize = 100,
				digest = "mask-1-digest",
				sha256 = "mask-1-sha256"
			},
			two = {
				type = 'mask',
				fileSize = 100,
				digest = "mask-2-digest",
				sha256 = "mask-2-sha256"
			}

		}

	} )
	:dispose()

	rxTest:expectOnlyCompleted( docStore:performIntegrityCheck() )

end

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

-- brief: Test that conflict resolver uses userUpdated to resolve conflicts in image revisions
-- This test should keep rev2a develop field and ignore rev2b both when rev2a has more recent
-- userUpdated and when userUpdated is the same for both

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_localRev( rxTest )

	rxTest:setTimeLimit( 5 )

	-- local rev2a has more recent userUpdated

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2020-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo1',
				fromDefaults = true
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo2',
				device = 'ipad'
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2020-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo3',
				crsHDREditMode = true
			},
			_labels = { c = true }

		},

		-- Conflict should resolve to keep the 'develop'
		-- section of the revision with the latest userUpdated field

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2a",
			_labels = { b = true },
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo2',
				device = 'ipad'
			}

		}

	}

	-- local rev2a same userUpdated as remote rev2b

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2020-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo1',
				fromDefaults = true
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo2',
				device = 'ipad'
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo3',
				crsHDREditMode = true
			},
			_labels = { c = true }

		},

		-- Conflict should resolve to keep the 'develop'
		-- section of the revision with the latest userUpdated field

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2a",
			_labels = { b = true },
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo2',
				device = 'ipad'
			}

		}

	}

end

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

-- brief: Test that conflict resolver uses userUpdated to resolve conflicts in image revisions
-- This test should keep rev2b develop field and count rev2a as it occured before rev2b.

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_remoteRev( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2020-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo1',
				fromDefaults = true
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-09-15T12:18:38.23Z',
				xmpCameraRaw = 'foo2',
				device = 'ipad'
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo3',
				crsHDREditMode = true
			},
			_labels = { c = true }

		},

		-- Image resolver should maintain develop sections

		resolution = {
			_id = 'testDocId',

			_changeCounter = 5,
			_localId = 1,
			_revId = "other",

			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo3',
				crsHDREditMode = true
			},

		}

	}

end

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

-- brief: Test that conflict resolver uses userUpdated to resolve conflicts in image revisions.
-- Conflict resolver should *NOT* create an empty develop section if no develop section is found.

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_noDevelopSettingsOnRemote( rxTest )

rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo',
				crsHDREditMode = true
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { c = true }

		},

		-- Image resolver should not create an empty develop section

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2b",
			_labels = { c = true },
			type = 'asset',
			subtype = 'image',

		},

	}

end

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

-- brief: Test that conflict resolver uses userUpdated to resolve conflicts in image revisions.
-- Conflict resolver should *NOT* create an empty develop section if no develop section is found.

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_noDevelopSettingsOnLocal( rxTest )

rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo',
				crsHDREditMode = true
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { c = true }

		},

		-- Image resolver should not create an empty develop section

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2a",
			_labels = { b = true },
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo',
				crsHDREditMode = true
			},

		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_noDevelopSettings( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			foo = 'bar',
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			foo = 'baz',
			_labels = { c = true }
		},

		-- Image resolver should *NOT* create an empty develop section.

		resolution = {
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2a",
			_labels = { b = true },
			foo = 'bar',
			type = 'asset',
			subtype = 'image',
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_noUserUpdatedInRemoteRev( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { a = true },
			develop = {
				userUpdated =  '2020-10-15T12:18:38.23Z',
				xmpCameraRaw = 'foo',
				fromDefaults = true
			},
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { b = true },
			develop = {
				userUpdated =  '2021-10-15T12:18:38.23Z',
				xmpCameraRaw = 'bar',
				device = 'ipad'
			},

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { c = true },
			develop = {
				xmpCameraRaw = 'bar2',
				crsHDREditMode = true
				
			},
		},

		-- since rev2b doesn't have userUpdated, maybeImageConflictResolution 
		-- should keep develop settings from rev2a

		resolution = {
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2a",
			_labels = { b = true },
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2021-10-15T12:18:38.23Z',
				xmpCameraRaw = 'bar',
				device = 'ipad'
			},
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_noUserUpdatedInLocalRev( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { a = true },
			develop = {
				xmpCameraRaw = 'foo',
				fromDefaults = true
			},
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { b = true },
			develop = {
				xmpCameraRaw = 'bar',
				device = 'ipad'
			},

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'image',
			_labels = { c = true },
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'bar2',
				crsHDREditMode = true
			},
		},

		-- since rev2a doesn't have userUpdated, maybeImageConflictResolution 
		-- should keep develop settings from rev2b

		resolution = {
			_id = 'testDocId',
			_changeCounter = 5,
			_localId = 1,
			_revId = "other",
			type = 'asset',
			subtype = 'image',
			develop = {
				userUpdated =  '2022-10-15T12:18:38.23Z',
				xmpCameraRaw = 'bar2',
				crsHDREditMode = true
			},
		},

	}

end

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

local function rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

	-- If only one revision's user updated has changed, the settings from that revision
	-- should win regardless of what the userUpdated value is: more recent, less
	-- recent or no value at all.

	local baseDoc = {
		_id = 'testDocId',
		type = 'asset',
		subtype = 'image',
		develop = {
			userUpdated =  rev1Date,
			xmpCameraRaw = 'rev1',
		}
	}

	local rev1 = KSTableUtils.deepCopy( baseDoc )
	rev1._labels = { a = true }

	local rev2a = KSTableUtils.deepCopy( baseDoc )
	rev2a._labels = { b = true }
	rev2a.develop = rev2aDate and {
		userUpdated = rev2aDate,
		xmpCameraRaw = 'rev2a',
	}

	local rev2b = KSTableUtils.deepCopy( baseDoc )
	rev2b._labels = { c = true }
	rev2b.develop = rev2bDate and {
		userUpdated =  rev2bDate,
		xmpCameraRaw = 'rev2b',
	}

	rxTestThreeWayMerge {

		rxTest = rxTest,
		rev1 = rev1,
		rev2a = rev2a,
		rev2b = rev2b,
		resolution = resolution,

	}

end

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

local function changeUserUpdatedInOnlyRemoteRevResolution( rev2bDate )

	return {
		_id = 'testDocId',
		_changeCounter = 5,
		_localId = 1,
		_revId = "other",
		type = 'asset',
		subtype = 'image',
		develop = {
			userUpdated =  rev2bDate,
			xmpCameraRaw = 'rev2b',
		},
	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdatedInOnlyRemoveRev_MoreRecent( rxTest )

	local rev1Date = '2022-10-15T12:18:38.23Z'
	local rev2aDate = rev1Date
	local rev2bDate = '2023-10-15T12:18:38.23Z' -- More Recent

	local resolution = changeUserUpdatedInOnlyRemoteRevResolution( rev2bDate )

	rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdatedInOnlyRemoveRev_LessRecent( rxTest )

	local rev1Date = '2022-10-15T12:18:38.23Z'
	local rev2aDate = rev1Date
	local rev2bDate = '2021-10-15T12:18:38.23Z' --  Less Recent

	local resolution = changeUserUpdatedInOnlyRemoteRevResolution( rev2bDate )

	rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdatedInOnlyRemoveRev_Removed( rxTest )

	local rev1Date = '2022-10-15T12:18:38.23Z'
	local rev2aDate = rev1Date
	local rev2bDate = nil -- Removed

	local resolution = changeUserUpdatedInOnlyRemoteRevResolution( rev2bDate )
	resolution.develop = nil

	rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

end

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

local function changeUserUpdatedInOnlyLocalRevResolution( rev2aDate )

	return {
		_id = 'testDocId',
		_changeCounter = 4,
		_localId = 1,
		_labels = { b = true },
		_revId = "rev2a",
		type = 'asset',
		subtype = 'image',
		develop = {
			userUpdated =  rev2aDate,
			xmpCameraRaw = 'rev2a',
		}
	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdatedInOnlyLocalRev_MoreRecent( rxTest )

	local rev1Date = '2022-10-15T12:18:38.23Z'
	local rev2aDate = '2023-10-15T12:18:38.23Z' -- More Recent
	local rev2bDate = rev1Date

	local resolution = changeUserUpdatedInOnlyLocalRevResolution( rev2aDate )

	rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdatedInOnlyLocalRev_LessRecent( rxTest )

	local rev1Date = '2022-10-15T12:18:38.23Z'
	local rev2aDate = '2021-10-15T12:18:38.23Z' --  Less Recent
	local rev2bDate = rev1Date

	local resolution = changeUserUpdatedInOnlyLocalRevResolution( rev2aDate )

	rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdatedInOnlyLocalRev_Removed( rxTest )

	local rev1Date = '2022-10-15T12:18:38.23Z'
	local rev2aDate = nil -- Removed
	local rev2bDate = rev1Date

	local resolution = changeUserUpdatedInOnlyLocalRevResolution( rev2aDate )
	resolution.develop = nil

	rxTestResolveThreeWayMerge_ImageDevelopSettings_changedUserUpdated( rxTest, rev1Date, rev2aDate, rev2bDate, resolution )

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_localRev( rxTest )

	-- local rev2a has more recent userUpdated

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should maintain atomic sections in `adjustment` and `temporal`

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_labels = {
				b = true,
			},
			_localId = 1,
			_revId = "rev2a",

			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			}

		}

	}

	-- local rev2a same userUpdated as remote rev2b

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should maintain atomic sections in `adjustment` and `temporal`

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_labels = {
				b = true,
			},
			_localId = 1,
			_revId = "rev2a",

			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			}

		}

	}

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_remoteRev( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should maintain atomic sections in `adjustment` and `temporal`

		resolution = {
			_id = 'testDocId',

			_changeCounter = 5,
			_localId = 1,
			_revId = "other",

			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			}

		}

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_remoteRev( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should maintain atomic sections in `adjustment` and `temporal`

		resolution = {
			_id = 'testDocId',

			_changeCounter = 5,
			_localId = 1,
			_revId = "other",

			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			}

		}

	}

end

--------------------------------------------------------------------------------
function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_remoteRev( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 10,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 10000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should maintain atomic sections in `adjustment` and `temporal`

		resolution = {
			_id = 'testDocId',

			_changeCounter = 5,
			_localId = 1,
			_revId = "other",

			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-10-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-02-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			}

		}

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_noDevelopSettings( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					saturation = 0,
				},
				temporal = {
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			_labels = { c = true }

		},

		-- Video resolver should maintain atomic sections in `adjustment` and `temporal`

		resolution = {
			_id = 'testDocId',

			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2b",
			_labels = { c = true },
			type = 'asset',
			subtype = 'video',

		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_noDevelopSettings2( rxTest )

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			_labels = { a = true }
		},

		rev2a = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			foo = 'bar',
			_labels = { b = true }

		},

		rev2b = {
			_id = 'testDocId',
			type = 'asset',
			subtype = 'video',
			foo = 'baz',
			_labels = { c = true }
		},

		-- Video resolver should *NOT* create an empty develop section.

		resolution = {
			_id = 'testDocId',
			_changeCounter = 4,
			_localId = 1,
			_revId = "rev2a",
			_labels = { b = true },
			foo = 'bar',
			type = 'asset',
			subtype = 'video',
		},

	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_localRevNewVersion( rxTest )

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 2,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 11,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 30000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should use the develop section from the revision with
		-- the newer version.

		resolution = {
			_id = assetId,
			_changeCounter = 4,
			_labels = {
				b = true,
			},
			_localId = 1,
			_revId = "rev2a",
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 2,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			}
		}

	}
	
end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_remoteRevNewVersion( rxTest )

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 2,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 11,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 30000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should use the develop section from the revision with
		-- the newer version.

		resolution = {
			_id = assetId,
			_changeCounter = 5,
			_localId = 1,
			_revId = "other",
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 2,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 11,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 30000000,
				}
			}
		}
	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_localRevNewProcessVersion( rxTest )

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 2,
				adjustments = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 11,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 30000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should use the develop section from the revision with
		-- the newer process version.

		resolution = {
			_id = assetId,
			_changeCounter = 4,
			_labels = {
				b = true,
			},
			_localId = 1,
			_revId = "rev2a",
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 2,
				adjustments = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			}
		}
	}

end

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

function WFCatConflictResolverTest.rxTestResolveThreeWayMerge_VideoDevelopSettings_remoteRevNewProcessVersion( rxTest )

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	rxTestThreeWayMerge {

		rxTest = rxTest,

		rev1 = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					temporalFraction = 50000000,
				}
			},
			_labels = { a = true }
		},

		rev2a = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 1,
				adjustments = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					saturation = 0,
				},
				temporal = {
					userUpdated = '2020-11-15T12:18:38.23Z',
					temporalFraction = 20000000,
				}
			},
			_labels = { b = true }

		},

		rev2b = {
			_id = assetId,
			type = 'asset',
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 2,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 11,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 30000000,
				}
			},
			_labels = { c = true }

		},

		-- Video resolver should use the develop section from the revision with
		-- the newer process version.

		resolution = {
			_id = assetId,
			_changeCounter = 5,
			_localId = 1,
			_revId = 'other',
			type = "asset",
			subtype = 'video',
			develop = {
				version = 1,
				processVersion = 2,
				adjustments = {
					userUpdated = '2020-09-15T12:18:38.23Z',
					saturation = 11,
				},
				temporal = {
					userUpdated = '2020-01-15T12:18:38.23Z',
					temporalFraction = 30000000,
				}
			}
		}
	}

end

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

return WFRxTestCase.wrapSuite( 'WFCatConflictResolverTest', WFCatConflictResolverTest )
