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

WFCatTrashPartialSyncTest.lua

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

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

NOTICE: All information contained herein is, and remains 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 are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
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_test
local WFRxTestCase = require 'WFRxTestCase'
local WFTestFileUtils = require 'WFTestFileUtils'

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

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

	-- WF.oz_client.managed_catalog
local WFCatTrash = require 'WFCatTrash'
local WFCatTrashTestUtils = require 'WFCatTrashTestUtils'
local WFCatTrashUtils = require 'WFCatTrashUtils'

	-- WF.web.json
local KSJsonEncoder = require 'KSJsonEncoder'

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

local coveragePath

local child = KSPathUtils.child
local parent = KSPathUtils.parent
local derefOpt = KSTableUtils.derefOpt

local failStubbedTests = false

local stripped = '<<stripped>>'


local albumAssetTable = 'album_asset'
if WFPlatformInfo.isDesktop then
	albumAssetTable = 'album_asset_v2'
end

local kOzWaitMultiplier = 30
local kTimeoutForSendingUpdates = 5

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

local WFCatTrashPartialSyncTest = {}

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

local function setTimeoutAndRetryLimit( rxTest, options, timeoutValue )

	rxTest:setTimeLimit( timeoutValue )

	if not options.mock then
		rxTest:setRetryLimit( 3, 30 )
	end

end

--==============================================================================
-- @mark tests that involve assets with partially synced data (from oz wiki)
--==============================================================================

local function testAddPartialDelete( rxTest, options )

	options = options or {}

	options.aux = options.xmpDevelop

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 15 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc = WFCatTrashTestUtils.setupCloudTrashTestSession( rxTest, options )
	assert( testSession )

	local userCreated = '2018-04-15T18:18:18Z'
	local importTimestamp = '2018-04-15T19:19:19Z'
	local assetCreated = '2018-04-15T20:20:20Z'
	local trashAssetCreated = '2018-04-15T21:21:21Z'
	local expectedUserCreated = userCreated
	local trashPurgeDate

	rxTest:expectSingleValue( catalog:ozUser():purgeAssetsAfterValue()
		:map( function( purgeAssetsAfter )
			trashPurgeDate = KSDate.shiftIso8601DateTime( trashAssetCreated, purgeAssetsAfter )
			return true
		end ),
	true )

	local subtype = 'image'
	local fileName = 'IMG_7763.JPG'
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"

	fileName = options.fileName or fileName
	subtype = options.subtype or subtype
	local trashAssetSubtype = 'deleted_' .. subtype


	local userId = catalog:ozUserId()
	local catalogId = catalog:ozCatalogId()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId

	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local originalFilePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )

	-- First pull gets the album prefetch, catalog prefetch, and an empty pull
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.prefetchReqResp( catalogId ) )

	if options.mock then
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.catalogPrefetchReqResp( catalogId ) )
	end

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.emptyPullReqResp( catalogId ) )
	rxTest:expectOnlyCompleted( puller:getUpdatesOnce() )

	local importSource = WFCatTrashTestUtils.makeImportSource( catalog, fileName, importTimestamp, sha256 )

	-- Add an image asset
	rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMaster( catalog, catalogId, assetId, originalFilePath, subtype, userCreated, importSource )
		:map( function( revId )
			revisionId = revId
			return revId
		end )
	, ':ignore:' )

	local expectedDevelopSettings = options.xmpDevelop and WFCatTrashTestUtils.getXmpDevelopSettings() or nil
	local xmpDevelopFilePath = child( parent( originalFilePath ), 'testXmpDevelop.xmp' )
	local developUserUpdated, _
	if options.xmpDevelop then
		revisionId, _, developUserUpdated = WFCatTrashTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )
		expectedDevelopSettings.userUpdated = developUserUpdated
	end

	local xmpSidecarFilePath = child( parent( originalFilePath ), 'testXmp.xmp' )
	if options.xmpSidecar then
		revisionId = WFCatTrashTestUtils.addXmpSidecarToDocument( rxTest, catalog, assetId, xmpSidecarFilePath )
	end

	local auxMockHttpRequests, auxInfo

	if options.aux then

		revisionId, auxMockHttpRequests, auxInfo = WFCatTrashTestUtils.addAuxToDocument( rxTest, catalog, assetId, options )

		local auxId = auxInfo and next( auxInfo )

		expectedDevelopSettings.masks = {
			[ auxId ] = {
				digest = auxInfo[ auxId ].digest
			}
		}

	end

	-- Send the metadata for the newly added asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, subtype ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Get the metadata for the newly added asset that includes the links for uploading binaries.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetUpdateResponse( assetId, revisionId, fileName, subtype, assetCreated, userCreated, importSource,
								{ master = false, renditions = false, xmpDevelop = false, aux = false },
								expectedDevelopSettings, nil, auxInfo )
	}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	-- Delete asset before binaries (master, xmp and xidecar) are uploaded
	rxTest:expectOnlyCompleted( WFCatTrash.moveAssetsToTrash( catalog, { assetId }, pc ) )

	-- Make sure a deleted_image was created with the correct original info and type/subtype
	local trashAssetId, trashRevId, trashDocValue = WFCatTrashTestUtils.getTrashAssetForDeletedAsset( rxTest, catalog, waitMultiplier, assetId )
	rxTest:expectNotEqual( assetId, trashAssetId ) -- should not have the original asset id
	rxTest:expectEqual( 'asset', trashDocValue.type )
	rxTest:expectEqual( trashAssetSubtype, trashDocValue.subtype )
	rxTest:expectEqual( assetId, derefOpt( trashDocValue, 'original', 'id' ) )
	rxTest:expectEqual( expectedUserCreated, derefOpt( trashDocValue, 'original', 'userCreated' )  )

	-- Get the revisionId of the original deleted asset.
	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:map( function( docValue )
			revisionId = docValue._revId
			return docValue
		end ), ':ignore:' )

	-- Verify we only send up the delete, the trash asset should not be sent if the original asset was already synced up to oz
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteAssetReqResp( catalogId, assetId, revisionId, trashAssetId, trashRevId ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Make sure the original asset was deleted
	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:pluck( '_deleted' ),
	true )

	local waitForRenditions = not options.noWaitForRenditions

	-- Get the trashed asset back in the sync feed with the links to upload the binaries
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 7 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.trashAssetUpdateResponse2( trashAssetId, trashRevId, fileName, trashAssetSubtype,
				trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId,
				{ master = false, renditions = false, xmpDevelop = false, aux = false },
				expectedDevelopSettings, auxInfo ),
		WFCatTrashTestUtils.assetDeletedResponse( assetId, trashAssetId, userId ),
	}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	-- Make sure trash has original
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
		:map( function( docValue )
			local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
			local haveOriginal = originalPath and WFFileUtils.exists( originalPath ) == 'file'
			return haveOriginal
		end ),
	true )

	uploader:startContinuousUploads()

	if options.xmpSidecar then

		trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )
	
		-- /rels/xmp gets uploaded before master is uploaded
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.uploadXmpReqResp( catalogId, trashAssetId, trashRevId, xmpSidecarFilePath ) )

		-- Wait for sidecar to complete if there is one.
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'xmp_sidecar' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()

		local theTrashAssetUpdateResponse = WFCatTrashTestUtils.trashAssetUpdateResponse(
				trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated,
				trashPurgeDate, userCreated, importSource, assetId, false, false,
				false, options.xmpSidecar, expectedDevelopSettings, false, auxInfo )

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller,
				{ theTrashAssetUpdateResponse },
				WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	end

	if options.aux then

		trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )

		for _, request in ipairs( auxMockHttpRequests ) do
			WFCatTrashTestUtils.updateAssetIdInAuxMockHttpRequest( request, assetId, trashAssetId )
			catalogSpec:expectHttpRequest( request )
		end
		
		-- Wait for sidecar to complete if there is one.
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'aux-direct' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()
		
		local theTrashAssetUpdateResponse = WFCatTrashTestUtils.trashAssetUpdateResponse(
				trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated,
				trashPurgeDate, userCreated, importSource, assetId, false, false,
				false, options.xmpSidecar, expectedDevelopSettings, options.aux, auxInfo )

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller,
				{ theTrashAssetUpdateResponse },
				WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	end

	-- Upload binaries to trash asset
	if options.xmpDevelop then
			
		-- /rels/xmpDevelop gets uploaded before master
		trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.uploadXmpDevelopReqResp( catalogId, trashAssetId, trashRevId, xmpDevelopFilePath ) )
		
		-- Wait for sidecar to complete if there is one.
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'xmp_develop' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()

		local theTrashAssetUpdateResponse = WFCatTrashTestUtils.trashAssetUpdateResponse(
				trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated,
				trashPurgeDate, userCreated, importSource, assetId, false, false,
				options.xmpDevelop, options.xmpSidecar, expectedDevelopSettings,
				options.aux, auxInfo )

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller,
				{ theTrashAssetUpdateResponse },
				WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )
		
	end

	trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )

	-- Upload the master
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, trashAssetId, trashRevId, originalFilePath, nil, options.xmpSidecar ) )

	-- Wait for the upload master to complete, (it will upload after the xmp_develop if there is one.
	rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
	:expectDataWithinTime( 5 * waitMultiplier, 1)
	:dispose()

	if waitForRenditions then
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 5 * waitMultiplier, catalogSpec, catalogId, trashAssetId, trashRevId, trashAssetSubtype ), true )
	end
	
	local theTrashAssetUpdateResponse = WFCatTrashTestUtils.trashAssetUpdateResponse(
			trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated,
			trashPurgeDate, userCreated, importSource, assetId, true, waitForRenditions,
			options.xmpDevelop, options.xmpSidecar, expectedDevelopSettings,
			options.aux, auxInfo )

	-- Get the updated metadata with the master link.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller,
			{ theTrashAssetUpdateResponse },
			WFCatTrashTestUtils.stripForAssetUpdate( waitForRenditions ) ), 'success' )

	-- Wait for the update to get applied to the assets docValue
	rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
		:map( type ))
	:expectDataWithinTime( 5 * waitMultiplier, 'string' )
	:dispose()

	-- Wait for the update to get applied to the assets docValue for xmpDevelop
	if options.xmpDevelop then
		rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )
			:map( type ))
		:expectDataWithinTime( 5 * waitMultiplier, 'string' )
		:dispose()
	end

	-- Wait for the update to get applied to the assets docValue for aux
	if options.aux then

		local auxId = auxInfo and next( auxInfo )

		rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/aux/' .. auxId, 'href' )
			:map( type ))
		:expectDataWithinTime( 5 * waitMultiplier, 'string' )
		:dispose()

	end

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddPartial_Delete( rxTest )

