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

WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.lua

--------------------------------------------------------------------------------
ADOBE CONFIDENTIAL
------------------
Copyright 2023 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
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.

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

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'
local WFTestFileUtils = require 'WFTestFileUtils'

	-- WF.core.os
local KSDate = require 'KSDate'
local WFFileUtils = require 'WFFileUtils'

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

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

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

local coveragePath

local derefOpt = KSTableUtils.derefOpt


local kOzWaitMultiplier = 30
local kTimeoutForSendingUpdates = 5

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

local WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest = {}

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

local function setTimeoutAndRetryLimit( rxTest, options, timeoutValue )

	rxTest:setTimeLimit( timeoutValue )

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

end

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

local removeSporadicAssetUpdates = WFCatTrashTestUtils.removeSporadicUpdatesWithTypes( { 'asset' } )

--==============================================================================
-- @mark tests that involve other client actions
--==============================================================================


local function testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, options )

-- NMB-14104
-- This tests the two client scenario where one client attempts to delete and
-- restore an asset while another client deleted and permanently deleted that asset
-- from the trash. In this case, the client attempting to restore the asset
-- should instead create the new asset from scratch instead of asking Oz to restore
-- the trash asset.

	options = options or {}

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 15 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc, errorHandler = 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 subtype = 'image'
	local fileName = "IMG_7763.JPG"
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"
	local proxyFileName = "IMG_7763.DNG"

	fileName = options.fileName or fileName
	subtype = options.subtype or subtype

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

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