-- NMB-9960
--Scenario:
--
--1 Add a photo (Alternate step 1: add photo that has a sidecar file)
--2 Pause Binary Uploader using debug tools panel (new debug option to be written)
--3 Modify the photo with a lot of brush strokes to cause it to need to use external develop settings (we could add a debug tool to show when this happens if needed)
--4 Delete the photo
--5 Turn on the Debug Tools Binary Uploader
--6 View trash on another client and make sure the photo shows up
--
--Expected Result:
--
--Binaries are uploaded to trash
--Trash asset has master, sidecar xmp, and external xmp_develop file.

	testAddPartialDelete( rxTest, { mock = true, xmpDevelop = true, xmpSidecar = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddPartial_Delete_testNightly( rxTest )

-- NMB-9960
--Scenario:
--
--1 Add a photo (Alternate step 1: add photo that has a sidecar file)
--2 Pause Binary Uploader using debug tools panel (new debug option to be written)
--3 Modify the photo with a lot of brush strokes to cause it to need to use external develop settings (we could add a debug tool to show when this happens if needed)
--4 Delete the photo
--5 Turn on the Debug Tools Binary Uploader
--6 View trash on another client and make sure the photo shows up
--
--Expected Result:
--
--Binaries are uploaded to trash
--Trash asset has master, sidecar xmp, and external xmp_develop file.

	testAddPartialDelete( rxTest, { xmpDevelop = true, xmpSidecar = true } )

end

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

local function testAddPartialDeleteRestore( rxTest, options )

	options = options or {}

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 15 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc = WFCatTrashTestUtils.setupCloudTrashTestSession( rxTest, options )
	assert( testSession )

	local userCreated = '2018-04-15T18:18:18Z'
	local importTimestamp = '2018-04-15T19:19:19Z'
	local assetCreated = '2018-04-15T20:20:20Z'
	local trashAssetCreated = '2018-04-15T21:21:21Z'
	local expectedUserCreated = userCreated
	local trashPurgeDate

	rxTest:expectSingleValue( catalog:ozUser():purgeAssetsAfterValue()
		:map( function( purgeAssetsAfter )
			trashPurgeDate = KSDate.shiftIso8601DateTime( trashAssetCreated, purgeAssetsAfter )
			return true
		end ),
	true )

	local subtype = 'image'
	local fileName = 'IMG_7763.JPG'
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"

	fileName = options.fileName or fileName
	subtype = options.subtype or subtype
	local trashAssetSubtype = 'deleted_' .. subtype


	local userId = catalog:ozUserId()
	local catalogId = catalog:ozCatalogId()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId

	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local originalFilePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )

	-- First pull gets the album prefetch, catalog prefetch, and an empty pull
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.prefetchReqResp( catalogId ) )

	if options.mock then
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.catalogPrefetchReqResp( catalogId ) )
	end

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.emptyPullReqResp( catalogId ) )
	rxTest:expectOnlyCompleted( puller:getUpdatesOnce() )

	local importSource = WFCatTrashTestUtils.makeImportSource( catalog, fileName, importTimestamp, sha256 )

	-- Add an image asset
	rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMaster( catalog, catalogId, assetId, originalFilePath, subtype, userCreated, importSource )
		:map( function( revId )
			revisionId = revId
			return revId
		end )
	, ':ignore:' )

	local expectedDevelopSettings = options.xmpDevelop and WFCatTrashTestUtils.getXmpDevelopSettings() or nil
	local xmpDevelopFilePath = child( parent( originalFilePath ), 'testXmpDevelop.xmp' )
	if options.xmpDevelop then
		local developUserUpdated, _
		revisionId, _, developUserUpdated = WFCatTrashTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )
		expectedDevelopSettings.userUpdated = developUserUpdated
	end

	local xmpSidecarFilePath = child( parent( originalFilePath ), 'testXmp.xmp' )
	if options.xmpSidecar then
		revisionId = WFCatTrashTestUtils.addXmpSidecarToDocument( rxTest, catalog, assetId, xmpSidecarFilePath )
	end

	-- Send the metadata for the newly added asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, subtype ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Get the metadata for the newly added asset that includes the links for uploading binaries.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetUpdateResponse( assetId, revisionId, fileName, subtype, assetCreated, userCreated, importSource,
								{ master = false, renditions = false, xmpDevelop = false }, expectedDevelopSettings )
	}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	-- Delete asset before binaries (master, xmp and xidecar) are uploaded
	rxTest:expectOnlyCompleted( WFCatTrash.moveAssetsToTrash( catalog, { assetId }, pc ) )

	-- Make sure the original asset was deleted, and get it's new revision id
	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:map( function( docValue )
			revisionId = docValue._revId
			return docValue
		end )
		:pluck( '_deleted' )
		, true )

	-- Make sure a deleted_image was created with the correct original info and type/subtype
	local trashAssetId, trashRevId, trashDocValue = WFCatTrashTestUtils.getTrashAssetForDeletedAsset( rxTest, catalog, waitMultiplier, assetId )
	rxTest:expectNotEqual( assetId, trashAssetId ) -- should not have the original asset id
	rxTest:expectEqual( 'asset', trashDocValue.type )
	rxTest:expectEqual( trashAssetSubtype, trashDocValue.subtype )
	rxTest:expectEqual( assetId, derefOpt( trashDocValue, '_localOnly', 'universal', 'trash', 'createFrom', 'id' ))
	rxTest:expectEqual( assetId, derefOpt( trashDocValue, 'original', 'id' ) )
	rxTest:expectEqual( expectedUserCreated, derefOpt( trashDocValue, 'original', 'userCreated' )  )

	-- Restore asset before binaries (master, xmp and xidecar) are uploaded
	rxTest:expectOnlyCompleted( WFCatTrash.restoreTrashAssets( catalog, { trashAssetId }, pc ) )

	-- Make sure the trash asset was deleted
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
		:pluck( '_deleted' ),
	true )

	local restoredAssetId, restoredRevId, restoredDocValue = WFCatTrashTestUtils.getNewlyCreatedAsset( rxTest, catalog, waitMultiplier, { assetId, trashAssetId } )
	rxTest:expectEqual( 'asset', restoredDocValue.type )
	rxTest:expectEqual( subtype, restoredDocValue.subtype )
	rxTest:expectEqual( assetId, derefOpt( restoredDocValue, '_localOnly', 'universal', 'trash', 'createFrom', 'id' ) )
	rxTest:expectEqual( revisionId, derefOpt( restoredDocValue, '_localOnly', 'universal', 'trash', 'createFrom', 'revision_id' ) )
	rxTest:expectEqual( nil, derefOpt( restoredDocValue, 'original' ) )

	-- Verify we send up the delete of the original asset, and the create of the restored asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteRestoreAssetReqResp( catalogId, assetId, revisionId, trashAssetId, trashRevId, restoredAssetId, restoredRevId ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 2, kTimeoutForSendingUpdates ) )

	if not options.mock then
		-- wait for the restore to occur on Oz. This can take a bit and sometimes gets split across two puller updates which
		-- will confuse the intermediate results the tests are expecting to see
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilAssetExistsWithLinks( 3 * waitMultiplier, catalogSpec, catalogId, restoredAssetId, {}, options ), true )
	end

	local waitForRenditions = not options.noWaitForRenditions

	-- Get the restored asset back in the sync feed with the links to upload the binaries
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetDeletedResponse( assetId, trashAssetId, userId ),
		WFCatTrashTestUtils.assetDeletedResponse( trashAssetId, restoredAssetId, userId ),
		WFCatTrashTestUtils.restoredAssetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, assetId, false, false, false, false, expectedDevelopSettings )
	} ), 'success' )

	-- Make sure restored asset has original
	rxTest:expectSingleValue( catalog:docStore():docValue( restoredAssetId )
		:map( function( docValue )
			local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
			local haveOriginal = originalPath and WFFileUtils.exists( originalPath ) == 'file'
			return haveOriginal
		end ),
	true )

	if options.xmpSidecar then
		
		restoredRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, restoredAssetId )
		-- /rels/xmp gets uploaded before master
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.uploadXmpReqResp( catalogId, restoredAssetId, restoredRevId, xmpSidecarFilePath ) )

	end

	uploader:startContinuousUploads()

	if options.xmpSidecar then

		-- Wait for sidecar to complete if there is one.
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'xmp_sidecar' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()

		-- Get the updated metadata with the master link.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.restoredAssetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, assetId, false, false, false, options.xmpSidecar, expectedDevelopSettings ),
		}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	end

	if options.xmpDevelop then
	
		restoredRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, restoredAssetId )
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.uploadXmpDevelopReqResp( catalogId, restoredAssetId, restoredRevId, xmpDevelopFilePath ) )
	
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'xmp_develop' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()
		
		-- Get the updated metadata with the master link.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.restoredAssetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, assetId, false, false, options.xmpDevelop, options.xmpSidecar, expectedDevelopSettings ),
		}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	end

	restoredRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, restoredAssetId )

	-- Upload the master
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, restoredAssetId, restoredRevId, originalFilePath, nil, options.xmpSidecar ) )

	-- Wait for the upload master to complete, (it will upload after the xmp_develop if there is one.
	rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
	:expectDataWithinTime( 5 * waitMultiplier, 1)
	:dispose()

	if waitForRenditions then
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 5 * waitMultiplier, catalogSpec, catalogId, restoredAssetId, restoredRevId, subtype ), true )
	end

	-- Get the updated metadata with the master link.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.restoredAssetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, assetId, true, waitForRenditions, options.xmpDevelop, options.xmpSidecar, expectedDevelopSettings ),
	}, WFCatTrashTestUtils.stripForAssetUpdate( waitForRenditions ) ), 'success' )

	-- Wait for the update to get applied to the assets docValue
	rxTest:observe( catalog:docStore():ongoingDocValue( restoredAssetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
		:map( type ))
	:expectDataWithinTime( 5 * waitMultiplier, 'string' )
	:dispose()

	-- Wait for the update to get applied to the assets docValue for xmpDevelop
	if options.xmpDevelop then
		rxTest:observe( catalog:docStore():ongoingDocValue( restoredAssetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )
			:map( type ))
		:expectDataWithinTime( 5 * waitMultiplier, 'string' )
		:dispose()
	end

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddPartial_DeleteRestore( rxTest )

-- NMB-10672
--Scenario:
--
--1 Add a photo (Alternate step 1: add photo that has a sidecar file)
--2 Pause Binary Uploader using debug tools panel (new debug option to be written)
--3 Modify the photo with a lot of brush strokes to cause it to need to use external develop settings (we could add a debug tool to show when this happens if needed)
--4 Delete the photo
--5 Restore the photo
--6 Turn on the Debug Tools Binary Uploader
--7 View restored on another client and make sure the photo shows up
--
--Expected Result:
--
--Binaries are uploaded to restored
--Restored asset has master, sidecar xmp, and external xmp_develop file.

	testAddPartialDeleteRestore( rxTest, { mock = true, xmpDevelop = true, xmpSidecar = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddPartial_DeleteRestore_testNightly( rxTest )

	testAddPartialDeleteRestore( rxTest, { xmpDevelop = true, xmpSidecar = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddPartial_Sync_testOtherDeleteSyncRestore( rxTest )

-- Add an asset that requires the use of external develop settings go offline
-- before the master or xmp get uploaded delete the asset from LrWeb then restore
-- the asset. Note other LrCC clients won't show the asset until it has the
-- master uploaded.
-- Go online and make sure the edits get applied to the asset.

-- Can also add a sidecar xmp file which isn't uploaded until master completes.

	rxTest:expectEqual( false, failStubbedTests )
end

--==============================================================================
-- @mark tests that involve assets with partially synced metadata (confirmation not received from Oz)
--==============================================================================

local function testAddDeletePartialSync( rxTest, options )

	options = options or {}

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 10 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc = WFCatTrashTestUtils.setupCloudTrashTestSession( rxTest, options )
	assert( testSession )

	local userCreated = '2018-04-15T18:18:18Z'
	local importTimestamp = '2018-04-15T19:19:19Z'
	local assetCreated = '2018-04-15T20:20:20Z'
	local trashAssetCreated = '2018-04-15T21:21:21Z'
	local expectedUserCreated = userCreated
	local trashPurgeDate

	rxTest:expectSingleValue( catalog:ozUser():purgeAssetsAfterValue()
		:map( function( purgeAssetsAfter )
			trashPurgeDate = KSDate.shiftIso8601DateTime( trashAssetCreated, purgeAssetsAfter )
			return true
		end ),
	true )

	local subtype = 'image'
	local fileName = 'IMG_7763.JPG'
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"

	fileName = options.fileName or fileName
	subtype = options.subtype or subtype
	local trashAssetSubtype = 'deleted_' .. subtype

	local docStore = catalog:docStore()
	local userId = catalog:ozUserId()
	local catalogId = catalog:ozCatalogId()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId

	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local originalFilePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )

	-- First pull gets the album prefetch, catalog prefetch, and an empty pull
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.prefetchReqResp( catalogId ) )

	if options.mock then
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.catalogPrefetchReqResp( catalogId ) )
	end

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.emptyPullReqResp( catalogId ) )
	rxTest:expectOnlyCompleted( puller:getUpdatesOnce() )

	local importSource = WFCatTrashTestUtils.makeImportSource( catalog, fileName, importTimestamp, sha256 )

	-- Add an image asset
	rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMaster( catalog, catalogId, assetId, originalFilePath, subtype, userCreated, importSource )
		:map( function( revId )
			revisionId = revId
			return revId
		end )
	, ':ignore:' )

	-- Delete asset before binaries (master, xmp and xidecar) are uploaded
	rxTest:expectOnlyCompleted( WFCatTrash.moveAssetsToTrash( catalog, { assetId }, pc ) )

	-- Make sure a deleted_image was created with the correct original info and type/subtype
	local trashAssetId, trashRevId, trashDocValue = WFCatTrashTestUtils.getTrashAssetForDeletedAsset( rxTest, catalog, waitMultiplier, assetId )
	rxTest:expectNotEqual( assetId, trashAssetId ) -- should not have the original asset id
	rxTest:expectEqual( 'asset', trashDocValue.type )
	rxTest:expectEqual( trashAssetSubtype, trashDocValue.subtype )
	rxTest:expectEqual( assetId, derefOpt( trashDocValue, 'original', 'id' ) )
	rxTest:expectEqual( expectedUserCreated, derefOpt( trashDocValue, 'original', 'userCreated' )  )

	-- Make sure we create trash asset on Oz, but not wait the update response
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, trashAssetSubtype ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Make sure the original asset was deleted
	rxTest:expectSingleValue( docStore:docValue( assetId )
		:pluck( '_deleted' ),
	true )

	if options.delTrash then

		-- Delete trash asset before receiving trash update from Oz
		rxTest:expectOnlyCompleted( WFCatTrash.deleteTrashAssets( catalog, { trashAssetId }, pc ) )

		-- Make sure the trash asset is deleted
		rxTest:expectSingleValue( docStore:docValue( trashAssetId ):pluck( '_deleted' ), true )

		-- Get the trashed asset back in the sync feed, we should ignore the update
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false ),
		}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

		-- Conflict is expected when trash asset is deleted locally, but receive update from Oz
		rxTest:expectSingleValue( docStore:docValue( trashAssetId ):pluck( '_conflict' ), true)

		-- Wait the conflict to be solved by conflict resolver
		rxTest:observe( docStore:ongoingDocValue( trashAssetId ):pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

		-- Get the latest revisionId for trash asset
		rxTest:expectSingleValue( docStore:docValue( trashAssetId )
			:map( function( trashDocValue )
				trashRevId = trashDocValue._revId
				return trashDocValue
			end )
			:pluck( '_deleted' ),
		true)

		-- Make sure we send purge trash to Oz after receiving trash update
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteTrashReqResp( catalogId, trashAssetId, trashRevId ) )

		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

		-- Get the trash delete success from Oz
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.assetDeletedResponse( trashAssetId, nil, userId ),
		} ), 'success' )

	elseif options.restore then

		local restoredAssetId, restoredRevId, restoredDocValue
		local restoredCreateWithRevId, restoredUserUpdated

		-- restore trash asset before receiving trash update from Oz
		rxTest:expectOnlyCompleted( WFCatTrash.restoreTrashAssets( catalog, { trashAssetId }, pc ) )

		restoredAssetId, restoredRevId, restoredDocValue = WFCatTrashTestUtils.getNewlyCreatedAsset( rxTest, catalog, waitMultiplier, { assetId, trashAssetId } )
		rxTest:expectEqual( 'asset', restoredDocValue.type )
		rxTest:expectEqual( subtype, restoredDocValue.subtype )
		rxTest:expectEqual( nil, derefOpt( restoredDocValue, 'original' ) )
		restoredCreateWithRevId = WFCatTrashUtils.getCreateWithRevisionId( restoredDocValue )
		restoredUserUpdated = restoredDocValue.userUpdated

		-- Make sure the trash asset is deleted
		rxTest:expectSingleValue( docStore:docValue( trashAssetId ):pluck( '_deleted' ), true )

		-- Get the trashed asset back in the sync feed, we should ignore the update
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false ),
		}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

		-- Conflict is expected when trash asset is deleted locally, but receive update from Oz
		rxTest:expectSingleValue( docStore:docValue( trashAssetId ):pluck( '_conflict' ), true)

		-- Wait the conflict to be solved by conflict resolver
		rxTest:observe( docStore:ongoingDocValue( trashAssetId ):pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

		-- Get the latest revisionId for trash asset
		rxTest:expectSingleValue( docStore:docValue( trashAssetId )
			:map( function( trashDocValue )
				trashRevId = trashDocValue._revId
				return trashDocValue
			end )
			:pluck( '_deleted' ),
		true)

		-- Send create restored asset with trash info.
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushRestoreAsset2ReqResp( catalogId, restoredAssetId, restoredCreateWithRevId, trashAssetId, trashRevId, restoredUserUpdated ) )

		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

		-- Get the trash delete success from Oz
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.restoredAssetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, assetId ),
			WFCatTrashTestUtils.assetDeletedResponse( trashAssetId, restoredAssetId, userId ),
		}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

		-- Make sure the restored asset has local files
		rxTest:expectSingleValue( docStore:docValue( restoredAssetId )
			:map( function( docValue )
				restoredRevId = docValue._revId

				local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
				return originalPath and WFFileUtils.exists( originalPath ) == 'file'
			end ),
		true )

		-- Upload the master
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, restoredAssetId, restoredRevId, originalFilePath ) )

		uploader:startContinuousUploads()

		-- Wait for the upload master to complete, it will upload after the xmp_develop if there is one.
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 5 * waitMultiplier, catalogSpec, catalogId, restoredAssetId, restoredRevId, subtype ), true )

		-- Get the updated metadata with the master link.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.restoredAssetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, assetId, true, true, options.xmpDevelop )
		}, WFCatTrashTestUtils.stripForAssetUpdate( true ) ), 'success' )

		-- Wait for the update to get applied to the assets docValue
		rxTest:observe( docStore:ongoingDocValue( restoredAssetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
			:map( type ))
		:expectDataWithinTime( 5 * waitMultiplier, 'string' )
		:dispose()

		-- Make sure the original asset was deleted
		rxTest:expectSingleValue( docStore:docValue( assetId ):pluck( '_deleted' ), true )

		-- Make sure the trash asset was deleted
		rxTest:expectSingleValue( docStore:docValue( trashAssetId ):pluck( '_deleted' ), true )

	end

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddDeletePartialSync_DelTrash( rxTest )

-- NMB-10416
--Scenario:
--
--1 Pause Sync
--2 Add a photo
--3 Delete the photo
--4 Turn on the Debug tools metadataPusher to push the trash asset creation
--5 Purge Trash
--6 Turn on Sync
--
--Expected Result:
--
--The trash asset should be purged on Oz and on every device

	testAddDeletePartialSync( rxTest, { mock = true, delTrash = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddDeletePartialSync_DelTrash_testNightly( rxTest )

-- NMB-10416

	testAddDeletePartialSync( rxTest, { delTrash = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddDeletePartialSync_Restore( rxTest )

-- NMB-10414
--Scenario:
--
--1 Pause Sync
--2 Add a photo
--3 Delete the photo
--4 Turn on the Debug tools metadataPusher to push the trash asset creation
--5 Restore asset
--6 Turn on Sync
--
--Expected Result:
--
--The restored asset should be in All Photo grid, and trash asset is removed

	testAddDeletePartialSync( rxTest, { mock = true, restore = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddDeletePartialSync_Restore_testNightly( rxTest )

-- NMB-10414

	testAddDeletePartialSync( rxTest, { restore = true } )

end

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

local function testAddPartialSyncDelete( rxTest, options )

	options = options or {}

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 10 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc = WFCatTrashTestUtils.setupCloudTrashTestSession( rxTest, options )
	assert( testSession )

	local userCreated = '2018-04-15T18:18:18Z'
	local importTimestamp = '2018-04-15T19:19:19Z'
	local assetCreated = '2018-04-15T20:20:20Z'
	local trashAssetCreated = '2018-04-15T21:21:21Z'
	local expectedUserCreated = userCreated
	local trashPurgeDate

	rxTest:expectSingleValue( catalog:ozUser():purgeAssetsAfterValue()
		:map( function( purgeAssetsAfter )
			trashPurgeDate = KSDate.shiftIso8601DateTime( trashAssetCreated, purgeAssetsAfter )
			return true
		end ),
	true )

	local subtype = 'image'
	local fileName = 'IMG_7763.JPG'
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"

	fileName = options.fileName or fileName
	subtype = options.subtype or subtype
	local trashAssetSubtype = 'deleted_' .. subtype


	local userId = catalog:ozUserId()
	local catalogId = catalog:ozCatalogId()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId

	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local originalFilePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )

	-- First pull gets the album prefetch, catalog prefetch, and an empty pull
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.prefetchReqResp( catalogId ) )

	if options.mock then
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.catalogPrefetchReqResp( catalogId ) )
	end

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.emptyPullReqResp( catalogId ) )
	rxTest:expectOnlyCompleted( puller:getUpdatesOnce() )

	local importSource = WFCatTrashTestUtils.makeImportSource( catalog, fileName, importTimestamp, sha256 )

	-- Add an image asset
	rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMaster( catalog, catalogId, assetId, originalFilePath, subtype, userCreated, importSource )
		:map( function( revId )
			revisionId = revId
			return revId
		end )
	, ':ignore:' )

	-- Send the metadata for the newly added asset, but don't wait for the response
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, subtype ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Delete asset before binaries (master, xmp and xidecar) are uploaded
	rxTest:expectOnlyCompleted( WFCatTrash.moveAssetsToTrash( catalog, { assetId }, pc ) )

	-- Make sure a deleted_image was created with the correct original info and type/subtype
	local trashAssetId, trashRevId, trashDocValue = WFCatTrashTestUtils.getTrashAssetForDeletedAsset( rxTest, catalog, waitMultiplier, assetId )
	rxTest:expectNotEqual( assetId, trashAssetId ) -- should not have the original asset id
	rxTest:expectEqual( 'asset', trashDocValue.type )
	rxTest:expectEqual( trashAssetSubtype, trashDocValue.subtype )
	rxTest:expectEqual( assetId, derefOpt( trashDocValue, 'original', 'id' ) )
	rxTest:expectEqual( expectedUserCreated, derefOpt( trashDocValue, 'original', 'userCreated' )  )

	-- Make sure the original asset was deleted
	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:pluck( '_deleted' ),
	true )

	if options.pullConflict then

		-- Get the metadata for original asset
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.assetUpdateResponse( assetId, revisionId, fileName, subtype, assetCreated, userCreated, importSource,
									{ master = false, renditions = false, xmpDevelop = options.xmpDevelop } )
		}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

		-- Conflict is expected when original asset is deleted locally, but receive update from Oz
		rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
			:map( function( docValue )
				return docValue
			end )
			:pluck( '_conflict' ),
		true)

		-- Wait the conflict to be solved by conflict resolver
		rxTest:observe( catalog:docStore():ongoingDocValue( assetId ):pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

	end

	-- Get the latest revisionId for original
		rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
			:map( function( docValue )
				revisionId = docValue._revId
				return docValue
			end )
			:pluck( '_deleted' ),
		true)

	-- Make sure we send delete original asset with trash info, based on pusher counter data.
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteAssetReqResp( catalogId, assetId, revisionId, trashAssetId, trashRevId ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Get the trashed asset back in the sync feed with the links to upload the original
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 7 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false, options.xmpDevelop ),
		WFCatTrashTestUtils.assetDeletedResponse( assetId, trashAssetId, userId ),
	}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false ) ), 'success' )

	-- Update the trashRevId with the latest revision id before issuing the WFCatTrashTestUtils.createMasterReqResp
	trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )

	-- Make sure master file exists with trash asset
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
		:map( function( docValue )
			local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
			local haveOriginal = originalPath and WFFileUtils.exists( originalPath ) == 'file'

			return haveOriginal
		end ),
	true )

	-- Upload the master
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, trashAssetId, trashRevId, originalFilePath ) )
	uploader:startContinuousUploads()

	-- Wait for the upload master to complete, (it will upload after the xmp_develop if there is one.
	rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
	:expectDataWithinTime( 5 * waitMultiplier, 1)
	:dispose()

	-- Wait for oz to generate the proxy and renditions before requesting next update
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 5 * waitMultiplier, catalogSpec, catalogId, trashAssetId, trashRevId, trashAssetSubtype ), true )

	-- Get the metadata for the newly added asset that includes the links for uploading binaries.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, true, true )
	}), 'success' )

	-- Wait for the update to get applied to the assets docValue
	rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
		:map( type ))
	:expectDataWithinTime( 5 * waitMultiplier, 'string' )
	:dispose()

	-- Make sure the original asset was deleted
	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:pluck( '_deleted' ),
	true )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddPartialSyncDelete( rxTest )

--Scenario: (LRMI-4190377 / NMB-10383)
--
--1 Pause Sync
--2 Add a photo
--3 Turn on the Debug tools metadataPusher to push the trash asset creation
--4 Delete asset
--6 Turn on Sync
--
--Expected Result:
--
--The trash asset should be in trash can, and original asset is deleted

	testAddPartialSyncDelete( rxTest, { mock = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddPartialSyncDelete_testNightly( rxTest )

-- LRMI-4190377 / NMB-10383

	testAddPartialSyncDelete( rxTest )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddPartialSyncDelete_PullConflict_testNightly( rxTest )

-- LRMI-4190377 / NMB-10383

	testAddPartialSyncDelete( rxTest, { pullConflict = true } )

end

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

local function testAddPartialSyncAddAssocDocsPartialSyncDelete( rxTest, options )

	options = options or {}

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 20 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc = WFCatTrashTestUtils.setupCloudTrashTestSession( rxTest, options )
	assert( testSession )

	local userCreated = '2018-04-15T18:18:18Z'
	local importTimestamp = '2018-04-15T19:19:19Z'
	local assetCreated = '2018-04-15T20:20:20Z'
	local trashAssetCreated = '2018-04-15T21:21:21Z'

	local subtype = 'image'
	local fileName = 'IMG_7763.JPG'
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"

	fileName = options.fileName or fileName
	subtype = options.subtype or subtype
	local trashAssetSubtype = 'deleted_' .. subtype


	local userId = catalog:ozUserId()
	local catalogId = catalog:ozCatalogId()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId

	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local originalFilePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )

	-- First pull gets the album prefetch, catalog prefetch, and an empty pull
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.prefetchReqResp( catalogId ) )

	if options.mock then
		-- TODO [kcarlson 2018-07-09]:  Not sure why this doesn't get issued when using oz
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.catalogPrefetchReqResp( catalogId ) )
	end

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.emptyPullReqResp( catalogId ) )
	rxTest:expectOnlyCompleted( puller:getUpdatesOnce() )

	local importSource = WFCatTrashTestUtils.makeImportSource( catalog, fileName, importTimestamp, sha256 )