-- Step 1: Add an image asset
	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local filePath, proxyFilePath
	if options.proxyOnlyAsset then
		proxyFilePath = WFCatTrashTestUtils.getProxyFilePath( rxTest, tempTestFolder, proxyFileName )

		rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithProxyOnly( catalog, catalogId, assetId, proxyFilePath, subtype, userCreated, importSource )
			:tap( function( revId )
				revisionId = revId
			end ),
			':ignore:' )

	else
		filePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )
		proxyFilePath = WFCatTrashTestUtils.getProxyFilePath( rxTest, tempTestFolder, proxyFileName )

		rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMasterAndProxy( catalog, catalogId, assetId, filePath, proxyFilePath, subtype, userCreated, importSource )
			:tap( function( revId )
				revisionId = revId
			end ),
		':ignore:' )
	end

	-- Send the metadata for the newly added asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, subtype ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- these are the /rels/* links we currently expect in the received document
	local linksExpectedInDoc = { master = false, proxy = false, renditions = false }

	-- 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, linksExpectedInDoc )
	}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	revisionId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, assetId )

	-- push and validate for the binary upload
	local expectedOriginalUploadCount = 0
	local expectedProxyUploadCount = 0
	if options.proxyOnlyAsset then
		expectedProxyUploadCount = expectedProxyUploadCount + 1

		-- Upload the proxy (and generate renditions)
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createProxyReqResp( catalogId, assetId, revisionId, KSIdGenerator.uuidInLowercaseHex(), proxyFilePath ) )
		uploader:startContinuousUploads()

		-- wait for the uploader to issue the create proxy request
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'proxy' ))
		:expectDataWithinTime( 5 * waitMultiplier, expectedProxyUploadCount )
		:dispose()
	else
		expectedOriginalUploadCount = expectedOriginalUploadCount + 1

		-- Upload the master
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, assetId, revisionId, filePath ) )
		uploader:startContinuousUploads()

		-- Wait for the uploader to issue the create master request
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
		:expectDataWithinTime( 5 * waitMultiplier, expectedOriginalUploadCount )
		:dispose()
	end
	uploader:stopContinuousUploads()

	-- Wait for oz to generate the renditions before requesting next update
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 10 * waitMultiplier, catalogSpec, catalogId, assetId, revisionId, subtype, { proxyOnlyAsset = true } ), true )

	-- Get the updated metadata with the binary link.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetUpdateResponse( assetId, revisionId, fileName, subtype, assetCreated, userCreated, importSource, { master = not options.proxyOnlyAsset, proxy = options.proxyOnlyAsset, renditions = true }, nil, true )
	}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	-- Wait for the update to get applied to the assets docValue
	local binaryRelsKey = options.proxyOnlyAsset and '/rels/proxy_type/2560' or '/rels/master'
	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( '_serverProvidedMetadata', 'links', binaryRelsKey, 'href' )
		:map( type ))
	:expectDataWithinTime( 5 * waitMultiplier, 'string' )
	:dispose()

-- Step 1.5: Maybe remove some of the local binaries for the asset to test different restore scenarios
	if options.dropLocalMaster then
		rxTest:expectSingleValue( catalog:docStore():applyOverlay(
			WFCatAssetTestUtils.getOverlayToRemoveLocalOriginal( assetId ) ),
			':ignore:' )
	end

	if options.dropLocalProxy then
		rxTest:expectSingleValue( catalog:docStore():applyOverlay(
			WFCatAssetTestUtils.getOverlayToRemoveLocalProxy( assetId ) ),
			':ignore:' )
	end


-- Other Client ------------------------------------------------------------

-- Step 2: On a second client, delete the asset and then remove the asset from the trash
-- so that no longer exists on Oz

	local ozTrashAssetCreated = KSDate.timeInIso8601Format()
	local ozTrashPurgeDate = KSDate.shiftIso8601DateTime( ozTrashAssetCreated, 5184000 )
	local ozTrashAssetId = KSIdGenerator.uuidInLowercaseHex()
	local ozTrashAssetRevId = KSIdGenerator.uuidInLowercaseHex()

	WFCatTrashTestUtils.simulateOtherClientAssetDelete {
		rxTest = rxTest,
		catalog = catalog,
		catalogSpec = catalogSpec,
		assetId = assetId,
		revisionId = revisionId,
		trashAssetId = ozTrashAssetId,
		trashAssetRevId = ozTrashAssetRevId,
		userCreated = userCreated,
		trashPurgeDate = ozTrashPurgeDate,
		options = options,
	}

	WFCatTrashTestUtils.simulateOtherClientDeleteTrash {
		rxTest = rxTest,
		catalog = catalog,
		catalogSpec = catalogSpec,
		trashAssetId = ozTrashAssetId,
		trashAssetRevId = ozTrashAssetRevId,
	}

	rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilDeleted( 10 * waitMultiplier, catalogSpec, catalogId, ozTrashAssetId, ozTrashAssetRevId, options ), true )

-- Original Client ------------------------------------------------------------

-- Step 3: Move the asset to trash locally

	-- Delete the asset which is on Oz
	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 )
	:tap( function( docValue )
		revisionId = docValue._revId
	end )
	:pluck( '_deleted' )
	, true )

	-- 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 )
		:tap( function( docValue )
			trashUserCreated = docValue.userCreated
			trashPurgeDate = docValue.purgeDate
		end )
	, ':ignore:' )

-- Step 4: Restore the asset locally

	-- Restore the trash asset
	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( nil, derefOpt( restoredDocValue, 'original' ) )

	-- Make sure the last trash userCreated and purgeDate data is correctly stored in restored asset createFrom field
	rxTest:expectSingleValue( catalog:docStore():docValue( restoredAssetId )
		:map( function( docValue )
			local createFromInfo = WFCatTrashUtils.createFromInfo( docValue )
			return createFromInfo.lastTrashUserCreated == trashUserCreated and createFromInfo.lastTrashPurgeDate == trashPurgeDate
		end )
	, true )

-- Step 4.5: Turn on Sync

	-- Get the other client's tombstones from the sync feed.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 7 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetDeletedResponse( assetId, ozTrashAssetId, userId ),
		WFCatTrashTestUtils.trashAssetDeletedResponse( ozTrashAssetId, userId ),
	}, removeSporadicAssetUpdates ), 'success' )

	-- Send up the Delete/Restore request
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteRestoreAssetReqResp( catalogId, assetId, revisionId, trashAssetId, trashRevId, restoredAssetId, restoredRevId ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 2, kTimeoutForSendingUpdates ) )

	if options.splitErrors then

		-- Typically the first error arrives in less than 10 seconds and the second error arrives after a minute.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "delete", assetId, revisionId, "asset", assetId, "asset", ozTrashAssetId, userId ),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		-- Wait for the error to be processed.
		rxTest:observe( WFCatTrashTestUtils.pollErrorHandlerErrorsProcessed( errorHandler, 'ResourceNotFoundError' ))
		:expectDataWithinTime( 5 * waitMultiplier, 1, false )
		:dispose()

	else

		-- Get the errors with respect to what we tried to copy from as they are no longer there.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "delete", assetId, revisionId, "asset", assetId, "asset", ozTrashAssetId, userId ),
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "create", restoredAssetId, restoredRevId, "asset", trashAssetId, "deleted_asset", nil, userId ),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		-- Wait for the errors to be processed.
		rxTest:observe( WFCatTrashTestUtils.pollErrorHandlerErrorsProcessed( errorHandler, 'ResourceNotFoundError' ))
		:expectDataWithinTime( 5 * waitMultiplier, 2, false )
		:dispose()

	end

	-- Verify WFCatErrorHandler will update the createFrom trash info
	-- so that we attempt to create from the oz trash asset even though
	-- it is not there anymore, we will re-issue the restore request
	-- and get a new error later.
	rxTest:observe( catalog:docStore():ongoingDocValue( restoredAssetId )
	:map( WFCatTrashUtils.getCreateFromId ) )
	:expectDataWithinTime( 5 * waitMultiplier, ozTrashAssetId )
	:dispose()

	-- Send up a new Restore request referring to the other client's trash asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushRestoreAssetReqResp( catalogId, restoredAssetId, ozTrashAssetId, ozTrashAssetRevId ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	if options.splitErrors then
		-- typical path?
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			-- First error is leftover from our first attempt at the delete/restore.
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "create", restoredAssetId, restoredRevId, "asset", trashAssetId, "deleted_asset", nil, userId ),
			-- Second error is from the WFCatTrashTestUtils.pushRestoreAssetReqResp call above.
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "create", restoredAssetId, restoredRevId, "asset", ozTrashAssetId, "deleted_asset", nil, userId ),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )
	else
		-- Get the error with respect to what we tried to copy from as they are no longer there.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "create", restoredAssetId, restoredRevId, "asset", ozTrashAssetId, "deleted_asset", nil, userId),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )
	end

	-- Wait for the error(s) to be processed.
	rxTest:observe( WFCatTrashTestUtils.pollErrorHandlerErrorsProcessed( errorHandler, 'ResourceNotFoundError' ))
	:expectDataWithinTime( 5 * waitMultiplier, 3, false )
	:dispose()

	-- Verify WFCatErrorHandler will remove the createFrom trash info
	-- so the photo will be created via a regular create operation.
	rxTest:observe( catalog:docStore():ongoingDocValue( restoredAssetId )
	:map( function( docValue )
		return WFCatTrashUtils.createFromInfo( docValue )
	end ) )
	:expectDataWithinTime( 5 * waitMultiplier, nil )
	:dispose()

-- Step 5: The error handler should process the RNFE and turn the restore into an asset create
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, subtype ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates * 3 ) )

	if options.dropLocalMaster and not options.dropLocalProxy then
		importSource.willNotUploadMaster = true
	end

	-- pull from Oz to get the asset create response
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.assetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource,
								{ master = false, renditions = false, xmpDevelop = false },
								nil --[[developSettings]], true --[[alwaysHaveLocationData]] )
		}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	-- validate the restored asset is on oz
	rxTest:observe( catalog:docStore():ongoingDocValue( restoredAssetId )
	:map( function( docValue )
		local receivedUpdateFromOz = docValue._serverProvidedMetadata ~= nil

		-- stash the latest oz revisionId so we can validate the upload
		local revisionIds = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
		if revisionIds then
			restoredRevId = revisionIds[ #revisionIds ]
		end

		return receivedUpdateFromOz
	end ) )
	:expectDataWithinTime( 5 * waitMultiplier, true )
	:dispose()

-- Step 6: The restored asset gets its binaries uploaded (if applicable)

	if not ( options.dropLocalMaster and options.dropLocalProxy ) then
		-- we should have some binaries so validate that they're uploaded

		-- Make sure the restored asset has the expected set of local files
		rxTest:expectSingleValue( catalog:docStore():docValue( restoredAssetId )
			:map( function( docValue )
				local hasBinary = false
				if options.proxyOnlyAsset or options.dropLocalMaster then
					local proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )
					hasBinary = proxyPath and WFFileUtils.exists( proxyPath ) == 'file'
				else
					local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
					hasBinary = originalPath and WFFileUtils.exists( originalPath ) == 'file'
				end

				return hasBinary
			end ),
		true )

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

		if options.proxyOnlyAsset or options.dropLocalMaster then
			expectedProxyUploadCount = expectedProxyUploadCount + 1

			-- Upload the proxy (and generate renditions)
			catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createProxyReqResp( catalogId, restoredAssetId, restoredRevId, KSIdGenerator.uuidInLowercaseHex(), proxyFilePath ) )
			uploader:startContinuousUploads()

			-- wait for the uploader to issue the create proxy request
			rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'proxy' ))
			:expectDataWithinTime( 5 * waitMultiplier, expectedProxyUploadCount )
			:dispose()
		else
			expectedOriginalUploadCount = expectedOriginalUploadCount + 1

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

			-- Wait for the uploader to issue the create master request
			rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
			:expectDataWithinTime( 5 * waitMultiplier, expectedOriginalUploadCount )
			:dispose()
		end
		uploader:stopContinuousUploads()

		-- Wait for oz to generate the renditions before requesting next update
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 10 * waitMultiplier, catalogSpec, catalogId, restoredAssetId, restoredRevId, subtype, { proxyOnlyAsset = true } ), true )

		-- Get the updated metadata with the binary link.
		local expectMasterLink = not ( options.dropLocalMaster or options.proxyOnlyAsset )
		local expectProxyAndRenditionLinks = not options.dropLocalProxy
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.assetUpdateResponse( restoredAssetId, restoredRevId, fileName, subtype, assetCreated, userCreated, importSource, { master = expectMasterLink, proxy = expectProxyAndRenditionLinks, renditions = expectProxyAndRenditionLinks }, nil, true )
		}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	end

	-- Check that there is nothing else to send.
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 0, kTimeoutForSendingUpdates ) )

	-- Check to see how the ResourceNotFoundErrors were resolved
	rxTest:expectEqual( {
		ResourceNotFoundError = {
			count = 3,
			resolution = {
				IgnoredMoreThanOneOperationBehind = {
					count = 1,
					sourceDocIds = {
						restoredAssetId
					},
				},
				RemovedTrashInfoToCreateAssetDirectly_docThatIncurredErrorWasTrashAsset = {
					count = 1,
					sourceDocIds = {
						restoredAssetId,
					},
				},
				UpdatedLocalCreateFromTargetingOzDoc_LocalDelete = {
					count = 1,
					sourceDocIds = {
						assetId,
					},
				},
			},
		},
	}, errorHandler:DEBUG_errorsResolved() )

end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDeleteRestore_OtherDeleteOtherDelTrash_Master( rxTest )
-- NMB-14104
	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { mock = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDeleteRestore_OtherDeleteOtherDelTrash_Master_testNightly( rxTest )
-- NMB-14104
	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDeleteRestore_OtherDeleteOtherDelTrash_NoMaster( rxTest )
-- NMB-14104
-- Expected result: The asset was a Lightroom style asset and will be restored
-- as a Lightroom style asset

	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { mock = true, proxyOnlyAsset = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDeleteRestore_OtherDeleteOtherDelTrash_NoMaster_testNightly( rxTest )
-- NMB-14104
-- Expected result: The asset will be restored as a Lightroom style asset

	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { proxyOnlyAsset = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDeleteRestore_OtherDeleteOtherDelTrash_NoLocalMaster( rxTest )
-- NMB-14104
-- Expected result: The asset had a master, but not locally and it's no longer on Oz
-- so the asset will be restored as a Lightroom style asset

	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { mock = true, dropLocalMaster = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDeleteRestore_OtherDeleteOtherDelTrash_NoLocalMaster_testNightly( rxTest )
-- NMB-14104
-- Expected result: The asset had a master, but not locally and it's no longer on Oz
-- so the asset will be restored as a Lightroom style asset

	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { dropLocalMaster = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDeleteRestore_OtherDeleteOtherDelTrash_NoLocalBinaries( rxTest )
-- NMB-14104
-- In this case we fail, as we only had the thumbnail.
-- The asset won't show up on other clients but would show up on Lr Web as an incomplete
-- asset.

	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { mock = true, dropLocalMaster = true, dropLocalProxy = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDeleteRestore_OtherDeleteOtherDelTrash_NoLocalBinaries_testNightly( rxTest )
-- NMB-14104
-- In this case we fail, as we only had the thumbnail.

	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { dropLocalMaster = true, dropLocalProxy = true } )
end

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

-- TODO [jandrew 2025-03-14]: Disabled due to instability.

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.DISABLED_rxTest_CT_MockDeleteRestore_OtherDeleteOtherDelTrash_splitErrors( rxTest )
-- NMB-14104
	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest, { mock = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDeleteRestore_OtherDeleteOtherDelTrash_splitErrors_testNightly( rxTest )
-- NMB-14104
	testAddSyncDeleteRestoreOtherDeleteOtherDelTrashSync( rxTest )
end

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

local function testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, options )

-- NMB-13896 #7 / NMB-14709
-- This tests the two client scenario where one client attempts to delete
-- an asset while another client deleted and permanently deleted that asset
-- from the trash. In this case, the client attempting to delete the asset
-- should instead create the new asset from scratch instead of asking Oz to restore
-- the trash asset.

--Steps:
--1) Add photo, let it sync.
--2) Pause Sync.
--3) Other Client Delete the photo
--4) Other Client Permanently Delete the photo from the trash.
--5) Delete the photo.
--6) Resume Sync
--Expected: Photo in Trash with whatever Binaries are available.

	options = options or {}

	local waitMultiplier = options.mock and 1 or kOzWaitMultiplier

	setTimeoutAndRetryLimit( rxTest, options, 15 * waitMultiplier )

	local testSession, catalog, catalogSpec, pusher, puller, uploader, pc, errorHandler = 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 subtype = 'image'
	local fileName = "IMG_7763.JPG"
	local sha256 = "3bd8b936cc646bf1296e5ba4a76c410a27beb80809f525a8f5072f0115c54ccc"
	local proxyFileName = "IMG_7763.DNG"

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

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

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

--1) Add photo, let it sync.
	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local filePath, proxyFilePath
	if options.proxyOnlyAsset then
		proxyFilePath = WFCatTrashTestUtils.getProxyFilePath( rxTest, tempTestFolder, proxyFileName )

		rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithProxyOnly( catalog, catalogId, assetId, proxyFilePath, subtype, userCreated, importSource )
			:tap( function( revId )
				revisionId = revId
			end ),
			':ignore:' )

	else
		filePath = WFCatTrashTestUtils.getOriginalFilePath( rxTest, tempTestFolder, fileName )
		proxyFilePath = WFCatTrashTestUtils.getProxyFilePath( rxTest, tempTestFolder, proxyFileName )

		rxTest:expectSingleValue( WFCatTrashTestUtils.addDocumentWithLocalMasterAndProxy( catalog, catalogId, assetId, filePath, proxyFilePath, subtype, userCreated, importSource )
			:tap( function( revId )
				revisionId = revId
			end ),
		':ignore:' )
	end

	-- Send the metadata for the newly added asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, subtype ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- these are the /rels/* links we currently expect in the received document
	local linksExpectedInDoc = { master = false, proxy = false, renditions = false }

	-- 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, linksExpectedInDoc )
	}, WFCatTrashTestUtils.stripForAssetUpdate( false ) ), 'success' )

	revisionId = WFCatTrashTestUtils.getLatestRevisionId( rxTest, catalog, assetId )

	-- push and validate for the binary upload
	local expectedOriginalUploadCount = 0
	local expectedProxyUploadCount = 0
	if options.proxyOnlyAsset then
		expectedProxyUploadCount = expectedProxyUploadCount + 1

		-- Upload the proxy (and generate renditions)
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createProxyReqResp( catalogId, assetId, revisionId, KSIdGenerator.uuidInLowercaseHex(), proxyFilePath ) )
		uploader:startContinuousUploads()

		-- wait for the uploader to issue the create proxy request
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'proxy' ))
		:expectDataWithinTime( 5 * waitMultiplier, expectedProxyUploadCount )
		:dispose()
	else
		expectedOriginalUploadCount = expectedOriginalUploadCount + 1

		-- Upload the master
		catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createMasterReqResp( catalogId, assetId, revisionId, filePath ) )
		uploader:startContinuousUploads()

		-- Wait for the uploader to issue the create master request
		rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
		:expectDataWithinTime( 5 * waitMultiplier, expectedOriginalUploadCount )
		:dispose()
	end
	uploader:stopContinuousUploads()

	-- Wait for oz to generate the renditions before requesting next update
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 10 * waitMultiplier, catalogSpec, catalogId, assetId, revisionId, subtype, { proxyOnlyAsset = true } ), true )

	-- Get the updated metadata with the binary link.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetUpdateResponse( assetId, revisionId, fileName, subtype, assetCreated, userCreated, importSource, { master = not options.proxyOnlyAsset, proxy = options.proxyOnlyAsset, renditions = true }, nil, true )
	}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	-- Wait for the update to get applied to the assets docValue
	local binaryRelsKey = options.proxyOnlyAsset and '/rels/proxy_type/2560' or '/rels/master'
	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( '_serverProvidedMetadata', 'links', binaryRelsKey, 'href' )
		:map( type ))
	:expectDataWithinTime( 5 * waitMultiplier, 'string' )
	:dispose()

-- Step 1.5: Maybe remove some of the local binaries for the asset to test different restore scenarios
	if options.dropLocalMaster then
		rxTest:expectSingleValue( catalog:docStore():applyOverlay(
			WFCatAssetTestUtils.getOverlayToRemoveLocalOriginal( assetId ) ),
			':ignore:' )
	end

	if options.dropLocalProxy then
		rxTest:expectSingleValue( catalog:docStore():applyOverlay(
			WFCatAssetTestUtils.getOverlayToRemoveLocalProxy( assetId ) ),
			':ignore:' )
	end

--2) Pause Sync.

-- Other Client ------------------------------------------------------------

--3) Other Client Delete the photo

	local ozTrashAssetCreated = KSDate.timeInIso8601Format()
	local ozTrashPurgeDate = KSDate.shiftIso8601DateTime( ozTrashAssetCreated, 5184000 )
	local ozTrashAssetId = KSIdGenerator.uuidInLowercaseHex()
	local ozTrashAssetRevId = KSIdGenerator.uuidInLowercaseHex()

	WFCatTrashTestUtils.simulateOtherClientAssetDelete {
		rxTest = rxTest,
		catalog = catalog,
		catalogSpec = catalogSpec,
		assetId = assetId,
		revisionId = revisionId,
		trashAssetId = ozTrashAssetId,
		trashAssetRevId = ozTrashAssetRevId,
		userCreated = userCreated,
		trashPurgeDate = ozTrashPurgeDate,
		options = options,
	}

--4) Other Client Permanently Delete the photo from the trash.

	WFCatTrashTestUtils.simulateOtherClientDeleteTrash {
		rxTest = rxTest,
		catalog = catalog,
		catalogSpec = catalogSpec,
		trashAssetId = ozTrashAssetId,
		trashAssetRevId = ozTrashAssetRevId,
	}

	rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilDeleted( 10 * waitMultiplier, catalogSpec, catalogId, ozTrashAssetId, ozTrashAssetRevId, options ), true )

-- Original Client ------------------------------------------------------------

--5) Delete the photo.

	-- Delete the asset which is on Oz
	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 )
	:tap( function( docValue )
		revisionId = docValue._revId
	end )
	:pluck( '_deleted' )
	, true )

	-- 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 )
		:tap( function( docValue )
			trashUserCreated = docValue.userCreated
			trashPurgeDate = docValue.purgeDate
		end )
	, ':ignore:' )

--6) Resume Sync

	-- Get the other client's tombstones from the sync feed.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 7 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.assetDeletedResponse( assetId, ozTrashAssetId, userId ),
		WFCatTrashTestUtils.trashAssetDeletedResponse( ozTrashAssetId, userId ),
	}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( true ) ), 'success' )

	-- Send up the Delete/Restore request
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushDeleteAssetReqResp( catalogId, assetId, revisionId, trashAssetId, trashRevId ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates ) )

	-- Get the errors with respect to what we tried to copy from as they are no longer there.
	rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
		WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "delete", assetId, revisionId, "asset", assetId, "asset", ozTrashAssetId, userId ),
	}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

	-- Wait for the errors to be processed.
	rxTest:observe( WFCatTrashTestUtils.pollErrorHandlerErrorsProcessed( errorHandler, 'ResourceNotFoundError' ))
	:expectDataWithinTime( 5 * waitMultiplier, 1, false )
	:dispose()

	-- Verify WFCatErrorHandler will update the createFrom trash info
	-- so that we attempt to create from the oz trash asset even though
	-- it is not there anymore, we will re-issue the restore request
	-- and get a new error later.
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId ):map( WFCatTrashUtils.getCreateFromId ), ozTrashAssetId )

	local intermediateId, intermediateRevId
	rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
		:map( function( localTrashDocValue )
			local createFromInfo = WFCatTrashUtils.createFromInfo( localTrashDocValue )
			intermediateId = createFromInfo.lastAssetId
			intermediateRevId = createFromInfo.lastRevisionId
			return type( intermediateId ), type( intermediateRevId )
		end )
	, 'string', 'string' )

	-- Send up a new Restore request referring to the other client's trash asset
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushRestoreDeleteAssetReqResp( catalogId, ozTrashAssetId, ozTrashAssetRevId, intermediateId, intermediateRevId, trashAssetId, trashRevId ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 2, kTimeoutForSendingUpdates ) )

	if options.splitErrors then

		-- Typical path, get the error with respect to what we tried to copy from as it is no longer there.
		-- The second error will get delivered later.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "create", intermediateId, intermediateRevId, "asset", ozTrashAssetId, "deleted_asset", nil, userId ),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		-- Wait for the errors to be processed.
		rxTest:observe( WFCatTrashTestUtils.pollErrorHandlerErrorsProcessed( errorHandler, 'ResourceNotFoundError' ))
		:expectDataWithinTime( 5 * waitMultiplier, 2, false )
		:dispose()

	else
		-- Get the errors with respect to what we tried to copy from as they are no longer there.
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 10 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "create", intermediateId, intermediateRevId, "asset", ozTrashAssetId, "deleted_asset", nil, userId ),
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "delete", intermediateId, intermediateRevId, "asset", intermediateId, "asset", nil, userId ),
		}, WFCatTrashTestUtils.transformOrderAndStripAssetUpdate( false )), 'success' )

		-- Wait for the errors to be processed.
		rxTest:observe( WFCatTrashTestUtils.pollErrorHandlerErrorsProcessed( errorHandler, 'ResourceNotFoundError' ))
		:expectDataWithinTime( 5 * waitMultiplier, 3, false )
		:dispose()
	end

	-- Verify WFCatErrorHandler will remove the createFrom trash info
	-- so the photo will be created via a regular create operation.
	rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
	:map( function( docValue )
		return WFCatTrashUtils.createFromInfo( docValue )
	end ) )
	:expectDataWithinTime( 5 * waitMultiplier, nil )
	:dispose()

	-- push the create
	catalogSpec:expectHttpRequest( WFCatTrashTestUtils.pushCreateAssetReqResp( catalogId, fileName, trashAssetSubtype ) )
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 1, kTimeoutForSendingUpdates * 3 ) )

	if options.dropLocalMaster and not options.dropLocalProxy then
		importSource.willNotUploadMaster = true
	end

	if options.splitErrors then

		-- pull from Oz to get the asset create response
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse2( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashUserCreated, trashPurgeDate, userCreated, importSource, assetId, { master = false, proxy = false, renditions = false } ),
			WFCatTrashTestUtils.resourceNotFoundErrorUpdateResponse( "delete", intermediateId, intermediateRevId, "asset", intermediateId, "asset", nil, userId ),
		}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	else

		-- pull from Oz to get the asset create response
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse2( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashUserCreated, trashPurgeDate, userCreated, importSource, assetId, { master = false, proxy = false, renditions = false } ),
		}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	end
	-- validate the restored asset is on oz
	rxTest:observe( catalog:docStore():ongoingDocValue( trashAssetId )
	:map( function( docValue )
		local receivedUpdateFromOz = docValue._serverProvidedMetadata ~= nil

		-- stash the latest oz revisionId so we can validate the upload
		local revisionIds = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
		if revisionIds then
			trashRevId = revisionIds[ #revisionIds ]
		end

		return receivedUpdateFromOz
	end ) )
	:expectDataWithinTime( 5 * waitMultiplier, true )
	:dispose()

-- The trash asset gets its binaries uploaded (if applicable)

	if not ( options.dropLocalMaster and options.dropLocalProxy ) then
		-- we should have some binaries so validate that they're uploaded

		-- Make sure the restored asset has the expected set of local files
		rxTest:expectSingleValue( catalog:docStore():docValue( trashAssetId )
			:map( function( docValue )
				local hasBinary = false
				if options.proxyOnlyAsset or options.dropLocalMaster then
					local proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )
					hasBinary = proxyPath and WFFileUtils.exists( proxyPath ) == 'file'
				else
					local originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
					hasBinary = originalPath and WFFileUtils.exists( originalPath ) == 'file'
				end

				return hasBinary
			end ),
		true )

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

		if options.proxyOnlyAsset or options.dropLocalMaster then
			expectedProxyUploadCount = expectedProxyUploadCount + 1

			-- Upload the proxy (and generate renditions)
			catalogSpec:expectHttpRequest( WFCatTrashTestUtils.createProxyReqResp( catalogId, trashAssetId, trashRevId, KSIdGenerator.uuidInLowercaseHex(), proxyFilePath ) )
			uploader:startContinuousUploads()

			-- wait for the uploader to issue the create proxy request
			rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'proxy' ))
			:expectDataWithinTime( 5 * waitMultiplier, expectedProxyUploadCount )
			:dispose()
		else
			expectedOriginalUploadCount = expectedOriginalUploadCount + 1

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

			-- Wait for the uploader to issue the create master request
			rxTest:observe( WFCatTrashTestUtils.pollUploadCount( uploader, 'master' ))
			:expectDataWithinTime( 5 * waitMultiplier, expectedOriginalUploadCount )
			:dispose()
		end
		uploader:stopContinuousUploads()

		-- Wait for oz to generate the renditions before requesting next update
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollUntilHasRequiredLinks( 10 * waitMultiplier, catalogSpec, catalogId, trashAssetId, trashRevId, subtype, { proxyOnlyAsset = true } ), true )

		-- Get the updated metadata with the binary link.
		local expectMasterLink = not ( options.dropLocalMaster or options.proxyOnlyAsset )
		local expectProxyAndRenditionLinks = not options.dropLocalProxy
		rxTest:expectSingleValue( WFCatTrashTestUtils.pollForUpdateWith( 5 * waitMultiplier, catalogSpec, catalogId, puller, {
			WFCatTrashTestUtils.trashAssetUpdateResponse2( trashAssetId, trashRevId, fileName, trashAssetSubtype, trashUserCreated, trashPurgeDate, userCreated, importSource, assetId, { master = expectMasterLink, proxy = expectProxyAndRenditionLinks, renditions = expectProxyAndRenditionLinks } ),
		}, WFCatTrashTestUtils.stripAssetAndLocation ), 'success' )

	end

	-- Check that there is nothing else to send.
	rxTest:expectOnlyCompleted( pusher:_DEBUG_sendNUpdates( 0, kTimeoutForSendingUpdates ) )

	-- Check to see how the ResourceNotFoundErrors were resolved
	rxTest:expectEqual( {
		ResourceNotFoundError = {
			count = 3,
			resolution = {
				IgnoredSecondPartOfTrashPair_deleteWasSentToOzBeforeAssetExisted = {
					count = 1,
					sourceDocIds = {
						intermediateId,
					},
				},
				RemovedTrashInfoToCreateAssetDirectly_deleteWasSentToOzBeforeAssetExisted = {
					count = 1,
					sourceDocIds = {
						intermediateId,
					},
				},
				UpdatedLocalCreateFromTargetingOzDoc_LocalDelete = {
					count = 1,
					sourceDocIds = {
						assetId,
					},
				},
			},
		},
	}, errorHandler:DEBUG_errorsResolved() )

end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDelete_OtherDeleteOtherDelTrash_Master( rxTest )
-- NMB-13896 #7 / NMB-14709
	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { mock = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDelete_OtherDeleteOtherDelTrash_Master_testNightly( rxTest )
-- NMB-13896 #7 / NMB-14709
	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDelete_OtherDeleteOtherDelTrash_NoMaster( rxTest )
-- NMB-13896 #7 / NMB-14709
-- Expected result: The asset was a Lightroom style asset and will be restored
-- as a Lightroom style asset

	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { mock = true, proxyOnlyAsset = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDelete_OtherDeleteOtherDelTrash_NoMaster_testNightly( rxTest )
-- NMB-13896 #7 / NMB-14709
-- Expected result: The asset will be restored as a Lightroom style asset

	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { proxyOnlyAsset = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDelete_OtherDeleteOtherDelTrash_NoLocalMaster( rxTest )
-- NMB-13896 #7 / NMB-14709
-- Expected result: The asset had a master, but not locally and it's no longer on Oz
-- so the asset will be restored as a Lightroom style asset

	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { mock = true, dropLocalMaster = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDelete_OtherDeleteOtherDelTrash_NoLocalMaster_testNightly( rxTest )
-- NMB-13896 #7 / NMB-14709
-- Expected result: The asset had a master, but not locally and it's no longer on Oz
-- so the asset will be restored as a Lightroom style asset

	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { dropLocalMaster = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDelete_OtherDeleteOtherDelTrash_NoLocalBinaries( rxTest )
-- NMB-13896 #7 / NMB-14709
-- In this case we fail, as we only had the thumbnail.
-- The asset won't show up on other clients but would show up on Lr Web as an incomplete
-- asset.

	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { mock = true, dropLocalMaster = true, dropLocalProxy = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_OzDelete_OtherDeleteOtherDelTrash_NoLocalBinaries_testNightly( rxTest )
-- NMB-13896 #7 / NMB-14709
-- In this case we fail, as we only had the thumbnail.

	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { dropLocalMaster = true, dropLocalProxy = true } )
end

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

function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.rxTest_CT_MockDelete_OtherDeleteOtherDelTrash_splitErrors( rxTest )
-- NMB-13896 #7 / NMB-14709
	testAddSyncDeleteOtherDeleteOtherDelTrashSync( rxTest, { mock = true, splitErrors = true } )
end


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


function WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest.setUp( inCoveragePath )

	coveragePath = inCoveragePath

end

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

return WFRxTestCase.wrapSuite( 'WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest', WFCatTrashMultipleDevicesOtherDeleteOtherDelTrashTest )