-- Step 0: Create album and space

	-- Add album
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local albumRevId
	local albumName = 'Album1'
	rxTest:expectSingleValue( catalog:docStore():applyOverlay {
			_id = albumId,
			type = 'album',
			subtype = 'collection',
			name = albumName,
		}
		:map( function( revId )
			albumRevId = revId
			return revId
		end )
	, ':ignore:' )

	-- Sync up the album
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAlbumReqResp( catalogId, albumId, albumName ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Get the metadata for the newly added album
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.albumUpdateResponse( catalogId, albumId, albumRevId, albumName )
	}), 'success' )

	-- Add spce
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local spaceAlbumId = KSIdGenerator.uuidInLowercaseHex()

	local now = KSDate.timeInIso8601Format()

	local spaceAlbumRevId
	rxTest:expectSingleValue( catalog:docStore():bulkApplyOverlay(
		{
			label = 'oz',
			{
				_id = spaceId,
				userCreated = now,
				userUpdated = now,
				type = 'space',
				subtype = 'shared',
				private = false,
				download = false,
				metadata = false,
				location = false,
				presentationFilters = true,
			},
			{
				_id = spaceAlbumId,
				type = 'space_album',
				space = { id = spaceId },
				album = { id = albumId },
			}
		} ) :map( function( overlayResults )
				spaceAlbumRevId = derefOpt( overlayResults, 2, 'revId' )
				return overlayResults
			end )
	, ':ignore:' )

	-- Now create the space via a direct put as MetadataPusher doesn't handle 'space' docs.
	local createSpaceUrl = string.format( '%s/spaces/%s', catalogSpec:catalogHref(), spaceId )
	local createSpaceOptions = {
		requestMethod = 'PUT',
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
		},
	}
	local createSpaceData = KSJsonEncoder.encodeOzUploadJson( {
		payload = {
			private = false,
			download = false,
			metadata = false,
			location = false,
			presentationFilters = true,
		},
		subtype = 'shared',
		resources = {
			{
				id = albumId,
				type = 'album',
				space_album_id = spaceAlbumId,
			}
		}
	} )

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.putCreateSpaceReqResp( createSpaceUrl, createSpaceData, createSpaceOptions ) )
	rxTest:expectSingleValue(
		catalogSpec:put( createSpaceUrl, createSpaceData, createSpaceOptions )
		:map( function( responseData, headers )
			return headers.status_code
		end )
	, '201' )

	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.spaceAlbumUpdateResponse( catalogId, spaceAlbumId, spaceAlbumRevId, spaceId, albumId ),
		WFCatTrashTestUtils.spaceUpdateResponse( catalogId, spaceId )
	}), 'success' )


-- Step 1: Add asset and album_asset

	-- Add an image asset
	rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMaster( catalog, catalogId, assetId, originalFilePath, subtype, userCreated, importSource )
		:map( function( revId )
			revisionId = revId
			return revId
		end )
	, ':ignore:' )

	local expectedDevelopSettings = options.xmpDevelop and WFCatTrashTestUtils.getXmpDevelopSettings() or nil
	local xmpDevelopFilePath = child( parent( originalFilePath ), 'testXmpDevelop.xmp' )
	if options.xmpDevelop then
		local developUserUpdated, _
		revisionId, _, developUserUpdated = WFCatTrashTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )
		expectedDevelopSettings.userUpdated = developUserUpdated
	end

	local xmpSidecarFilePath = child( parent( originalFilePath ), 'testXmp.xmp' )
	if options.xmpSidecar then
		revisionId = WFCatTrashTestUtils.addXmpSidecarToDocument( rxTest, catalog, assetId, xmpSidecarFilePath )
	end

	-- Add album_asset
	local albumAssetId = KSIdGenerator.uuidInLowercaseHex()
	local albumAssetRevId
	rxTest:expectSingleValue( catalog:docStore():applyOverlay {
			_id = albumAssetId,
			type = 'album_asset',
			album = { id = albumId },
			asset = { id = assetId }
		}
		:map( function( revId )
			albumAssetRevId = revId
			return revId
		end )
	, ':ignore:' )

-- Step 2: Add Assoc docs and push create request to Oz. Don't pull confirmation

	-- Add comment
	local commentId = KSIdGenerator.uuidInLowercaseHex()
	local commentBody = "Comment1 Mumble, mumble, mumble"
	local commentRevId
	rxTest:expectSingleValue( catalog:docStore():applyOverlay {
			type = 'comment',
			subtype = 'user',
			_id = commentId,
			body = commentBody,
			space = { id = spaceId },
			asset = { id = assetId },
			_localOnly = { created = '2018-12-15T21:21:21Z' },
		}
		:map( function( revId )
			commentRevId = revId
			return revId
		end )
	, ':ignore:' )

	-- Add favorite
	local favoriteId = KSIdGenerator.uuidInLowercaseHex()
	local favoriteRevId
	rxTest:expectSingleValue( catalog:docStore():applyOverlay {
			type = 'favorite',
			subtype = 'user',
			_id = favoriteId,
			space = { id = spaceId },
			asset = { id = assetId },
			_localOnly = { created = '2018-12-15T21:21:21Z' },
		}
		:map( function( revId )
			favoriteRevId = revId
			return revId
		end )
	, ':ignore:' )

	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetAndAssocDocsReqResp( catalogId, assetId, fileName, subtype, albumId, spaceId, commentId, commentBody, favoriteId ) )

	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 4, kTimeoutForSendingUpdates ) )

	-- Make sure the album_asset, comment and favorite are correctly stored
	rxTest:observe( catalog:indexStore():query {
		sql = [[SELECT assetId FROM ]] .. albumAssetTable .. [[ where albumId == ?]], albumId,
		quiesceUntilUpToDate = true,
	})
	:expectData( { { assetId } } )
	:dispose()

-- Step 3: Delete Asset

	-- Delete the asset which is on Oz
	rxTest:expectOnlyCompleted( WFCatTrash.moveAssetsToTrash( catalog, { assetId }, pc ) )

	-- Make sure a deleted_image was created
	local trashAssetId, trashRevId = WFCatTrashTestUtils.getTrashAssetForDeletedAsset( rxTest, catalog, waitMultiplier, assetId )
	rxTest:expectEqual( 'string', type( trashAssetId ) )
	rxTest:expectEqual( 'string', type( trashRevId ) )

	local trashUserCreated, trashPurgeDate
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
		:map( function( docValue )
			trashUserCreated = docValue.userCreated
			trashPurgeDate = docValue.purgeDate
			return docValue
		end )
	, ':ignore:' )

	-- Make sure comment is against trash asset
	rxTest:expectSingleValue( catalog:docStore():docValue( commentId )
		:map( function( commentDocValue )
			return commentDocValue.asset.id == trashAssetId
		end ),
	true)

	-- Make sure favorite is against trash asset
	rxTest:expectSingleValue( catalog:docStore():docValue( favoriteId )
		:map( function( favoriteDocValue )
			return favoriteDocValue.asset.id == trashAssetId
		end ),
	true)


-- Step 4: Push changes / Sync down confirmation of assoc docs. Solve conflict

	if options.pullConflict then

		-- Clients receive confirmation before pushing updates. Should resolve conflict and then push correct updates

		-- Get the confirmation for asset and assoc docs: album_asset, comment and favorite
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.assetUpdateResponse( assetId, revisionId, fileName, subtype, assetCreated, userCreated, importSource,
									{ master = false, renditions = false, xmpDevelop = false }, expectedDevelopSettings ),
			WFCatTrashTestUtils.albumAssetUpdateResponse( albumAssetId, albumAssetRevId, albumId, assetId ),
			WFCatTrashTestUtils.commentUpdateResponse( catalogId, commentId, commentRevId, spaceId, assetId, commentBody ),
			WFCatTrashTestUtils.favoriteUpdateResponse( catalogId, favoriteId, favoriteRevId, spaceId, assetId ),
			WFCatTrashTestUtils.feedbackUserUpdate(),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		-- Conflict is expected when original asset is deleted locally, but receive update from Oz
		rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
			:map( function( assetDocValue )
				revisionId = assetDocValue._revId
				return assetDocValue
			end )
			:pluck( '_conflict' ),
		true)

		-- Conflict is expected when album_asset is updated locally, but receive old update from Oz
		rxTest:expectSingleValue( catalog:docStore():docValue( albumAssetId )
			:map( function( albumAssetDocValue )
				return albumAssetDocValue
			end )
			:pluck( '_conflict' ),
		true)

		-- Conflict is expected when comment is updated locally, but receive old update from Oz
		rxTest:expectSingleValue( catalog:docStore():docValue( commentId )
			:map( function( commentDocValue )
				return commentDocValue
			end )
			:pluck( '_conflict' ),
		true)

		-- Conflict is expected when favorite is updated locally, but receive old update from Oz
		rxTest:expectSingleValue( catalog:docStore():docValue( favoriteId )
			:map( function( favoriteDocValue )
				return favoriteDocValue
			end )
			:pluck( '_conflict' ),
		true)

		-- Wait for the conflict to be resolved
		rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

		-- Wait for the conflict to be resolved
		rxTest:observe( catalog:docStore():ongoingDocValue( albumAssetId )
		:map( function( albumAssetDocValue )
			albumAssetRevId = albumAssetDocValue._revId
			return albumAssetDocValue
		end )
		:pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

		-- Wait for the conflict to be resolved
		rxTest:observe( catalog:docStore():ongoingDocValue( commentId ):pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

		-- Wait for the conflict to be resolved
		rxTest:observe( catalog:docStore():ongoingDocValue( favoriteId ):pluck( '_conflict' ) )
		:expectDataWithinTime( 5 * waitMultiplier, nil )
		:dispose()

		-- Sync up delete original asset and album_asset update (don't update favorite and comment if client has _serverProvidedMetadata)
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteAssetReqResp( catalogId, assetId, revisionId, trashAssetId, trashRevId ) )

		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

		-- Get the confirmation for original asset deletion, trash asset update.
		-- New album_asset, comment and favorite update, and old album_asset, comment and favorite update error and delete
		local newAlbumAssetId = KSIdGenerator.uuidInLowercaseHex()
		local newAlbumAssetRevId = KSIdGenerator.uuidInLowercaseHex()
		local newCommentId = KSIdGenerator.uuidInLowercaseHex()
		local newCommentRevId = KSIdGenerator.uuidInLowercaseHex()
		local newFavoriteId = KSIdGenerator.uuidInLowercaseHex()
		local newFavoriteRevId = KSIdGenerator.uuidInLowercaseHex()

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false, false, false, expectedDevelopSettings ),
			WFCatTrashTestUtils.assetDeletedResponse( assetId, trashAssetId, userId ),
			WFCatTrashTestUtils.albumAssetUpdateResponse( newAlbumAssetId, newAlbumAssetRevId, albumId, trashAssetId ),
			WFCatTrashTestUtils.commentUpdateResponse( catalogId, newCommentId, newCommentRevId, spaceId, trashAssetId, commentBody ),
			WFCatTrashTestUtils.favoriteUpdateResponse( catalogId, newFavoriteId, newFavoriteRevId, spaceId, trashAssetId ),
			WFCatTrashTestUtils.albumAssetDeletedResponse( albumAssetId, userId, newAlbumAssetId ),
			WFCatTrashTestUtils.commentDeletedResponse( commentId, userId, newCommentId ),
			WFCatTrashTestUtils.favoriteDeletedResponse( favoriteId, userId, assetId, newFavoriteId ),
		}, function( mockDataAsTable, ozDataAsTable, ignorePartialUpdates )

				-- Remove the sporadic feedback user / space update before running through
				-- WFCatTrashTestUtils.stripUpdatesAndLinks to make sure things line up properly.
				local ozUpdateTable = derefOpt( ozDataAsTable, 'updates' ) or {}
				WFCatTrashTestUtils.removeSporadicSpaceOrFeedbackFromOzUpdates( ozUpdateTable )

				mockDataAsTable, ozDataAsTable = WFCatTrashTestUtils.stripUpdatesAndLinks( mockDataAsTable, ozDataAsTable, ignorePartialUpdates )
				local mockUpdateTable = derefOpt( mockDataAsTable, 'updates' ) or {}
				ozUpdateTable = derefOpt( ozDataAsTable, 'updates' ) or {}
				if #mockUpdateTable == 0 or ( ignorePartialUpdates and #ozUpdateTable < #mockUpdateTable ) then
					return {}, {} -- don't compare partial updates
				end

				for i, update in pairs( mockUpdateTable ) do
					if update.type == "album_asset" or update.type == "comment" or update.type == "favorite" then
						if update.deleted then
							WFCatTrashTestUtils.stripBothUpdateFields( stripped, mockUpdateTable, ozUpdateTable, i, "new_id" )
						else
							if update.type == "album_asset" then
								newAlbumAssetId = derefOpt( ozUpdateTable[i], "id" )
							elseif update.type == "comment" then
								newCommentId = derefOpt( ozUpdateTable[i], "id" )
							elseif update.type == "favorite" then
								newFavoriteId = derefOpt( ozUpdateTable[i], "id" )
							end

							WFCatTrashTestUtils.stripBothUpdateFields( stripped, mockUpdateTable, ozUpdateTable, i, "id" )
							WFCatTrashTestUtils.stripBothUpdateFields( stripped, mockUpdateTable, ozUpdateTable, i, "links", "self", "href" )
						end
					elseif update.type == "error" then
						WFCatTrashTestUtils.stripBothUpdateFields( stripped, mockUpdateTable, ozUpdateTable, i, "id" )
					end

				end

				WFCatTrashTestUtils.stripUpdatesAndLinks( mockDataAsTable, ozDataAsTable, ignorePartialUpdates )

				return mockDataAsTable, ozDataAsTable

			end
		), 'success' )

		rxTest:sleep( .5 )

		-- Make sure there's no more update pushed
		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 0 ) )

		albumAssetId = newAlbumAssetId
		commentId = newCommentId
		favoriteId = newFavoriteId

	elseif options.resourceExistsError then

		-- Only mocked unit test, because we cannot predict Oz response to be assetGoneError or ResourceExistsError for comment and favorite

		-- Sync up delete original asset and assoc docs create
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteAssetAndCreateAssocsReqResp( catalogId, assetId, trashAssetId, trashRevId, spaceId, albumId, commentId, commentBody, favoriteId ) )

		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 4, kTimeoutForSendingUpdates ) )

		-- Get the confirmation for original asset deletion, trash asset update.
		-- New album_asset, comment and favorite update, and old album_asset, comment and favorite update error and delete
		local newAlbumAssetId = KSIdGenerator.uuidInLowercaseHex()
		local newAlbumAssetRevId = KSIdGenerator.uuidInLowercaseHex()
		local newCommentId = KSIdGenerator.uuidInLowercaseHex()
		local newCommentRevId = KSIdGenerator.uuidInLowercaseHex()
		local newFavoriteId = KSIdGenerator.uuidInLowercaseHex()
		local newFavoriteRevId = KSIdGenerator.uuidInLowercaseHex()

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false, false, false, expectedDevelopSettings ),
			WFCatTrashTestUtils.assetDeletedResponse( assetId, trashAssetId, userId ),

			WFCatTrashTestUtils.joinIdAlreadyInUseErrorUpdateResponse( "create", albumAssetId, albumAssetRevId, "album_asset" ),
			WFCatTrashTestUtils.resourceExistsErrorUpdateResponse( "create", commentId, commentRevId, "comment", catalogId ),
			WFCatTrashTestUtils.resourceExistsErrorUpdateResponse( "create", favoriteId, favoriteRevId, "favorite", catalogId ),

			WFCatTrashTestUtils.albumAssetUpdateResponse( newAlbumAssetId, newAlbumAssetRevId, albumId, trashAssetId ),
			WFCatTrashTestUtils.commentUpdateResponse( catalogId, newCommentId, newCommentRevId, spaceId, trashAssetId, commentBody ),
			WFCatTrashTestUtils.favoriteUpdateResponse( catalogId, newFavoriteId, newFavoriteRevId, spaceId, trashAssetId ),

			WFCatTrashTestUtils.albumAssetDeletedResponse( albumAssetId, userId, newAlbumAssetId ),
			WFCatTrashTestUtils.commentDeletedResponse( commentId, userId, newCommentId ),
			WFCatTrashTestUtils.favoriteDeletedResponse( favoriteId, userId, assetId, newFavoriteId ),

			WFCatTrashTestUtils.feedbackUserUpdate(),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		rxTest:sleep( .5 )

		-- Make sure there's no more update pushed
		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 0 ) )

		albumAssetId = newAlbumAssetId
		commentId = newCommentId
		favoriteId = newFavoriteId

	elseif options.assetGoneError then

		-- Only mocked unit test, because we cannot predict Oz response to be assetGoneError or ResourceExistsError for comment and favorite

		-- Sync up delete original asset and assoc docs create
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteAssetAndCreateAssocsReqResp( catalogId, assetId, trashAssetId, trashRevId, spaceId, albumId, commentId, commentBody, favoriteId ) )

		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 4, kTimeoutForSendingUpdates ) )

		-- Get the confirmation for original asset deletion, trash asset update.
		-- New album_asset, comment and favorite update, and old album_asset, comment and favorite update error and delete
		local newAlbumAssetId = KSIdGenerator.uuidInLowercaseHex()
		local newAlbumAssetRevId = KSIdGenerator.uuidInLowercaseHex()

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashAssetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false, false, false, expectedDevelopSettings ),
			WFCatTrashTestUtils.assetDeletedResponse( assetId, trashAssetId, userId ),

			WFCatTrashTestUtils.joinIdAlreadyInUseErrorUpdateResponse( "create", albumAssetId, albumAssetRevId, "album_asset" ),
			WFCatTrashTestUtils.assetGoneErrorUpdateResponse( commentId, commentRevId, 'comment', trashAssetId ),
			WFCatTrashTestUtils.assetGoneErrorUpdateResponse( favoriteId, favoriteRevId, 'favorite', trashAssetId ),

			WFCatTrashTestUtils.albumAssetUpdateResponse( newAlbumAssetId, newAlbumAssetRevId, albumId, trashAssetId ),
			WFCatTrashTestUtils.commentUpdateResponse( catalogId, commentId, commentRevId, spaceId, trashAssetId, commentBody ),
			WFCatTrashTestUtils.favoriteUpdateResponse( catalogId, favoriteId, favoriteRevId, spaceId, trashAssetId ),

			WFCatTrashTestUtils.albumAssetDeletedResponse( albumAssetId, userId, newAlbumAssetId ),

			WFCatTrashTestUtils.feedbackUserUpdate(),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		rxTest:sleep( .5 )

		-- Make sure there's no more update pushed
		rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 0 ) )

		albumAssetId = newAlbumAssetId

	end


-- Step 5:
--		Sync up assoc doc update and upload binaries.
-- 		Make sure trash asset is with correct assoc docs and binaries

	-- Make sure the album_asset, comment and favorite are correctly against trash asset
	rxTest:observe( catalog:indexStore():query {
		sql = [[SELECT assetId FROM ]] .. albumAssetTable .. [[ where albumId == ?]], albumId,
		quiesceUntilUpToDate = true,
	})
	:expectData( { { trashAssetId } } )
	:dispose()

	rxTest:observe( catalog:indexStore():query {
		sql = [[SELECT docId, assetId FROM spacefavorite where spaceId == ?]], spaceId,
		quiesceUntilUpToDate = true,
	})
	:expectData( { { favoriteId, trashAssetId } } )
	:dispose()

	rxTest:observe( catalog:indexStore():query {
		sql = [[SELECT docId, assetId FROM comment where spaceId == ?]], spaceId,
		quiesceUntilUpToDate = true,
	})
	:expectData( { { commentId, trashAssetId } } )
	:dispose()

	-- Make sure the restored asset has local files
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
		:map( function( docValue )
			local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
			local haveOriginal = originalPath and WFFileUtils.exists( originalPath ) == 'file'

			return haveOriginal
		end ),
	true )

	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
	:map( function( docValue )
		local revisionIds = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
		local numRevisionIds = #revisionIds
		trashRevId = revisionIds[ numRevisionIds ]
		return true
	end ),
	true )

	uploader:startContinuousUploads()

	local function stripForAssetUpdateAndRemoveSporadicFeedbackUser( mockDataAsTable, ozDataAsTable, ignorePartialUpdates )
		ozDataAsTable = WFCatTrashTestUtils.removeSporadicSpaceOrFeedbackFromOzUpdates( ozDataAsTable )
		local assetStripFn = WFCatTrashTestUtils.stripForAssetUpdate( true )
		return assetStripFn( mockDataAsTable, ozDataAsTable, ignorePartialUpdates )
	end

	if options.xmpSidecar then
	
		trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )
	
		-- /rels/xmp gets uploaded before master
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.uploadXmpReqResp( catalogId, trashAssetId, trashRevId, xmpSidecarFilePath ) )

		-- Wait for sidecar to complete if there is one.
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'xmp_sidecar' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1)
		:dispose()

		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, assetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false, false, options.xmpSidecar, expectedDevelopSettings ),
		}, stripForAssetUpdateAndRemoveSporadicFeedbackUser ), 'success' )
	end
	
	if options.xmpDevelop then
	
		trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )
		
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.uploadXmpDevelopReqResp( catalogId, trashAssetId, trashRevId, xmpDevelopFilePath ) )
	
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'xmp_develop' ))
			:expectDataWithinTime( 5 * waitMultiplier, 1)
			:dispose()
	
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, assetCreated, trashPurgeDate, userCreated, importSource, assetId, false, false, options.xmpDevelop, options.xmpSidecar, expectedDevelopSettings ),
		}, stripForAssetUpdateAndRemoveSporadicFeedbackUser ), 'success' )
	
	end

	trashRevId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, trashAssetId )

	-- Upload the master
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, trashAssetId, trashRevId, originalFilePath, nil, options.xmpSidecar ) )

	-- Wait for the upload master to complete, it will upload after the xmp_develop if there is one.
	rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
	:expectDataWithinTime( 5 * waitMultiplier, 1)
	:dispose()

	-- Get the updated metadata with the master link.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.trashAssetUpdateResponse( trashAssetId, trashRevId, fileName, trashAssetSubtype, assetCreated, trashPurgeDate, userCreated, importSource, assetId, true, true, options.xmpDevelop, options.xmpSidecar ),
	}, stripForAssetUpdateAndRemoveSporadicFeedbackUser ), 'success' )

	-- Wait for the update to get applied to the assets docValue
	rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
		:map( type ))
	:expectDataWithinTime( 5 * waitMultiplier, 'string' )
	:dispose()

	-- Wait for the update to get applied to the assets docValue for xmpDevelop
	if options.xmpDevelop then
		rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )
			:map( type ))
		:expectDataWithinTime( 5 * waitMultiplier, 'string' )
		:dispose()
	end

	-- Make sure the original asset was deleted
	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:pluck( '_deleted' ),
	true )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddPartialSyncAddAssocDocsPartialSyncDelete_AssetGoneError( rxTest )

-- LRMI-4190611
--Scenario:
--
--1 Pause Sync
--2 Add a photo into a shared album
--3 Add favorite and comment
--4 Turn on Debug tools metadataPusher to push the asset, album_asset, comment and favorite creation
--5 Delete asset
--6 Turn on Sync
--7 Restore asset
--
-- Comment and favorite receives AssetGoneError:
-- Delete happened before "Create F1 against A": So, A has no favorite at the time of delete
-- Delete happened on Oz: A has no favorite on Oz
-- Client sends Create F1 against A: AssetGoneError with B id
-- Client sends Create F1 against B: Will be created
-- Client sends Create F1 against B again ? (because of AssetGoneError): ResourceExists error
-- Documents in sync feed related to favorites: F1 and AssetGoneError
--
--Expected Result:
--
--The restored asset should be in the same album, with comment and favorite

	testAddPartialSyncAddAssocDocsPartialSyncDelete( rxTest, { mock = true, assetGoneError = true, xmpSidecar = true, xmpDevelop = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddPartialSyncAddAssocDocsPartialSyncDelete_ResourceExistsError( rxTest )

-- LRMI-4190611
--Scenario:
--
--1 Pause Sync
--2 Add a photo into a shared album
--3 Add favorite and comment
--4 Turn on Debug tools metadataPusher to push the asset, album_asset, comment and favorite creation
--5 Delete asset
--6 Turn on Sync
--7 Restore asset
--
-- Comment and favorite receives ResourceExistsError:
-- Delete happened after "Create F1 against A" but before client had confirmation: A has favorite F1
-- Client sends Create F1 against A: No Error. Will be created
-- Delete happened on Oz: When delete happens, Oz creates F2 against B and F1 tombstone
-- Client sends Create F1 against B: ResourceExistsError as there is a tombstone for F1
-- Documents in sync feed related to favorites: F1 tombstone, F2
--
--Expected Result:
--
--The restored asset should be in the same album, with comment and favorite

	testAddPartialSyncAddAssocDocsPartialSyncDelete( rxTest, { mock = true, resourceExistsError = true, xmpSidecar = true, xmpDevelop = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_MockAddPartialSyncAddAssocDocsPartialSyncDelete_PullConflict( rxTest )

-- LRMI-4190611
--Scenario:
--
--1 Pause Sync
--2 Add a photo into a shared album
--3 Add favorite and comment
--4 Turn on Debug tools metadataPusher to push the asset, album_asset, comment and favorite creation
--5 Delete asset
--6 Turn on Sync
--7 Restore asset
--
--Delete happened after "Create F1 against A" but before client had confirmation: A has favorite F1
--Client sends Create F1 against A: No Error. Will be created
--Clients Delete A
--Clients receive confirmation for A, F1 confirmation
--Clients locally conflict for F1, F1 against B
--Clients push delete A with B info, but no update for F1 (we don't handle updates for favorite if client has confirmation for favorite)
--Delete happened on Oz: When delete happens, Oz creates F2 against B and F1 tombstone
--Documents in sync feed related to favorites: F1 tombstone, F2
--
--Expected Result:
--
--The restored asset should be in the same album, with comment and favorite

	testAddPartialSyncAddAssocDocsPartialSyncDelete( rxTest, { mock = true, pullConflict = true, xmpSidecar = true, xmpDevelop = true } )

end

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

function WFCatTrashPartialSyncTest.rxTest_CT_OzAddPartialSyncAddAssocDocsPartialSyncDelete_PullConflict_testNightly( rxTest )

-- LRMI-4190611
--Scenario:
--
--1 Pause Sync
--2 Add a photo into a shared album
--3 Add favorite and comment
--4 Turn on Debug tools metadataPusher to push the asset, album_asset, comment and favorite creation
--5 Delete asset
--6 Turn on Sync
--7 Restore asset
--
--Delete happened after "Create F1 against A" but before client had confirmation: A has favorite F1
--Client sends Create F1 against A: No Error. Will be created
--Clients Delete A
--Clients receive confirmation for A, F1 confirmation
--Clients locally conflict for F1, F1 against B
--Clients push delete A with B info, but no update for F1 (we don't handle updates for favorite if client has confirmation for favorite)
--Delete happened on Oz: When delete happens, Oz creates F2 against B and F1 tombstone
--Documents in sync feed related to favorites: F1 tombstone, F2
--
--Expected Result:
--
--The restored asset should be in the same album, with comment and favorite

	testAddPartialSyncAddAssocDocsPartialSyncDelete( rxTest, { pullConflict = true, xmpSidecar = true, xmpDevelop = true } )

end

--==============================================================================
-- @mark setup
--==============================================================================


function WFCatTrashPartialSyncTest.setUp( inCoveragePath )

	coveragePath = inCoveragePath

end

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

return WFRxTestCase.wrapSuite( 'WFCatTrashPartialSyncTest', WFCatTrashPartialSyncTest )
