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

WFCatBinaryUploaderTest.lua

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

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

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

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

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.crypto
local WFCrypto = require 'WFCrypto'

	-- WF.core.lua
local KSData = require 'KSData'

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

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

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

	-- WF.core.utils
local KSDate = require 'KSDate'
local KSIdGenerator = require 'KSIdGenerator'
local WFArguments = require 'WFArguments'
local WFCatAssetUtils = require 'WFCatAssetUtils'
local WFRingBuffer = require 'WFRingBuffer'
local WFTableMerge = require 'WFTableMerge'
local WFCapabilities = require 'WFCapabilities'

	-- WF.web.utils
local WFURI = require 'WFURI'

	-- WF.oz_client.managed_catalog
local WFCatAssetAux = require 'WFCatAssetAux'
local WFCatBinaryUploader = require 'WFCatBinaryUploader'
local WFCatBinaryUploaderTestUtils = require 'WFCatBinaryUploaderTestUtils'
local WFCatCopies = require 'WFCatCopies'
local WFCatOzTestSession = require 'WFCatOzTestSession'
local WFCatMetadataPuller = require 'WFCatMetadataPuller'
local WFCatMetadataPusher = require 'WFCatMetadataPusher'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatPhotoImportance = require 'WFCatPhotoImportance'
local WFCatSpecMock = require 'WFCatSpecMock'
local WFCatTestSession = require 'WFCatTestSession'
local WFCatVersions = require 'WFCatVersions'
local WFCatWatermark = require 'WFCatWatermark'
local WFOzDocUtils = require 'WFOzDocUtils'

local OzFilePaths

if WFPlatformInfo.isMobile() then
	OzFilePaths = require 'OzFilePaths'
end

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

local kOZChunkSize = 5242880 -- Oz default chunk size: from chunkedUploader

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

local check = assert( WFArguments.check )
local derefOpt = assert( KSTableUtils.derefOpt )

local addXmpDevelopSettingsToDocument = assert( WFCatAssetTestUtils.addXmpDevelopSettingsToDocument )
local addDocumentWithOzMetadataUpdate = assert( WFCatAssetTestUtils.addDocumentWithOzMetadataUpdate )

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

local testDataPath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), "import-samples" )
local testDataFolder1 = KSPathUtils.child( testDataPath, "folder-1" )
local testAssetsFolder = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), "assets" )
local hdrTestDataFolder = KSPathUtils.child( testDataPath, "hdr" )

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

local xmpSidecarData = [[<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140 79.160451, 2017/05/06-01:08:21 (debug)">
 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Description rdf:about=""
    xmlns:xmp="http://ns.adobe.com/xap/1.0/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:exif="http://ns.adobe.com/exif/1.0/"
    xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/"
   xmp:ModifyDate="2020-12-12T11:10:09.876Z"
   xmp:Rating="1"
   xmp:CreatorTool="Adobe Nimbus Test"
   exif:GPSAltitude="10000/2"
   exif:GPSAltitudeRef="1"
   exif:GPSLongitude="100,5.125S"
   exif:GPSLatitude="20,1.5"
   crs:RawFileName="IMG_9876.cr2">
   <dc:subject>
    <rdf:Seq>
     <rdf:li>mary</rdf:li>
     <rdf:li>had</rdf:li>
     <rdf:li>a little lamb</rdf:li>
    </rdf:Seq>
   </dc:subject>
  </rdf:Description>
 </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                           
<?xpacket end="w"?>
]]

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

local WFCatBinaryUploaderTest = {}

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

local sCoveragePath

function WFCatBinaryUploaderTest.setUp( inCoveragePath )

	sCoveragePath = inCoveragePath

end

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

local function removeEmptyErrorHeader( errorMessage )

	return string.gsub( errorMessage, '<h4>(.[^<]+)</h4><ul></ul>', '' )

end

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

local function removeGetCleanOzAccountError( errorMessage )

	-- Remove intentionally generated error related to getCleanOzAccount
	return string.gsub( errorMessage, '<li>(.[^<]+)getCleanOzAccount(.[^<]+)</li>', '' )

end

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

local function removeMakeUniqueEmptySettingsForDownloadOnlyAssetWarning( errorMessage )

	-- strip warning that can be generated when creating a version in a space
	return string.gsub( errorMessage,
		'<li>(.[^<]+)NmbLocalRenditionDigest: makeUniqueEmptySettingsForDownloadOnlyAsset called with invalid revId nil, will use default xmp</li>',
		'' )

end

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

local function remove60SecondsWarning( errorMessage )

	return string.gsub( errorMessage, "<li>(.[^<]+)Doc store hasn't updated with the presence of uploaded(.[^<]+)</li>", '' )

end

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

local function remove10SecondsWarning( errorMessage )

	local first, _ = string.find(errorMessage, "YOU SHOULD NOT USE WFRxLowFidelityTimer WITH DELAYS LESS THAN 10 SECONDS")

	if first then
		errorMessage = ''
	end

	return errorMessage

end

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


local function getDiagnosticLogErrorsIgnoringAccountError( uploader, remove10SecError )

	-- Get errors encountered from diagnostic log, ignore account error.
	local diagnosticLogErrors = removeGetCleanOzAccountError( uploader:DEBUG_getDiagnosticLogErrors() )
	diagnosticLogErrors = remove60SecondsWarning( diagnosticLogErrors )
	diagnosticLogErrors = removeMakeUniqueEmptySettingsForDownloadOnlyAssetWarning( diagnosticLogErrors )

	if remove10SecError then
		diagnosticLogErrors = remove10SecondsWarning( diagnosticLogErrors )
	end

	return diagnosticLogErrors

end

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

local function removeMarkingOriginalInvalidError( errorMessage )

	return string.gsub( errorMessage, '<li>(.[^<]+)Marking original as invalid(.[^<]+)</li>', '' )

end

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

local function removeOriginalMissingError( errorMessage )

	return string.gsub( errorMessage, '<li>(.*)but original is missing.</li>', '' )

end

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

local function removeUhOhWarning( errorMessage )

	return string.gsub( errorMessage, '<li>(.[^<]+)Uh oh. Trying to upload(.[^<]+)</li>', '' )

end

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

local function remove403_1016_PermissionError( errorMessage )

	return string.gsub( errorMessage, '<li>(.[^<]+)Got status 403 back from Oz(.[^<]+)code = 1016(.[^<]+)</li>', '' )

end

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

local taskDelays = {

	progressiveBackoff = false,
	noWorkDelay = 1,
	maxNoWorkDelay = 1,
	errorDelay = 1,
	maxDelay = 1,

}

local options = {

	continuousTaskDelays = taskDelays,
	isCatalogPuller = true

}

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

--[[

	\brief Copy original file and optionally proxy file to temporary test folder and return paths and sha256 of original.

	\param rxTest (WFRxTestCaseRunner)
	\param filename (string) - filename of original (only supports small.JPG and IMG_9876.CR2)
	\param opts (table) - optional. original = true and proxy = true to copy both.

	\return originalFilePath, proxyFilePath, sha256, testFolder

]]

local function getPathsFor( rxTest, filename, opts )

	opts = opts or { original = true }

	if filename == 'small.JPG' or ( filename == "IMG_9876.CR2" and not opts.proxy ) then

		local uploadTestFolder = WFTestFileUtils.createTestFolder( rxTest )

		local testOriginalFilePath, testProxyFilePath, originalFilePath, proxyFilePath, sha256

		if opts.original then
			testOriginalFilePath = KSPathUtils.child( testDataFolder1, filename )
			originalFilePath = KSPathUtils.child( uploadTestFolder, filename )
			WFFileUtils.copy( testOriginalFilePath, originalFilePath )

			sha256 = WFCrypto.digestFile( originalFilePath, 'SHA256' )
		end

		if opts.proxy then
			testProxyFilePath = KSPathUtils.child( testAssetsFolder, "small.DNG" )
			proxyFilePath = KSPathUtils.child( uploadTestFolder, "small.DNG" )
			WFFileUtils.copy( testProxyFilePath, proxyFilePath )
		end

		return originalFilePath, proxyFilePath, sha256, uploadTestFolder

	else
		error( "unrecognized test filename " .. filename )
	end

end

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

local function getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	local copiedRevisionId, copiedOriginalFilePath, copiedProxyFilePath, copiedXmpSidecarFilePath, copiedXmpDevelopFilePath

	-- find the newly copied document and update the expected urlResponses
	rxTest:expectSingleValue( catalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			copiedRevisionId = docValue._revId
			copiedOriginalFilePath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
			copiedProxyFilePath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )
			copiedXmpSidecarFilePath = WFCatAssetUtils.getXmpSidecarPathFromDocValue( docValue )
			copiedXmpDevelopFilePath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )
			return true
		end ),
	true )

	return copiedRevisionId, copiedOriginalFilePath, copiedProxyFilePath, copiedXmpSidecarFilePath, copiedXmpDevelopFilePath

end

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

local function generateSidecarFile( rxTest, importSidecarPath )

	local sidecarData = KSData.make( xmpSidecarData )
	KSData.saveToFile( sidecarData, importSidecarPath )

	rxTest:finally( function()
		WFFileUtils.delete( importSidecarPath )
	end )

	return KSData.length( sidecarData )

end

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

local function pollBinariesInFlight( uploader )

	return Observable.timer( .1, 1, WFRxScheduler.sharedScheduler() )
	:map( function()
		return uploader._numberOfScheduledBinaryUploads
	end )

end

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

local function makeWFCatSpecSpy( catalogSpec, putBinaryFn, getBinaryFn  )

	putBinaryFn = putBinaryFn or function( self, ... )
		return catalogSpec:putBinary( ... )
	end

	getBinaryFn = getBinaryFn or function( self, ... )
		return catalogSpec:getBinary( ... )
	end

	local putFn = function( self, ... )
		return catalogSpec:put( ... )
	end

	local getFn = function( self, ... )
		return catalogSpec:get( ... )
	end

	local WFCatSpecSpy = {
		originalCatalogSpec = catalogSpec,
		getBinary = getBinaryFn,
		putBinary = putBinaryFn,
		get = getFn,
		put = putFn
	}

	return Rx.Class( 'WFCatSpec', WFCatSpecSpy )()

end

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

function WFCatBinaryUploaderTest.rxTestNewBinaryDetailEquality( rxTest )

	local detail1 = WFCatBinaryUploader.TEST_newBinaryDetail('mask', 'sha256')
	local detail2 = WFCatBinaryUploader.TEST_newBinaryDetail('mask', 'sha256')
	local detail3 = WFCatBinaryUploader.TEST_newBinaryDetail('depthMap', 'sha256')

	rxTest:expectEqual( true, detail1 == detail2 )
	rxTest:expectEqual( false, detail1 == detail3 )

end

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

local function addWillNotUploadMasterToDoc( catalog, assetId )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		importSource = {
			willNotUploadMaster = true,
		},
	}

end

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

local function removeOriginalPathFromDoc( catalog, assetId )

	if WFPlatformInfo.isDesktop then
		return catalog:docStore():applyOverlay {
			_id = assetId,
			_localOnly = {
				files = {
					original = {
						master = { _delete = true },
					},
				}
			}
		}
	elseif WFPlatformInfo.isiOS then
		return catalog:docStore():applyOverlay {
			_id = assetId,
			_localOnly = {
				files = {
					master = { _delete = true },
					latestMaster = { _delete = true }
				}
			}
		}
	elseif WFPlatformInfo.isAndroid then

		return catalog:docStore():applyOverlay {
			_id = assetId,
			_localOnly = {
				latestOriginal = { _delete = true },
				original = { _delete = true },
				originalFilePath = { _delete = true },
			}
		}
	end

end

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

local function removeProxyPathFromDoc( catalog, assetId )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_localOnly = {
			files = {
				proxy = { _delete = true },
			}
		}
	}

end

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

local function addAuxReferenceToDocument(catalog, assetId, auxParamsList)
	local overlay = {
		_id = assetId
		}

	overlay = WFCatAssetTestUtils.addAuxiliaryMetadataReference( overlay, assetId, auxParamsList, catalog:fileSpec():catalogPath())

	return catalog:docStore():applyOverlay( overlay )
end

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

local function createIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath,
												proxyFilePath, sourceAssetId, sourceRevisionId, optRedactionsTable )

	local baseAssetDoc = WFCatAssetTestUtils.createBaseAssetDocument( catalog:ozCatalogId(), assetId, revisionId, sha256, originalFilePath )

	baseAssetDoc._spaceId = spaceId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForIACAsset( baseAssetDoc, spaceId, assetId, revisionId )

	if originalFilePath then
		WFCatAssetTestUtils.addOriginalPathToDocument( baseAssetDoc, sha256, originalFilePath )
	end

	if proxyFilePath then
		WFCatAssetTestUtils.addProxyPathToDocument( baseAssetDoc, proxyFilePath )
	end

	local maybeSourceOriginalLink, maybeSourceXmpLink, maybeSourceXmpDevelopLink
	if sourceAssetId and sourceRevisionId then
		maybeSourceOriginalLink = '/v2/catalogs/' .. catalog:ozCatalogId() .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/master'
		maybeSourceXmpLink = '/v2/catalogs/' .. catalog:ozCatalogId() .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/xmp'
		maybeSourceXmpDevelopLink = '/v2/catalogs/' .. catalog:ozCatalogId() .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/xmp/develop'
	end

	local universal
	if optRedactionsTable then
		universal = {
			sharedwithyou = {
				contribution = optRedactionsTable
			}
		}
	end

	baseAssetDoc = WFTableMerge.applyOverlay( baseAssetDoc, {
		_localOnly = {
			sourceOriginalLink = maybeSourceOriginalLink,
			sourceXmpLink = maybeSourceXmpLink,
			sourceXmpDevelopLink = maybeSourceXmpDevelopLink,
			universal = universal,
		}
	} )

	return baseAssetDoc

end

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

local function addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath,
												proxyFilePath, sourceAssetId, sourceRevisionId, optRedactionsTable )

	local iacAssetDoc = createIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath,
												proxyFilePath, sourceAssetId, sourceRevisionId, optRedactionsTable )


	return catalog:docStore():setDocValue( iacAssetDoc )

end

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

local function addIACWillNotUploadMasterDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, proxyFilePath, sourceAssetId, sourceRevisionId, sourceProxyId, optRedactionsTable )

	local iacAssetDoc = createIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, nil,
												proxyFilePath, sourceAssetId, sourceRevisionId, optRedactionsTable )

	KSTableUtils.setNested( iacAssetDoc, true, 'importSource', 'willNotUploadMaster' )
	KSTableUtils.setNested( iacAssetDoc, nil, '_localOnly', 'sourceOriginalLink' )

	local maybeSourceProxyLink
	if sourceAssetId and sourceRevisionId and sourceProxyId then
		maybeSourceProxyLink = '/v2/catalogs/' .. catalog:ozCatalogId() .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/proxies/' .. sourceProxyId
	end
	KSTableUtils.setNested( iacAssetDoc, maybeSourceProxyLink, '_localOnly', 'sourceProxyLink' )

	return catalog:docStore():setDocValue( iacAssetDoc )
end

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

local function addCreateLinksToDoc( catalog, catalogId, assetId, revisionId )

	local hrefPrefaceForDirectCreate = string.format( '/v2/catalogs/%s/assets/%s/', catalogId, assetId )
	local hrefPrefaceForCreate = string.format( '%srevisions/{revision_id}/', hrefPrefaceForDirectCreate )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		-- add the oz metadata overlay so we know where to upload it to
		_serverProvidedMetadata = {
			links = {
				[ '/rels/master_create' ] = {
					href = hrefPrefaceForCreate .. 'master',
					templated = true,
				},
				[ '/rels/main_create/parts' ] = {
					href = hrefPrefaceForCreate .. 'main/parts?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}',
					templated = true,
				},
				[ '/rels/proxy_create' ] = {
					href = hrefPrefaceForCreate .. 'proxies/{proxy_id}',
					templated = true,
				},
				[ '/rels/xmp_develop_create' ] = {
					href = hrefPrefaceForCreate .. 'xmp/develop',
					templated = true,
				},
				[ '/rels/xmp_develop_create/parts' ] = {
					href = hrefPrefaceForDirectCreate .. 'sha256/{sha256}/xmp/develop/parts?access=put&file_size={file_size}&part_size={part_size}&upload_status={upload_status}&content_type={content_type}',
					templated = true,
				},
				[ '/rels/versions/xmp_develop_create/parts' ] = {
					href = hrefPrefaceForDirectCreate .. '/versions/{version_id}/sha256/{sha256}/xmp/develop/parts?access=put&file_size={file_size}&part_size={part_size}&upload_status={upload_status}&content_type={content_type}',
					templated = true,
				},
				[ '/rels/xmp_create' ] = {
					href = hrefPrefaceForCreate .. 'xmp',
					templated = true,
				},
			},
			revision_ids = {
				revisionId,
			},
		},
	}

end

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

local function addAuxTempLinkToDoc( catalog, assetId, auxSha256 )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/temp/aux/' .. auxSha256 ] = {
					exists = true,
				},
			},
		},
	}

end

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

local function addAuxExistsToDoc( catalog, assetId, auxId )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/aux/' .. auxId ] = {
					exists = true,
				},
			},
		},
	}

end

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

local function addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxId, auxSha256)

	return catalog:docStore():applyOverlay {
		_id = assetId,
		-- add the oz metadata overlay so we know where to upload it to
		_serverProvidedMetadata = {
			links = {
				[ '/rels/aux/' .. auxId ] = {
					href = '/v2/catalogs/' .. catalogId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/aux' .. auxSha256,

				},
			},
		},
	}

end

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

local function addAuxCreateLinksToDoc( catalog,  assetId)

	return catalog:docStore():applyOverlay {
		_id = assetId,
		-- add the oz metadata overlay so we know where to upload it to
		_serverProvidedMetadata = {
			links = {
				[ '/rels/aux_create/parts' ] = {
					href = 'mock-upload-point'

				},
			},
		},
	}

end

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

local function addCreateLinksToVersionDoc( catalog, catalogId, versionId, versionRevisionId, noCreateLink )

	local hrefPrefaceForCreate = '/v2/catalogs/' .. catalogId .. '/versions/' .. versionId .. '/revisions/{revision_id}'

	return catalog:docStore():applyOverlay {
		_id = versionId,
		-- add the oz metadata overlay so we know where to upload it to
		_serverProvidedMetadata = {
			links = {
				[ '/rels/xmp_develop_create' ] = not noCreateLink and {
					href = hrefPrefaceForCreate .. '/xmp/develop',
					templated = true,
				} or nil,
				[ 'self' ] = {
					href = hrefPrefaceForCreate:replace( '{revision_id}', versionRevisionId )
				}
			},
			revision_ids = {
				versionRevisionId,
			},
		},
	}

end

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

local function addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId )

	-- Uploading and copying a master will also set the /xmp field so set that as well

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/master' ] = {
					href = "/v2/catalogs/" .. catalogId .. "/assets/".. assetId .. "/revisions/" .. revisionId .. "/master",
				},
				[ '/rels/xmp' ] = {
					href = "/v2/catalogs/" .. catalogId .. "/assets/".. assetId .. "/revisions/" .. revisionId .. "/xmp",
				},
			},
		},
	}

end

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

local function addXmpLinkToDoc( catalog, catalogId, assetId, revisionId )

	-- Uploading and copying a master will also set the /xmp field so set that as well

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/xmp' ] = {
					href = "/v2/catalogs/" .. catalogId .. "/assets/".. assetId .. "/revisions/" .. revisionId .. "/xmp",
				},
			},
		},
	}

end

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

local function addProxyLinkToDoc( catalog, catalogId, assetId, revisionId, maybeProxyId )

	local proxyId = maybeProxyId or KSIdGenerator.uuidInLowercaseHex()

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/proxy_type/2560' ] = {
					href = "/v2/catalogs/" .. catalogId .. "/assets/".. assetId .. "/revisions/" .. revisionId .. "/proxies/" .. proxyId,
				},
			},
		},
	}

end

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

local function addMasterExistsToDoc( catalog, catalogId, assetId )

	-- Uploading and copying a master will also set the /xmp field so set that as well

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/master' ] = {
					exists = true,
				},
			},
		},
	}

end

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

local function addProxyExistsToDoc( catalog, catalogId, assetId )

	-- Uploading and copying a master will also set the /xmp field so set that as well

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/proxy_type/2560' ] = {
					exists = true,
				},
			},
		},
	}

end

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

local function addXmpExistsToDoc( catalog, catalogId, assetId )

	-- Uploading and copying a master will also set the /xmp field so set that as well

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/xmp' ] = {
					exists = true,
				},
			},
		},
	}

end


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

local function addXmpDevelopTempLinkToDoc( catalog, assetId, sha256 )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/temp/xmp/develop/' .. sha256 ] = {
					exists = true,
				},
			},
		},
	}

end

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

local function addXmpDevelopExistsToDoc( catalog, catalogId, assetId, sha256 )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/xmp/develop' ] = {
					exists = true,
					sha256 = sha256,
				},
			},
		},
	}

end

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

local function addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, sha256 )

	return catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/xmp/develop' ] = {
					href = "/v2/catalogs/" .. catalogId .. "/assets/".. assetId .. "/revisions/" .. revisionId .. "/xmp/develop",
					sha256 = sha256,
				},
			},
		},
	}

end

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

local function addOriginalToDocPath( catalog, assetId, sha256, originalFilePath )

	local overlay = WFCatAssetTestUtils.getOriginalPathOverlay( assetId, sha256, originalFilePath )

	return catalog:docStore():applyOverlay( overlay )

end

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

local function addRevisionNotSyncedToOz( rxTest, catalog, assetId, revisionId, atomicDevelopSettings )

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_newRevId = revisionId,
		userUpdated = KSDate.timeInIso8601FormatNearestSecond(),
	} )

	-- SQL query to ensure that needsBinaryUpload is updated as expected before restarting uploads

	local queryParams = {
		sql = "SELECT docId FROM needsBinaryUpload WHERE docId = ? AND binaryType IN ( ?, ? ) AND isReadyToUpload & ?",
		singleColumnQuery = true,
		assetId,
	}

	if atomicDevelopSettings then

		-- for ADS, master and proxy will not upload in this state but other binaries will
		queryParams[ #queryParams + 1 ] = 'master'
		queryParams[ #queryParams + 1 ] = 'proxy'

		local kReadyToUploadAtomic = 2
		queryParams[ #queryParams + 1 ] = kReadyToUploadAtomic

	else

		-- for non-ADS, aux and xmp_develop will not upload in this state but other binaries will
		queryParams[ #queryParams + 1 ] = 'aux'
		queryParams[ #queryParams + 1 ] = 'xmp_develop'

		local kReadyToUploadLegacy = 1
		queryParams[ #queryParams + 1 ] = kReadyToUploadLegacy

	end

	rxTest:expectSingleValue( catalog:indexStore():queryOnce( queryParams ), {} )

end

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

local function revisionSyncedToOz( rxTest, catalog, assetId, revisionIds )

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			revision_ids = revisionIds,
		}
	} )

end

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

local function applyErrorOverlay( rxTest, catalog, assetId, subtype )

	rxTest:waitFor(
		catalog:docStore():applyOverlay {
			_id = assetId,
			_localOnly = {
				ozError = {
					subtype = subtype,
					status = 404,
					retries = 2,
					lastRetry = KSDate.timeInIso8601Format(),
				}
			}
		}
	)

end

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

local function watchForXmpAndMasterUploads( rxTest, catalog, catalogSpec, uploader, catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId )

	local httpRequestsSoFar
	rxTest:waitFor( catalogSpec._httpMock:requestsProcessed():map( function( numRequests ) httpRequestsSoFar = numRequests end ):take( 1 ) )

	-- update the expected url/response pairs
	local errorCode = nil
	local readMetadataNone = true
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId,
			errorCode, readMetadataNone ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur (the xmp sidecar)
	httpRequestsProcessed:expectDataWithinTime( 2, httpRequestsSoFar + 1 )

	-- Make it look like metadatapuller got an update with the xmp link in it to let the
	-- uploader know the xmp has been succesfully copied and the master can be uploaded (with no X-Read-Meta)
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	-- expect the second PUT to occur (the master)
	httpRequestsProcessed:expectDataWithinTime( 2, httpRequestsSoFar + 2 )

	-- Make it look like metadatapuller got an update with the master link in it to let the uploader know the
	-- master has been successfully copied. In the xmp sidecar case we end up seting the xmp link again here,
	-- but that's a no-op and is no big deal
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

end

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

local function watchForXmpAndMasterUploadsForIAC( rxTest, catalog, catalogSpec, uploader, spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, optErrorCode, optRedactionsHeader )

	local httpRequestsSoFar
	rxTest:waitFor( catalogSpec._httpMock:requestsProcessed():map( function( numRequests ) httpRequestsSoFar = numRequests end ):take( 1 ) )

	catalogSpec:expectHttpRequest(
		WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, optErrorCode, optRedactionsHeader )
	)

	catalogSpec:expectHttpRequest(
		WFCatBinaryUploaderTestUtils.generateCopyMasterResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, optErrorCode, optRedactionsHeader )
	)

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, httpRequestsSoFar + 1 )

	-- Make it look like metadatapuller got an update with the /rels/xmp exists: true in it to let the
	-- uploader know the xmp is copied and the master can now be copied/uploaded.
	rxTest:waitFor( addXmpExistsToDoc( catalog, catalogId, assetId ) )

	httpRequestsProcessed:expectDataWithinTime( 2, httpRequestsSoFar + 2 )

	-- Make it look like the metadatapuller got an update with the /rels/master exists: true For In App
	-- Contribute we don't always get a /rels/master href link due to sharing restrictions so IAC
	-- uploads must look for the exists: true field on /rels
	rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

end

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

--[[

	\brief setup WFCatMockCatalog, WFCatSpecSpy and WFCatBinaryUploader for mock tests

	\param rxTest (WFRxTestCaseRunner)
	\param catalogId (string) - uuid of catalog
	\param urlResponses (optional table) - list of mock url request/responses
	\param options (table) - Optional modifiers:

		enableRateLimiter (boolean) - enable rate limiter

		unordered (boolean) - if true request mocks not expected in specific order

		atomicDevelopSettings (boolean) if true then enable ADS feature in uploader

		clearFromRecentlyUploadedSeconds (integer) optional override for this setting
				for uploader. Default is 1.

		forceDocIdUploadOrdering (boolean) if true, uploader SQLite queries will include
				an additional ORDER BY docId statement. Useful in validating tests
				that require strict upload ordering.

	\return catalog, catalogSpec, uploader

]]
local function setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses, options )

	options = options or {}

	local userId = KSIdGenerator.uuidInLowercaseHex()

	local indexSpecScript
	if WFPlatformInfo.isMobile() then
		indexSpecScript = 'LrmCatalogIndexDefinitions.lua'
	end

	local catalogSpec = WFCatSpecMock( catalogId, userId, rxTest, urlResponses, options.enableRateLimiter, options.unordered )
	local catalog = WFCatMockCatalog.createWFCatalogForTestWithSpec( rxTest, catalogSpec, nil, indexSpecScript, nil )

	local uploaderOpts = {
		clearFromRecentlyUploadedSeconds = options.clearFromRecentlyUploadedSeconds or 1,
		waitForOtherUploadSeconds = .5,
		waitForCopyFailedSeconds = .5,
		timer = function( seconds )
			return Observable.timer( seconds, WFRxScheduler.sharedScheduler() )
		end,
		capabilities = {
			[ 'WFCatBinaryUploader.atomicDevelopSettings' ] = options.atomicDevelopSettings,
		},
		forceDocIdUploadOrdering = not not options.forceDocIdUploadOrdering
	}
	if options.uploaderOpts then
		uploaderOpts = KSTableUtils.merge( {}, uploaderOpts, options.uploaderOpts )
	end

	local uploader = WFCatBinaryUploader( catalog, nil, taskDelays, catalogSpec, uploaderOpts )

	uploader:DEBUG_resetDiagnosticLogErrors()

	rxTest:finally( function()

		uploader:stopContinuousUploads()
		uploader:close()

		-- Work around an async close issue causing WFCatImportDuplicate not to
		-- get it's closeAction called before the next test is started.
		if sCoveragePath then
			catalog:indexStore():DEBUG_dumpCoverage( sCoveragePath )
		end

		catalog:indexStore():close()

	end )

	return catalog, catalogSpec, uploader

end

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

-- Return a reference to catalogTestSession to prevent it from being GCed before the puller uses it.
local function setupCatalogAndDocStore( rxTest )

	local catalogTestSession = WFCatTestSession( rxTest )

	local catalog, catalogSpec, catalogId, docStore

	rxTest:waitFor(

		catalogTestSession:catalog()
			:tap( function( _catalog )
				check( 'WFCatalog' )
				catalog = _catalog
				catalogSpec = _catalog:ozCatalogSpec()
				catalogId = _catalog:ozCatalogSpec():catalogId()
				docStore = catalog:docStore()
			end ) )

	return catalogTestSession, catalog, catalogSpec, catalogId, docStore

end

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

local function createAlbumAssetDocForSpaceAsset( rxTest, catalog, spaceId, assetId )

	local sql
	if WFPlatformInfo.isMobile() then
		sql = [[ SELECT docId FROM albums WHERE spaceId == ? ]]
	else
		sql = [[ SELECT albumId FROM albums WHERE spaceId == ? ]]
	end
	local albumId = rxTest:getFirstValue(
		catalog:indexStore(): query( {
			sql = sql,
			spaceId,
			singleColumnQuery = true }
		)
		:filter( function( albumIds )
			return #albumIds > 0
		end )
		:pluck( 1 )
		:take( 1 ) )

	-- asset will not show up in space feed unless both asset and album_asset documents are pushed

	local albumAssetDoc = {
		_id = KSIdGenerator.uuidInLowercaseHex(),
		album = {
			id = albumId,
		},
		asset = {
			id = assetId,
		},
		type = "album_asset",
		_spaceId = spaceId
	}

	return albumAssetDoc

end

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

--[[!
	\brief Sets up catalog, pusher, puller and uploader for end to end tests

	\param rxTest (rxTest)
	\param optOverrides (optional table) Optional document modifiers:

		uploaderOpts (table): Options that will be merged with default options to
			create options passed into WFCatBinaryUploader constructor

		spyFn (function): function that will be use as WFCatSpecSpy

		inAppContributeRole (string): if non-nil then set test up to push to a space from
			a contributor catalog. Role should be editor or contributor (viewers cannot
			upload binaries).

		atomicDevelopSettings (boolean): true to enable atomicDevelopSettings for binaryUplaoder

	\return catalogTestSession, catalog, catalogSpec, catalogId, pusher, puller, uploader,
			spaceId, spaceFeedsManager, conflictResolver

--]]
local function setupPusherPullerAndUploaderForNightly( rxTest, optOverrides )

	optOverrides = optOverrides or {}

	local catalogTestSession, catalog, catalogSpec, catalogId, spaceId

	if optOverrides.inAppContributeRole then

		local _, _, _, c, cs, cts, sid, _, _, _ = WFCatOzTestSession.setupSpaceOzTest( rxTest, optOverrides.inAppContributeRole )
		catalogTestSession, catalog, catalogSpec, spaceId = cts, c, cs, sid

	else

		catalogTestSession, catalog, catalogSpec, catalogId = setupCatalogAndDocStore( rxTest )

	end

	local capabilities = {
		[ 'WFCatBinaryUploader.atomicDevelopSettings' ] = optOverrides.atomicDevelopSettings,
	}

	local pusher, puller, spaceFeedsManager, conflictResolver

	if spaceId then

		-- start up space feeds manager. start syncing down but not up yet.
		-- Some tests need to control when sync up starts.

		spaceFeedsManager = WFCatOzTestSession.createSpaceFeedsManager( rxTest, catalog,
			{ capabilities = capabilities } )

		spaceFeedsManager:reportCatalogInitialSyncIsComplete()
		spaceFeedsManager:startFetchingUpdates()

		-- because of the way the space feeds manager immediately downloads some updates
		-- which will later sync in the space feed, there WILL be conflicts to resolve
		-- which may block pushing asset and album_asset updates needed for the test to pass.

		conflictResolver = require 'WFCatConflictResolver'( catalog:docStore(), catalog:indexStore() )
		rxTest:addCloseTarget( conflictResolver )

	else

		local pusherOptions = KSTableUtils.merge( {}, options, { capabilities = capabilities } )
		pusher = WFCatMetadataPusher( catalog, pusherOptions )
		rxTest:addCloseTarget( pusher )

		puller = WFCatMetadataPuller( catalog, "Test Puller", options )
		rxTest:addCloseTarget( puller )

	end

	local defaultUploaderOpts = {
		clearFromRecentlyUploadedSeconds = 1,
		waitForOtherUploadSeconds = .5,
		waitForCopyFailedSeconds = .5,
		timer = function( seconds )
			return Observable.timer( seconds, WFRxScheduler.sharedScheduler() )
		end,
	}

	local uploaderOpts = optOverrides.uploaderOpts and KSTableUtils.merge( {}, defaultUploaderOpts, optOverrides.uploaderOpts ) or defaultUploaderOpts

	uploaderOpts.capabilities = { [ 'WFCatBinaryUploader.atomicDevelopSettings' ] = optOverrides.atomicDevelopSettings, }
		-- any time atomicDevelopSettings option is specified need to enable that feature in the binary uploader

	local catalogSpecArg = catalogSpec
	if optOverrides.spyFn then
		catalogSpecArg = makeWFCatSpecSpy( catalogSpec, optOverrides.spyFn )
	end

	local uploader = WFCatBinaryUploader( catalog, nil, taskDelays, catalogSpecArg, uploaderOpts )

	rxTest:addCloseTarget( uploader )

	uploader:DEBUG_resetDiagnosticLogErrors()

	rxTest:finally( function()
		if spaceFeedsManager then
			spaceFeedsManager:stopSendingUpdates()
			spaceFeedsManager:stopFetchingUpdates()
		else
			puller:stopContinuousUpdates()
			pusher:stopContinuousUpdates()
		end
		uploader:stopContinuousUploads()
	end )

	return catalogTestSession, catalog, catalogSpec, catalogId, pusher, puller, uploader,
			spaceId, spaceFeedsManager, conflictResolver

end

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

local function setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath )

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()

	if not originalFilePath then
		originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	end

	if not sha256 then
		sha256 = WFCrypto.digestFile( originalFilePath, 'SHA256' )
	end

	local assetDoc = WFCatAssetTestUtils.createBaseAssetDocument( catalogId, assetId, revisionId, sha256, originalFilePath )
	assetDoc = WFCatAssetTestUtils.addOriginalPathToDocument(assetDoc, sha256, originalFilePath)
	if proxyFilePath then
		assetDoc = WFCatAssetTestUtils.addProxyPathToDocument( assetDoc, proxyFilePath )
	end

	return assetId, revisionId, originalFilePath, sha256, assetDoc

end

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

local function _testOzBasedOriginalUpload( rxTest, originalFilePath )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ))

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		-- WF-1813 need to check presence of the master link & no "invalid=true" in master link & no sha256 error in importsource
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

end

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

-- FIXME [jandrew 2025-03-11]: Disabled due to instability.

function WFCatBinaryUploaderTest.DISABLED_rxTestUploadRaceConditionWithChangingSha256_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local function noRetryOnPutError( self, url, data, options, progressReporter )
		options.maxAttempts = 1
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local _, catalog, _, _, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { spyFn = noRetryOnPutError } )
	local docStore = catalog:docStore()

	-- Import a file.

	local importFolder = WFTestFileUtils.createTestFolder( rxTest )
	local testFileName = "test.xmp"
	local testFilePath = KSPathUtils.child( importFolder, testFileName )

	local file = io.open( testFilePath, 'wb' )
	file:write( "<xmp/>" )
	file:close()

	local initialSha256 = WFCrypto.digestFile( testFilePath, 'SHA256' )
	local initialSize = WFFileUtils.fileAttributes( testFilePath ).fileSize

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local localOnlyFilesOverlay = {
		original = {
			master = {
				volumeGuid = WFFileUtils.getVolumeGuidForPath( testFilePath ),
				relativePath = WFFileUtils.removeVolumeFromPath( testFilePath ),
				sha256 = initialSha256,
				size = initialSize,
			}
		}
	}

	if WFPlatformInfo.isMobile() then
		localOnlyFilesOverlay = {
			presetProfile = {
				path = testFilePath,
				sha256 = initialSha256,
				size = initialSize,
			}
		}
	end

	rxTest:waitFor(
		docStore:setDocValue {
			_id = assetId,
			type = 'asset',
			subtype = 'preset',
			name = 'Test',
			_localOnly = {
				binaryUpload = {
					waitForInvalid = initialSha256
				},
				files = localOnlyFilesOverlay,
			},
			importSource = {
				fileSize = initialSize,
				sha256 = initialSha256,
				contentType = 'application/rdf+xml',
			}
		} )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( docStore:ongoingDocValue( assetId )
			:map( function( docValue )
				return not not docValue._serverProvidedMetadata
			end ) )

	docObserver:expectDataWithinTime( 15, true )
	rxTest:sleep(.25)

	-- change the file
	file = io.open( testFilePath, 'wb' )
	file:write( "<xmp>updated</xmp>" )
	file:close()

	local updatedSha256 = WFCrypto.digestFile( testFilePath, 'SHA256' )
	local updatedSize = WFFileUtils.fileAttributes( testFilePath ).fileSize

	local updatedLocalOnlyFilesOverlay = {
		original = {
			master = {
				volumeGuid = WFFileUtils.getVolumeGuidForPath( testFilePath ),
				relativePath = WFFileUtils.removeVolumeFromPath( testFilePath ),
				sha256 = updatedSha256,
				size = updatedSize,
			}
		}
	}

	if WFPlatformInfo.isMobile() then
		updatedLocalOnlyFilesOverlay = {
			presetProfile = {
				path = testFilePath,
				sha256 = updatedSha256,
				size = updatedSize,
			}
		}
	end

	rxTest:waitFor(
		docStore:applyOverlay( {
			_id = assetId,
			_localOnly = {
				binaryUpload = {
					waitForInvalid = updatedSha256,
				},
				files = updatedLocalOnlyFilesOverlay,
			},
			importSource = {
				fileSize = updatedSize,
				sha256 = updatedSha256,
			}
		} ) )

	local finalDocObserver = rxTest:observe( docStore:ongoingDocValue( assetId )
		:map( function( docValue )
			return not not ( ( derefOpt( docValue, 'importSource', 'sha256' ) == updatedSha256 )
				and derefOpt( docValue,  '_serverProvidedMetadata', 'links', '/rels/master' )
				and	( derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'invalid' ) ~= true ) )
		end ) )

	finalDocObserver:expectDataWithinTime( 360, true )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		-- WF-1813 need to check presence of the master link & no "invalid=true" in master link & no sha256 error in importsource
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_directUploadLinkMissing_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, _, sha256, assetDoc = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- start up sync engine but not binary pusher

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()

	-- wait for initial revision to sync to the cloud (and back)

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return not not docValue._serverProvidedMetadata
		end ) )
	docObserver:expectDataWithinTime( 60, true )

	-- remove the original direct upload link from the document to mimic scenario
	-- where asset that has not synced from Oz since direct upload feature shipped
	-- so will reload links before starting direct upload

	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/main_create/parts' ] = { _delete = true }
			}
		}
	} ) )

	-- this removal should not cause either the pusher or puller to do work.
	-- Link should not refresh until binary uploader starts working.

	local isPushing = rxTest:observe( pusher:isDoingWork() )
	isPushing:expectDataWithinTime( 5, false )

	local isPulling = rxTest:observe( puller:isDoingWork() )
	isPulling:expectDataWithinTime( 5, false )

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/main_create/parts' )
		:map( function( link )
			return not not link
		end )
	)
	docObserver:expectDataWithinTime( 5, false )

	-- now start uploader, should reload the link and upload to Oz.

	uploader:startContinuousUploads()

	docObserver:expectDataWithinTime( 10, true )
		-- once uploader starts, should refresh the link and upload via direct upload

	-- confirm is direct upload via sync diagnostics

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.01 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	syncDiagnosticsObserver:expectDataWithinTime( 10,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 1 binaries to Oz.",
			uploadsByType = {
				catalog = {
					master = {
						[ 'S3 Uploads' ] = 1,
						total = 1,
					},
					[ 'S3 Uploads' ] = 1,
					total = 1,
				}
			}
		} )

	-- confirm original binary synced to Oz and metadata confirmation synced back

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )
	docObserver:expectDataWithinTime( 60, true )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_SDR_JPG_testNightly( rxTest )

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	_testOzBasedOriginalUpload( rxTest, originalFilePath )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_HDR_JXL_testNightly( rxTest )

	local originalFilePath = KSPathUtils.child( hdrTestDataFolder, "hdr.jxl" )

	_testOzBasedOriginalUpload( rxTest, originalFilePath )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_HDR_AVIF_testNightly( rxTest )

	local originalFilePath = KSPathUtils.child( hdrTestDataFolder, "hdr.avif" )

	_testOzBasedOriginalUpload( rxTest, originalFilePath )

end

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

local previousStartTimes = {}
local previousEndTimes = {}
local previousProcessedCounts = {}

local function verifyEventAndMapAwayCruft( rxTest, event, assetId, optOffset )

	local previousKey = string.format( "%s:%s", assetId, optOffset )

	if event then

		-- Processed count show only ever increase.

		if previousProcessedCounts[ previousKey ] then
			rxTest:expectEqual( true, event.processedCount >= previousProcessedCounts[ previousKey ] )
		end

		previousProcessedCounts[ previousKey ] = event.processedCount

		-- Processed count show never be greater than the total count.

		rxTest:expectEqual( true, event.processedCount <= event.totalCount )

		-- Start time is immutable

		local startTime = derefOpt( event, 'extraInfo', 'startTime' )

		if previousStartTimes[ previousKey ] then
			rxTest:expectEqual( previousStartTimes[ previousKey ], startTime )
		end

		previousStartTimes[ previousKey ] = startTime

		-- End time is immutable.

		local endTime = derefOpt( event, 'extraInfo', 'endTime' )

		if previousEndTimes[ previousKey ] then
			rxTest:expectEqual( previousEndTimes[ previousKey ], endTime )
		end

		previousEndTimes[ previousKey ] = endTime

		local timestamp = derefOpt( event, 'extraInfo', 'timestamp' )

		local percentComplete = event.percentComplete

		if percentComplete > 0 and percentComplete < 1 then
			percentComplete = '<number>'
		end

		local processedCount = event.processedCount

		if processedCount > 0 and processedCount < event.totalCount then
			processedCount = '<number>'
		end

		return {
			errorCount = event.errorCount,
			extraInfo = {
				timestamp = timestamp and '<number>',
				endTime = endTime and '<number>',
				startTime = startTime and '<number>',
			},
			percentComplete = percentComplete,
			processedCount = processedCount,
			totalCount = event.totalCount,
		}

	end

	return {}

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_WithProgress_testNightly( rxTest )

	rxTest:setTimeLimit( 60 )

	local uploaderOpts = { fallbackToOzUploadForOriginalsOnly = true, forceIncludeProgress = true }
	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { uploaderOpts = uploaderOpts } )

	-- Time remaining is hard to verify, so we just verify that the upload reported
	-- taking longer than 0 seconds at some point in time.

	local timeRemainingObserver = rxTest:observe( uploader:getEstimatedTimeRemaining()
		:map( function( timeRemaining )
			return timeRemaining > 0
		end ) )

	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_7763.JPG" )
	local assetId, _, _, sha256, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local progressEventObserver = rxTest:observe( uploader:DEBUG_getInFlightUploadProgressUpdateStream()

		:map( function( events )

			local event = derefOpt( events, assetId, 'master', sha256 )

			return verifyEventAndMapAwayCruft( rxTest, event, assetId )

		end ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	progressEventObserver:expectDataWithinTime( 100, {
		errorCount = 0,
		extraInfo = {
			timestamp = '<number>',
			startTime = "<number>",
		},
		percentComplete = 0,
		processedCount = 0,
		totalCount = 2072173
	} )

	progressEventObserver:expectDataWithinTime( 50, {
		errorCount = 0,
		extraInfo = {
			timestamp = '<number>',
			startTime = '<number>',
		},
		percentComplete = '<number>',
		processedCount = '<number>',
		totalCount = 2072173
	} )

	timeRemainingObserver:expectDataWithinTime( 10, true )

	progressEventObserver:expectDataWithinTime( 10, {
		errorCount = 0,
		extraInfo = {
			endTime = '<number>',
			timestamp = '<number>',
			startTime = '<number>',
		},
		percentComplete = 1,
		processedCount = 2072173,
		totalCount = 2072173
	} )

	progressEventObserver:dispose()

	timeRemainingObserver:expectDataWithinTime( 60, false )

	timeRemainingObserver:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_DirectUpload_WithProgress_testNightly( rxTest )

	rxTest:setTimeLimit( 120 )

	local uploaderOpts = { forceIncludeProgress = true }
	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { uploaderOpts = uploaderOpts } )

	-- Time remaining is hard to verify, so we just verify that the upload reported
	-- taking longer than 0 seconds at some point in time.

	local timeRemainingObserver = rxTest:observe( uploader:getEstimatedTimeRemaining()
		:map( function( timeRemaining )
			return timeRemaining > 0
		end ) )

	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_7763.JPG" )
	local assetId, _, _, sha256, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local progressEventObserver = rxTest:observe( uploader:DEBUG_getInFlightUploadProgressUpdateStream()

		:map( function( events )

			local wrapperEvent = derefOpt( events, assetId, 'master', sha256 )

			local strippedWrapperProgress = verifyEventAndMapAwayCruft( rxTest, wrapperEvent, assetId )

			local allStrippedProgress = { strippedWrapperProgress }

			local chunkedEvents = derefOpt( wrapperEvent, 'extraInfo', 'progressOfChunks' )  or {}

			for offset, chunkedProgress in pairs( chunkedEvents ) do
				allStrippedProgress[ #allStrippedProgress + 1 ] = verifyEventAndMapAwayCruft( rxTest, chunkedProgress, assetId, offset )
			end

			return allStrippedProgress

		end ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	progressEventObserver:expectDataWithinTime( 10, {
		{
			errorCount = 0,
			extraInfo = {},
			percentComplete = 0,
			processedCount = 0,
			totalCount = 2072173
		}
	} )

	progressEventObserver:expectDataWithinTime( 5, {
		{
			errorCount = 0,
			extraInfo = {},
			percentComplete = '<number>',
			processedCount = '<number>',
			totalCount = 2072173
		},
		{
			errorCount = 0,
			extraInfo = {
				startTime = "<number>",
				timestamp = "<number>",
			},
			percentComplete = "<number>",
			processedCount = "<number>",
			totalCount = 2072173,
		},
	} )

	timeRemainingObserver:expectDataWithinTime( 60, true )

	progressEventObserver:expectDataWithinTime( 10, {
		{
			errorCount = 0,
			extraInfo = {},
			percentComplete = 1,
			processedCount = 2072173,
			totalCount = 2072173
		},
		{
			errorCount = 0,
			extraInfo = {
				timestamp = "<number>",
				endTime = "<number>",
				startTime = "<number>",
			},
			percentComplete = 1,
			processedCount = 2072173,
			totalCount = 2072173,
		},
	} )

	progressEventObserver:dispose()

	timeRemainingObserver:expectDataWithinTime( 60, false )

	timeRemainingObserver:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadAdd( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
		-- code has changed so that if direct upload template link for original binary is missing,
		-- we will reload the asset document to get it, but not for spaces since direct upload
		-- of original was not implemented for spaces.

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.001 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	local assetOptions = { spaceId = spaceId }
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) )

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
			uploadsByType = {}
		} )

	uploader:startContinuousUploads()

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {
				[ assetId ] = {
					master = true,
				},
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 1,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			scheduledUpload = {
				binaryType = "master",
				docId = assetId,
				priority = -10,
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 1 binaries to Oz.",
			uploadsByType = {
				[ spaceId ] = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	completionStream:expectDataWithinTime( 5, assetId )
	completionStream:dispose()

	-- Upload Finished

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1, -- haven't yet received Oz response with the master link
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 1 binaries to Oz.",
			uploadsByType = {
				[ spaceId ] = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 0,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 1 binaries to Oz.",
			uploadsByType = {
				[ spaceId ] = {
					master = {
						total = 1,
					},
					total = 1,
				}
			}
		} )

	syncDiagnosticsObserver:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_dontUploadInternalXmpDevelop( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local setupOptions = {
		atomicDevelopSettings = true
	}
	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, setupOptions )

	local assetDocOpts = {
		internalXmpCameraRaw = true,
	}
	local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId,  assetDocOpts )

	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	-- add document to the doc store, which should trigger nothing in the upload code

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- nothing should have been added to needsBinaryUpload table

	rxTest:expectSingleValue( catalog:indexStore():queryOnce( {
		sql = "SELECT docId FROM needsBinaryUpload WHERE docId = ?",
		singleColumnQuery = true,
		assetId,
	} ), { } )

	-- start the uploader and nothing should upload

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, false )

	local expectedSyncDiagnostics = {
		documentsNeedingBinaryUpload = 0,
		inFlightUploads = {},
		isCurrentlySyncing = false,
		numberOfInFlightUploads = 0,
		numberOfScheduledBinaryUploads = 0,
		recentlyUploaded = { },
		summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
		uploadsByType = { },
	}

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.01 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpDevelop( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	local assetDocOpts = {
		localXmpSidecarDoesNotExist = true, noXmpSidecarLink = true, noMasterLink = true, noDevelopSettingsLink = true,
	}
	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId,  assetDocOpts )

	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	-- expect the upload of the xmp_develop file and then the master

	local mocks = { }

	local xmpDevelopFilePath = localOnly.xmpDevelopSettingsFilePath
	table.insert( mocks, WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponse( catalogId, assetId, revisionId, xmpDevelopFilePath ) )

	WFCatBinaryUploaderTestUtils.getOriginalDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks, revisionId )

	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	-- add document to the doc store and start uploading

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- make it look like the metadatapuller got an update with the xmp_develop link in it
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, localOnly.developSettingsSha256 ) )
	httpRequestsProcessed:expectDataWithinTime( 2, 4 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpDevelop_copy( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	local assetDocOpts = {
		noLocalProxy = true, localXmpSidecarDoesNotExist = true, noXmpSidecarLink = true, noMasterLink = true, noDevelopSettingsLink = true,
	}
	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId,  assetDocOpts )

	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	-- add sourceXmpDevelopLink and sourceXmpDevelopSha256 to asset so the uploader issues a copy

	local sourceUrl = '/this/is/a/sourceXmpDevelopLink'
	local sourceSha256 = assetDoc.develop.xmpCameraRaw.sha256
	assetDoc = WFTableMerge.applyOverlay( assetDoc, { _localOnly = { sourceXmpDevelopLink = sourceUrl, sourceXmpDevelopSha256 = sourceSha256 } } )

	-- expect the upload of the xmp_develop file and then the master

	local mocks = { }

	table.insert( mocks, WFCatBinaryUploaderTestUtils.generateCopyXmpDevelopResponseWithUrl( catalogId, assetId, revisionId, sourceUrl ) )

	WFCatBinaryUploaderTestUtils.getOriginalDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks, revisionId )

	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	-- add document to the doc store and start uploading

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- make it look like the metadatapuller got an update with the xmp_develop link in it
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, localOnly.developSettingsSha256 ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 4 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	uploader:stopContinuousUploads()

	-- WF-1323: If the binary uploader has a sourceXmpDevelopLink but there's already been
	-- an XMP upload, the sourceXmpDevelopLink has already been uploaded and should be ignored
	-- for all future uploads. This should force the uploader to look at the actual file info
	-- and upload that instead
	rxTest:waitFor( catalog:docStore():docValue( assetId )
		:flatMap( function( docValue )
			local spm = docValue._serverProvidedMetadata
			KSTableUtils.setNested( spm, true, 'links', '/rels/xmp/develop', 'invalid' )
			return catalog:docStore():applyOverlay( { _id = assetId, _serverProvidedMetadata = spm } )
		end ) )

	local xmpDevelopFilePath = localOnly.xmpDevelopSettingsFilePath
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponse( catalogId, assetId, revisionId, xmpDevelopFilePath ) )

	uploader:startContinuousUploads()

	-- wait for the latest PUT of the xmp_develop file to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 5 )

	-- remove the invalid flag to make it look like it's been uploaded
	rxTest:waitFor( catalog:docStore():docValue( assetId )
		:flatMap( function( docValue )
			local spm = docValue._serverProvidedMetadata
			KSTableUtils.setNested( spm, { _delete = true }, 'links', '/rels/xmp/develop', 'invalid' )
			return catalog:docStore():applyOverlay( { _id = assetId, _serverProvidedMetadata = spm } )
		end ) )

	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	uploader:stopContinuousUploads()

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpDevelop_metadataSyncedFirst_atomicDevelopSettings( rxTest )

	-- test for bug WF-2429 WRT asset xmp_develop upload

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local setupOptions = {
		atomicDevelopSettings = true,
	}
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, setupOptions )

	local assetDocOpts = {
		localXmpSidecarDoesNotExist = true, noXmpSidecarLink = true, noLocalOriginal = true, noLocalProxy = true,
	}
	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, assetDocOpts )

	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	-- should have invalid annotation on xmp_develop link

	KSTableUtils.setNested( assetDoc, true, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'invalid' )
	KSTableUtils.setNested( assetDoc, nil, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'sha256' )

	-- presence of temp link should NOT prevent xmp_develop binary upload in this workflow even though ADS is enabled

	KSTableUtils.setNested( assetDoc, { exists = true }, '_serverProvidedMetadata', 'links',
			'/rels/temp/xmp/develop/' .. localOnly.developSettingsSha256 )

	-- expect the upload of the xmp_develop file NOT via direct upload even though ADS is enabled

	local xmpDevelopFilePath = localOnly.xmpDevelopSettingsFilePath
	local xmpDevelopMock = WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponse( catalogId, assetId, revisionId, xmpDevelopFilePath )
	catalogSpec:expectHttpRequest( xmpDevelopMock )

	-- add document to the doc store and start uploading

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- make it look like the metadatapuller got an update with the updated xmp_develop link

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_serverProvidedMetadata = {
			links = {
				[ '/rels/xmp/develop' ] = {
					invalid = { _delete = true },
					sha256 = localOnly.developSettingsSha256,
				},
			},
		}
	} )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

end

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

function WFCatBinaryUploaderTest.rxTestQuicklyAbortUploadOnDocumentDeletion( rxTest, fileName )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local createMasterResponse = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseWithXReadMeta( catalogId, assetId, revisionId, originalFilePath )
	createMasterResponse.responseDelayBySeconds = 15
		-- Delay the response by 15 seconds so we have time to cancel it.
	createMasterResponse.shouldNotComplete = true
		-- Marked the request as 'shouldNotComplete' to indicate that we should not expect this
		-- HTTP request to finish executing.

	local urlResponses = {
		createMasterResponse
	}

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	-- Delete the asset

	rxTest:waitFor( catalog:docStore():applyOverlay {
			_id = assetId,
			_deleted = true,
		} )

	-- As soon as we delete the asset, we should cancel the HTTP request
	-- There should be no more work. Because the response in the mock
	-- request is delayed for 15s, the fact the the following succesfully
	-- resolves in 5 seconds shows that the request was succesfully cancelled.

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

end

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

local function testMockedUploadMissingOriginal( rxTest, fileName )

	-- Test for user deleting a file from their originals directory before upload
	-- occurred, which they shouldn't be doing.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = getPathsFor( rxTest, fileName )

	local urlResponses = {
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	-- Now delete the file.
	rxTest:expectEqual( 'file', WFFileUtils.exists( originalFilePath ) )
	WFFileUtils.delete( originalFilePath )
	rxTest:expectEqual( false, WFFileUtils.exists( originalFilePath ) )

	uploader:startContinuousUploads()

	if WFPlatformInfo.isMobile() then
		-- mobile currently deletes the asset if the original is not present
		-- WF-686 should align WFCatBinaryUploader's behavior with desktop and
		-- not delete
		rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:pluck( '_empty' )
		):expectDataWithinTime( 8, true )

	else
		-- desktop marks the document as missing an original so that the client
		-- can show this info to the user and the user can take action rather
		-- than silently deleting
		rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:pluck( '_localOnly', 'localOriginalError' )
		):expectDataWithinTime( 8, 'UploadFailedMissingOriginal' )

	end

	-- Make sure no http activity took place
	rxTest:expectEqual( 0, catalogSpec._httpMock:requestCount() )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	errorsRecordedInDiagnosticLog = removeMarkingOriginalInvalidError( errorsRecordedInDiagnosticLog )
	errorsRecordedInDiagnosticLog = removeUhOhWarning( errorsRecordedInDiagnosticLog )

	if WFPlatformInfo.isMobile() then
		errorsRecordedInDiagnosticLog = removeOriginalMissingError( errorsRecordedInDiagnosticLog )
	end

	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadMissingOriginal( rxTest )

	-- This is to guard against a user deleting a file from their originals
	-- directory.

	-- Mocked version of this is good enough since it also ensures that no
	-- Http traffic takes place.

	testMockedUploadMissingOriginal( rxTest, "small.JPG" )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadMissingOriginalLargeFile( rxTest )

	-- Doesn't execute different than non-chunked version above but since this
	-- is a quick test run it with a large file that would be chunked to guard
	-- against future changes.

	testMockedUploadMissingOriginal( rxTest, "IMG_9876.CR2" )

end


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

function WFCatBinaryUploaderTest.rxTestMockedUploadFailedContentTypeMismatch( rxTest )

	-- Test for user deleting a file from their originals directory before upload
	-- occurred, which they shouldn't be doing.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
		-- code has changed so that if direct upload template link for original binary is missing,
		-- we will reload the asset document to get it. But not for assets in spaces, beacause
		-- direct upload of binaries is not supported in spaces, so can keep this test going
		-- for now by putting asset in a space.

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )

	local urlResponses = {
		{
			url = string.format( "/v2/spaces/%s/assets/%s/revisions/%s/master", spaceId, assetId, revisionId ),

			data = originalFilePath,

			fail = {
				[[
					{
						"errors": {
							"content_type": [
								"should match the subtype of the asset"
							]
						},
						"code": 1003,
						"description": "Invalid content type"
					}
				]],
				{
					status_code = '415',
					[ 'X-Result-Code' ] = '1003',
				}
			},

			returnHttpErrorsAsData = false,

		}
	}

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local assetOptions = { spaceId = spaceId }
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) )

	uploader:startContinuousUploads()

	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:pluck( '_localOnly', 'localOriginalError' )
		):expectDataWithinTime( 8, 'UploadFailedContentTypeMismatch' )

end

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

function WFCatBinaryUploaderTest.rxTestUploadProxyOnly_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	-- Create a fake proxies folder for documents we set directly, copy image to that directory.
	local originalFilePath, proxyFilePath, sha256 = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )
	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- Remove original from the doc as we're pretending this is a proxy only asset (like from LrClassic)
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )
	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'href') and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/2048', 'href') then
				return true
			end
		end ) )

	docObserver:expectDataWithinTime(360, true)
end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadProxyOnly( rxTest )

	-- Maybe could happen when a lightroom classic asset is copied though in those cases there really
	-- should be a remote proxy already. But there may be edge cases where the proxy on Oz is
	-- unavailable for some reason but we have it locally

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	-- Create a fake proxies folder for documents we set directly, copy image to that
	-- directory.
	local originalFilePath, proxyFilePath = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, { proxyFilePath = proxyFilePath } ) )

	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	-- Remove original from the doc as we're pretending this is a proxy only asset (like from LrClassic)
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )

	-- update the expected url/response pairs
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCreateProxyResponse( catalogId, assetId, revisionId, KSIdGenerator.uuidInLowercaseHex(), proxyFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with a proxy link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

if WFPlatformInfo.isDesktop then

-- Desktop-Only test as it's specific to codepaths that use WFPlatformInfo.isClassic()

function WFCatBinaryUploaderTest.rxTestMockedUploadMissingProxy_Classic( rxTest )

	-- WF-2611: This test was added to verify a bug fix. Prior to the fix, the upload
	-- code would loop when trying to upload a proxy whose local binary does not exist.
	-- This would effect would compound, resulting in a massive memory spike.

	rxTest:setTimeLimit( 10 )

	WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', 'true' )

	rxTest:finally( function()
		WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', nil )
	end )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	-- Create a fake proxies folder for documents we set directly, copy image to that
	-- directory.

	local originalFilePath, proxyFilePath = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, { proxyFilePath = proxyFilePath } ) )

	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	-- Remove original from the doc as we're pretending this is a proxy only asset

	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )

	-- Delete the proxy from disk, but leave the annotation.

	WFFileUtils.delete( proxyFilePath )

	-- Make sure there's an entry in the index store table for the asset/proxy.

	rxTest:expectSingleValue(
		catalog:indexStore():queryOnce( {
				sql = [[ SELECT COUNT( docId ) FROM needsBinaryUpload
							WHERE docId = ? AND binaryType = 'proxy' ]],
				singleColumnQuery = true,
				quiesceUntilUpToDate = true,
				assetId
			} )
			:map( function( rows )
				return rows[ 1 ]
			end ), 1 )

	-- Start the uploader.

	uploader:startContinuousUploads()

	-- Make sure the asset ends up with a local proxy error.

	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( "_localOnly", "localOriginalError" ) )
		:expectDataWithinTime( 5, "UploadFailedMissingProxy" )
		:dispose()

	-- Make sure no HTTP requests were issued.

	rxTest:observe( catalogSpec._httpMock:requestsProcessed() )
		:expectDataWithinTime( 2, 0 )
		:dispose()

	-- Make sure there's no longer an entry in the index store table for the asset/proxy.

	rxTest:expectSingleValue(
		catalog:indexStore():queryOnce( {
				sql = [[SELECT COUNT( docId ) FROM needsBinaryUpload
							WHERE docId = ? AND binaryType = 'proxy' ]],
				singleColumnQuery = true,
				quiesceUntilUpToDate = true,
				assetId
			} )
			:map( function( rows )
				return rows[ 1 ]
			end ), 0 )

end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadProxyOnly_invalidProxyLinkWithPendingXmpDevelop( rxTest )

	-- If Proxy link is invalid with pending xmp_develop or aux, we don't need to
	-- wait for develop binaries to upload the proxy

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	-- Create asset with invalid proxy link, xmp_develop and aux locally but not on Oz
	local optArguments = {
		noMasterLink = true,
		noLocalOriginal = true,
		ozProxiesAreInvalid = true,
		hasLocal2560Proxy = true,
		hasMasks = true,
		noMaskLinks = true,
		noDevelopSettingsLink = true,
		localXmpSidecarDoesNotExist = true
	}

	local docValue, expectedData = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, nil, optArguments )
	local assetId = docValue._id
	local revisionIds = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
	local revisionId = revisionIds[ #revisionIds ]

	-- Provide proxy create template, but no xmp_develop or aux create template
	-- So xmp_develop or aux won't be actually uploading. We want to check that xmp_develop or aux
	-- won't block invalid link proxy upload
	docValue = WFCatAssetTestUtils.addBasicOzMetadataUpdateForProxyOnly( docValue, catalogId, assetId, revisionId )

	rxTest:waitFor( catalog:docStore():setDocValue( docValue ) )

	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	-- update the expected url/response pairs
	local proxyFilePath = expectedData.proxyFilePath
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCreateProxyResponse( catalogId, assetId, revisionId, KSIdGenerator.uuidInLowercaseHex(), proxyFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with a proxy link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, assetId, revisionId ) )

--	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

if  WFPlatformInfo.isDesktop then

function WFCatBinaryUploaderTest.rxTestUploadProxyOnlyWithChangingSha256_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', 'true' )

	rxTest:finally( function()
		WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', nil )
	end )

	local function noRetryOnPutError( self, url, data, options, progressReporter )
		options.maxAttempts = 1
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { spyFn = noRetryOnPutError } )
	local docStore = catalog:docStore()

	-- Create a fake proxies folder for documents we set directly, copy image to that directory.
	local originalFilePath, proxyFilePath, sha256, uploadTestFolder = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )
	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath )
	local proxySha256 = derefOpt( assetDoc, '_localOnly', 'files', 'proxy', '2560', 'sha256' )
	KSTableUtils.setNested( assetDoc, proxySha256, 'importSource', 'proxySHA256' )

	rxTest:waitFor( docStore:setDocValue( assetDoc ) )

	-- Remove original from the doc as we're pretending this is a proxy only asset (like from LrClassic)
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )
	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'href')
					and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/2048', 'href')
					and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') == nil then
				return true
			end
		end ) )

	docObserver:expectDataWithinTime( 60, true )
	rxTest:sleep(.25)

	-- change the sha256

	local updatedOriginalFilePath = KSPathUtils.child( testDataFolder1, 'IMG_7763.JPG' )
	local updatedSha256 = WFCrypto.digestFile( updatedOriginalFilePath, 'SHA256' )
	local updatedFileSize = WFFileUtils.fileAttributes( updatedOriginalFilePath ).fileSize

	local updatedTestProxyFilePath = KSPathUtils.child( testAssetsFolder, "IMG_7763.DNG" )
	local updatedProxyFilePath = KSPathUtils.child( uploadTestFolder, "IMG_7763.DNG" )
	local updatedProxySha256 = WFCrypto.digestFile( updatedTestProxyFilePath, 'SHA256' )
	WFFileUtils.copy( updatedTestProxyFilePath, updatedProxyFilePath )

	local updatedProxyPathOverlay = {
		_id = assetId,
		importSource = {
			fileSize = updatedFileSize,
			sha256 = updatedSha256,
			proxySHA256 = updatedProxySha256,
		}
	}

	updatedProxyPathOverlay = WFCatAssetTestUtils.addProxyPathToDocument( updatedProxyPathOverlay, updatedProxyFilePath )

	rxTest:waitFor( docStore:applyOverlay( updatedProxyPathOverlay ) )

	rxTest:observe( docStore:ongoingDocValue( assetId )
		:map( function( docValue )
			return not not ( ( derefOpt( docValue, 'importSource', 'sha256' ) == updatedSha256 )
				and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') == nil
				and derefOpt( docValue,  '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560' )
				and	( derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'invalid' ) == true ) )
		end ) )
		:expectDataWithinTime( 30, true )
		:dispose()

	rxTest:observe( docStore:ongoingDocValue( assetId )
		:map( function( docValue )
			return not not ( ( derefOpt( docValue, 'importSource', 'sha256' ) == updatedSha256 )
				and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') == nil
				and derefOpt( docValue,  '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560' )
				and	( derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'invalid' ) ~= true ) )
		end ) )
		:expectDataWithinTime( 300, true )
		:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestUploadProxyOnlyWithChangingSha256_useOriginalSha256_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', 'true' )

	rxTest:finally( function()
		WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', nil )
	end )

	local function noRetryOnPutError( self, url, data, options, progressReporter )
		options.maxAttempts = 1
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { spyFn = noRetryOnPutError } )
	local docStore = catalog:docStore()

	-- Create a fake proxies folder for documents we set directly, copy image to that directory.
	local originalFilePath, proxyFilePath, sha256, uploadTestFolder = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )
	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath )
	local proxySha256 = derefOpt( assetDoc, '_localOnly', 'files', 'proxy', '2560', 'sha256' )
	KSTableUtils.setNested( assetDoc, proxySha256, 'importSource', 'proxySHA256' )

	rxTest:waitFor( docStore:setDocValue( assetDoc ) )

	-- Remove original from the doc as we're pretending this is a proxy only asset (like from LrClassic)
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )
	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'href')
					and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/2048', 'href')
					and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') == nil then
				return true
			end
		end ) )

	docObserver:expectDataWithinTime( 60, true )
	rxTest:sleep(.25)

	-- change the sha256

	local updatedOriginalFilePath = KSPathUtils.child( testDataFolder1, 'IMG_7763.JPG' )
	local updatedSha256 = WFCrypto.digestFile( updatedOriginalFilePath, 'SHA256' )
	local updatedFileSize = WFFileUtils.fileAttributes( updatedOriginalFilePath ).fileSize

	local updatedTestProxyFilePath = KSPathUtils.child( testAssetsFolder, "IMG_7763.DNG" )
	local updatedProxyFilePath = KSPathUtils.child( uploadTestFolder, "IMG_7763.DNG" )
	WFFileUtils.copy( updatedTestProxyFilePath, updatedProxyFilePath )

	local updatedProxyPathOverlay = {
		_id = assetId,
		importSource = {
			fileSize = updatedFileSize,
			sha256 = updatedSha256,
			proxySHA256 = { _delete = true },
		}
	}

	-- If importSource.proxySHA256 doesn't exist,
	-- fallback to use importSource.sha256 comparing _localOnly.files.original.master.sha256

	updatedProxyPathOverlay = WFCatAssetTestUtils.addProxyPathToDocument( updatedProxyPathOverlay, updatedProxyFilePath )
	KSTableUtils.setNested( updatedProxyPathOverlay, updatedSha256, '_localOnly', 'files', 'original', 'master', 'sha256' )

	rxTest:waitFor( docStore:applyOverlay( updatedProxyPathOverlay ) )

	rxTest:observe( docStore:ongoingDocValue( assetId )
		:map( function( docValue )
			return not not ( ( derefOpt( docValue, 'importSource', 'sha256' ) == updatedSha256 )
				and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') == nil
				and derefOpt( docValue,  '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560' )
				and	( derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'invalid' ) == true ) )
		end ) )
		:expectDataWithinTime( 30, true )
		:dispose()

	rxTest:observe( docStore:ongoingDocValue( assetId )
		:map( function( docValue )
			return not not ( ( derefOpt( docValue, 'importSource', 'sha256' ) == updatedSha256 )
				and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') == nil
				and derefOpt( docValue,  '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560' )
				and	( derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'invalid' ) ~= true ) )
		end ) )
		:expectDataWithinTime( 300, true )
		:dispose()

end

end

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

function WFCatBinaryUploaderTest.rxTestMockUpload_xmpDevelop_fileIsMissing( rxTest )

	rxTest:setTimeLimit( 3 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId )
	local xmpDevelopSha256 = WFCatAssetUtils.getLocalExternalDevelopSettingsContentId( assetDoc )

	-- Delete file from file system
	WFFileUtils.deleteFile( localOnly.xmpDevelopSettingsFilePath )

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- Construct a mock candidate which is fed to the uploader in order to manufacture a file missing situation
	local mockCandidate = {
		assetId,		-- docId 1
		"PutFilePathHere",	-- localFilePath 2
		"putURLhere",		-- binaryURL 3
		"xmp_develop",		-- binarytype 4
		"putContentTypeHere",	-- contentType 5
		nil,			-- not needed 6
		0,			-- isDenied 7
		xmpDevelopSha256,	-- sha256 8
		nil,			-- 9
		nil,			-- 10
		nil,			-- sourceLinkForCopy 11
		nil,			-- localFileVolumeId 12
		nil,			-- localFileRelativePath 13
		0,			-- uploadPriority 14
	}

	local o = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return derefOpt( docValue, '_localOnly', 'files', 'xmp_develop' ) == nil
		end ) )

	o:expectDataWithinTime( 1, false )

	-- Pull the mock candidate which triggers a file missing detection, and resets the localonly xmp_develop section
	uploader:getHighestPriorityBinaryToUpload( { mockCandidate } )

	-- Check that the document has had its _localOnly.files section emptied
	o:expectDataWithinTime( 1, true )

end

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

function WFCatBinaryUploaderTest.rxTestMockUpload_xmpDevelop_fileIsMissingForOldDevelop( rxTest )

	rxTest:setTimeLimit( 3 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId )

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local fakeOldSha = "aPerfectlyValidSha256"

	-- Construct a mock candidate which is fed to the uploader
	local mockCandidate = {
		assetId,		-- docId 1
		"PutFilePathHere",	-- localFilePath 2
		"putURLhere",		-- binaryURL 3
		"xmp_develop",		-- binarytype 4
		"putContentTypeHere",	-- contentType 5
		nil,			-- not needed 6
		0,			-- isDenied 7
		fakeOldSha,		-- sha256 8
		nil,			-- 9
		nil,			-- 10
		nil,			-- sourceLinkForCopy 11
		nil,			-- localFileVolumeId 12
		nil,			-- localFileRelativePath 13
		0,			-- uploadPriority 14
	}

	local o = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return derefOpt( docValue, '_localOnly', 'files', 'xmp_develop' )
		end ) )

	-- Pull the mock candidate which triggers a collision detection, and does NOT reset the localonly xmp_develop section
	uploader:getHighestPriorityBinaryToUpload( { mockCandidate } )

	rxTest:sleep(0.5)

	-- Check that the document has not be altered
	o:expectDataWithinTime( 1, derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockUpload_aux_fileIsMissing( rxTest )

	rxTest:setTimeLimit( 3 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	local optOverrides = {
		hasDevelopAux = true,
		noDevelopAuxLinks = true
	}

	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, optOverrides )

	local auxId = localOnly.mask1AuxId
	local auxDigest = localOnly.mask1Fingerprint

	local relativeAuxPath = WFCatAssetAux.getAuxLocalOnlyFilePath( assetDoc, auxDigest )
	local auxPath = relativeAuxPath and WFCatAssetAux.getAbsolutePath( catalog, relativeAuxPath )

	rxTest:expectEqual( 'file', WFFileUtils.exists( auxPath ) )

	-- Delete aux file from the file system

	WFFileUtils.deleteFile( auxPath )

	rxTest:expectEqual( false, WFFileUtils.exists( auxPath ) )

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- Construct a mock candidate which is fed to the uploader

	local mockCandidate = {
		assetId,				-- docId 1
		auxPath,				-- localFilePath 2
		"putURLhere",			-- binaryURL 3
		"aux",					-- binarytype 4
		"putContentTypeHere",	-- contentType 5
		nil,					-- not needed 6
		0,						-- isDenied 7
		auxDigest,				-- sha256 8
		nil,					-- 9
		nil,					-- 10
		nil,					-- sourceLinkForCopy 11
		nil,					-- localFileVolumeId 12
		nil,					-- localFileRelativePath 13
		0,						-- uploadPriority 14
		nil,					--
		nil,					--
		nil,					--
		auxId					-- auxId 18
	}

	local o = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )

		:map( function( docValue )
			return WFCatAssetAux.getAuxLocalOnlyFilePath( docValue, auxDigest ) == nil
		end ) )

	o:expectDataWithinTime( 1, false )

	-- Pull the mock candidate which triggers a file missing detection, and removes the localonly aux file path

	uploader:getHighestPriorityBinaryToUpload( { mockCandidate } )

	-- Check that the document has had its localonly aux file path removed.

	o:expectDataWithinTime( 1, true )

end

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

if WFPlatformInfo.isNimbus() then

-- The improperly Applied _localOnly.files.original.master file was applied
-- by the PixelProvider so this won't happen on other clients.

function WFCatBinaryUploaderTest.rxTestFixImproperlyAppliedMasterFile( rxTest )

	-- If Proxy based photo from LrClassic was shared with this client by a
	-- different user has a localOnly.files.original attached remove the
	-- overlay.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local copiedRevisionId1 = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local spaceId = 'spaceId'

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId1, copiedRevisionId1, assetId, revisionId, '403' ),
	}

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, copiedAssetId1, copiedRevisionId1, sha256, originalFilePath ) )
	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = copiedAssetId1,
		_spaceId = spaceId,
		importSource = {
			importedBy = 'SomeoneElse',
			willNotUploadMaster = true,
		}
	} ) )

	local observeDocLocalOnlyFilesOriginaType = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId1 )
		:pluck( '_localOnly', 'files', 'original', 'master' )
		:map( type ) )

	observeDocLocalOnlyFilesOriginaType:expectDataWithinTime( 1, 'table' )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	observeDocLocalOnlyFilesOriginaType:expectDataWithinTime( 1, 'nil' )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	errorsRecordedInDiagnosticLog = remove403_1016_PermissionError( errorsRecordedInDiagnosticLog )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

end

--==============================================================================
-- @mark Copy
--==============================================================================

function WFCatBinaryUploaderTest.rxTestCanUploadOriginalViaCopy( rxTest )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local optArgs = {}

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local docValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optArgs )

	-- A non-copied Catalog Asset

	rxTest:expectEqual( false, WFCatBinaryUploader.canUploadOriginalViaCopy( docValue ) )

	-- Space Asset w/ sourceOriginalLink

	optArgs = {
		isSpaceAsset = true,
		hasOriginalSourceLinkForCopy = true
	}

	docValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optArgs )

	rxTest:expectEqual( true, WFCatBinaryUploader.canUploadOriginalViaCopy( docValue ) )

	-- Space Asset w/ sourceOriginalLink

	optArgs = {
		isCopy = true
	}

	docValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optArgs )

	rxTest:expectEqual( true, WFCatBinaryUploader.canUploadOriginalViaCopy( docValue ) )

end

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

local function testUploadCopy( rxTest, fallbackToOzUploadForOriginalsOnly )

	rxTest:setTimeLimit( 360 )

	local sawCopyRequest = false
	local copyRequestUrl

	local function watchForCopyRequest( self, url, data, options, progressReporter )
		if options.requestHeaders then
			for _, header in ipairs( options.requestHeaders ) do
				if header.header == 'X-Async-Copy' then
					sawCopyRequest = true
					copyRequestUrl = url
				end
			end
		end
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local uploaderOpts = { fallbackToOzUploadForOriginalsOnly = fallbackToOzUploadForOriginalsOnly }
	local setupOptions = { uploaderOpts = uploaderOpts, spyFn = watchForCopyRequest }
	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, setupOptions )

	local assetId, _, originalFilePath, sha256, assetDoc = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

	-- Import a duplicate of the asset
	local copiedAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local copiedRevisionId1 = KSIdGenerator.uuidInLowercaseHex()
	local assetDoc2 = WFCatAssetTestUtils.createBaseAssetDocument( catalogId, copiedAssetId1, copiedRevisionId1, sha256, originalFilePath )
	assetDoc2 = WFCatAssetTestUtils.addOriginalPathToDocument(assetDoc2, sha256, originalFilePath)
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc2 ) )

	local masterCreateUrl

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId1 )
	:map( function( docValue )
		if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') then
			masterCreateUrl = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master_create', 'href')
			masterCreateUrl = masterCreateUrl:replace( '{revision_id}', copiedRevisionId1 )
			return true
		end
	end ) )

	docObserver:expectDataWithinTime(360, true)

	-- Copy master api will use "X-Async-Copy" in the request HEAD
	rxTest:expectEqual( true, sawCopyRequest )
	rxTest:expectEqual( copyRequestUrl, masterCreateUrl )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopy_testNightly( rxTest )

	local fallbackToOzUploadForOriginalsOnly = true

	testUploadCopy( rxTest, fallbackToOzUploadForOriginalsOnly )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopy_enableDirectUpload_testNightly( rxTest )

	testUploadCopy( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy( rxTest )

	-- This test verifies the uploader will detect an asset with the same
	-- sha256 and use the Copy Master api instead of the Create Master api
	-- to uplaod the image.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local copiedRevisionId1 = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId1, copiedRevisionId1, assetId, revisionId ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, copiedAssetId1, copiedRevisionId1, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId1, copiedRevisionId1 ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_VideoOne_NoProxiesHeader( rxTest )

	-- WF-1786 This test verifies Copy API for video 1.0  would not have x-generate-proxies:all header

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local copiedRevisionId1 = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_7772.mp4" )

	-- Just check mock http headers do not contain x-generate-proxies:all header
	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCopyMasterResponseForVideo( catalogId, copiedAssetId1, copiedRevisionId1, assetId, revisionId ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, copiedAssetId1, copiedRevisionId1, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId1, copiedRevisionId1 ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

if WFPlatformInfo.isWindows then

-- this workflow only applies to windows

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_Video_SpecialProxiesAndRenditions( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local copiedRevisionId1 = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_7772.mp4" )

	-- Just check mock http headers do not contain x-generate-proxies:all header
	local specialProxiesRenditions = true
	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCopyMasterResponseForVideo( catalogId, copiedAssetId1, copiedRevisionId1, assetId, revisionId, specialProxiesRenditions ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local specialProxiesAndRenditionsOptions = {
		overlay = {
			video = {
				transferFunction = 'bt2100_hlg',
			}
		}
	}

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, specialProxiesAndRenditionsOptions ) )
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, copiedAssetId1, copiedRevisionId1, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId1, copiedRevisionId1 ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

end -- WFPlatformInfo.isWindows

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

local function testUploadCopyAfterAdd( rxTest, fallbackToOzUploadForOriginalsOnly )

	rxTest:setTimeLimit( 360 )

	local sawCopyRequest = false

	local function watchForCopyRequest( self, url, data, options, progressReporter )
		if options.requestHeaders then
			for _, header in ipairs( options.requestHeaders ) do
				if header.header == 'X-Async-Copy' then
					sawCopyRequest = true
				end
			end
		end
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local uploaderOpts = { fallbackToOzUploadForOriginalsOnly = fallbackToOzUploadForOriginalsOnly }
	local setupOptions = { uploaderOpts = uploaderOpts, spyFn = watchForCopyRequest }
	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, setupOptions )

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local copiedAssetId1, _, _, _, assetDoc2 = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc2 ) )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
	:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId1 )
	:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

	-- Copy master api will use "X-Async-Copy" in the request HEAD
	rxTest:expectEqual( true, sawCopyRequest )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopyAfterAdd_testNightly( rxTest )

	local fallbackToOzUploadForOriginalsOnly = true

	testUploadCopyAfterAdd( rxTest, fallbackToOzUploadForOriginalsOnly )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopyAfterAdd_enableDirectUpload_testNightly( rxTest )

	testUploadCopyAfterAdd( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopyAfterAdd( rxTest )

	-- This test demonstrates the uploader will detect an asset with the same
	-- sha256 and use the Copy Master api instead of the Create Master api
	-- to upload the image.

	rxTest:setTimeLimit( 30 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	local assetDocOpts = { localXmpSidecarDoesNotExist = true, noXmpSidecarLink = true, noMasterLink = true }
	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId,  assetDocOpts )

	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	local copiedAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local copiedRevisionId1 = KSIdGenerator.uuidInLowercaseHex()

	-- expect the upload of the master then the copy

	local mocks = { }

	local sha256 = localOnly.originalSha256

	WFCatBinaryUploaderTestUtils.getOriginalDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks, revisionId )

	table.insert( mocks, WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId1, copiedRevisionId1, assetId, revisionId ) )

	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	-- add document to the doc store and start uploading

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first three calls to perform master direct upload
	httpRequestsProcessed:expectDataWithinTime( 2, 3 )

	-- delay creating copy document in the doc store until after first PUT occurs to avoid intermittent failure
	-- where first PUT of master being for this copy asset instead of the original
	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, copiedAssetId1, copiedRevisionId1, sha256, originalFilePath ) )

	-- Update the first document with a master link to let the other assets see it and copy it.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )
	uploading:expectDataWithinTime( 5, true )

	-- wait for the copy master PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 4 )

	-- Make it look like metadatapuller got an update with a master link to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId1, copiedRevisionId1 ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 10, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

local function testMockedUploadCopyWithRemoteMaster( rxTest, noLocalMaster, noLocalProxy )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempId'
	local copiedRevisionId = 'tempRevisionId'

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	if noLocalMaster then
		-- Handle case where master wasn't downloaded or was purged.
		rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )
	end

	if noLocalProxy then
		-- Handle case where proxy wasn't downloaded or was purged.
		rxTest:waitFor( removeProxyPathFromDoc( catalog, assetId ) )
	end

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	watchForXmpAndMasterUploads( rxTest, catalog, catalogSpec, uploader, catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_localMasterRemoteMaster( rxTest )

	-- Typical case where you copied an asset after importing it.

	return testMockedUploadCopyWithRemoteMaster( rxTest, false )

end

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

local function testUploadCopyWithRemoteMaster( rxTest, noLocalMaster, noLocalProxy )

	rxTest:setTimeLimit( 360 )

	local sawCopyRequest = false
	local copyRequestUrl

	local function watchForCopyRequest( self, url, data, options, progressReporter )
		if options.requestHeaders then
			for _, header in ipairs( options.requestHeaders ) do
				if header.header == 'X-Async-Copy' then
					sawCopyRequest = true
					copyRequestUrl = url
				end
			end
		end
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { spyFn = watchForCopyRequest } )

	local originalFilePath, proxyFilePath, sha256 = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )
	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local sourceOriginalLink
	local isValidOriginalAvailableOnOz
	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') then
			-- if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'href') and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/2048', 'href') then

				-- WF-1813 need to check presence of the master link & no "invalid=true" in master link & no sha256 error in importsource
				isValidOriginalAvailableOnOz = WFOzDocUtils.isValidOriginalAvailableOnOz( docValue )
				sourceOriginalLink = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href')

				return isValidOriginalAvailableOnOz

			end
		end ) )

	docObserver:expectDataWithinTime(360, true)

	if noLocalMaster then
		-- Handle case where master wasn't downloaded or was purged.
		rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )
	end

	if noLocalProxy then
		-- Handle case where proxy wasn't downloaded or was purged.
		rxTest:waitFor( removeProxyPathFromDoc( catalog, assetId ) )
	end

	local copiedAssetId1, copiedRevisionId1, _, _, assetDoc2 = setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath  )

	KSTableUtils.setNested( assetDoc2, assetId, 'copy', 'copy_id' )
	KSTableUtils.setNested( assetDoc2, assetId, '_localOnly', 'sourceAssetId' )
	KSTableUtils.setNested( assetDoc2, sourceOriginalLink, '_localOnly', 'sourceOriginalLink' )

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc2 ) )

	if noLocalMaster then
		-- Handle case where master doesn't exist locally, use Oz copy API to copy master binary
		rxTest:waitFor( removeOriginalPathFromDoc( catalog, copiedAssetId1 ) )
	end

	if noLocalProxy then
		-- Handle case where proxy wasn't downloaded or was purged.
		rxTest:waitFor( removeProxyPathFromDoc( catalog, copiedAssetId1 ) )
	end

	local masterCreateUrl
	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId1 )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') then
				masterCreateUrl = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master_create', 'href')
				masterCreateUrl = masterCreateUrl:replace( '{revision_id}', copiedRevisionId1 )
				return true
			end
		end ) )

	docObserver:expectDataWithinTime(360, true)

	-- Copy master api will use "X-Async-Copy" in the request HEAD
	rxTest:expectEqual( true, sawCopyRequest )
	rxTest:expectEqual( copyRequestUrl, masterCreateUrl )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopy_localProxyRemoteMaster_testNightly( rxTest )

	local noLocalMaster = true
	local noLocalProxy = false

	return testUploadCopyWithRemoteMaster( rxTest, noLocalMaster, noLocalProxy )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopy_noLocalProxyRemoteMaster_testNightly( rxTest )

	local noLocalMaster = true
	local noLocalProxy = true

	return testUploadCopyWithRemoteMaster( rxTest, noLocalMaster, noLocalProxy )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_localProxyRemoteMaster( rxTest )

	-- Typical case that happens if you synced down your collection on another
	-- machine and didn't pull the master down yet (or if master was purged).

	return testMockedUploadCopyWithRemoteMaster( rxTest, true )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_noLocalProxyRemoteMaster( rxTest )

	-- Typical case that happens if you synced down your collection on another
	-- machine and didn't pull the master down yet (or if master was purged).

	return testMockedUploadCopyWithRemoteMaster( rxTest, true, true )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_noLocalMasterRemoteBad( rxTest )

	-- Copy an asset that has an invalid sha that was never checked and without
	-- having a master locally on the disk so we can't recalculate the sha256.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempId'
	local copiedRevisionId = 'tempRevisionId'

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )
	assert( catalogSpec )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	-- In this case the master wasn't downloaded or was purged.
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	-- Also add the sha error because the asset that was copied resulted in
	-- the sha hash as being different than was was original asset (who's
	-- sha hash was never checked when it was originally uploaded).
	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = copiedAssetId,
		importSource = {
			status =  {
				error = {
					all = {
						reason = {
							sha256 = "does not match"
						}
					}
				}
			}
		}
	} ) )

	local uploading = rxTest:observe( uploader:isDoingWork()
		:replay(100)
		:refCount()
	)

	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
	uploader:startContinuousUploads()

	-- Make sure the uploader tried to do something (it'll at least upload/copy the xmp)
	uploading:expectDataWithinTime( 5, true )

	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure the uploader completed the attempted upload (actually marked the asset as invalid).
	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure the asset is no longer in the needsBinaryUpload table.
	rxTest:expectSingleValue( catalog:indexStore():queryOnce( {
		sql = "SELECT docId FROM needsBinaryUpload",
		singleColumnQuery = true,
	} ), { } )

	-- Make sure we didn't run into any unexpected errors.
	local markInvalidErrorCount
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	errorsRecordedInDiagnosticLog, markInvalidErrorCount = removeMarkingOriginalInvalidError( errorsRecordedInDiagnosticLog )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

	-- Make sure we did mark the asset as invalid
	rxTest:expectEqual( 1, markInvalidErrorCount )

end

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

if not WFPlatformInfo.isAndroid then

-- Android explicitly wants to see more items in it's binary uploader as it
-- does further processing elsewhere.  See canAssetBeUploaded comment
-- "Additional check for Android."

function WFCatBinaryUploaderTest.rxTestMocked_syncCopyRemoteBad( rxTest )

	-- Scenario is syncing down an asset marked as invalid and has copy.is_copy set
	-- No local files will be present.

	-- Make sure we don't attempt to upload via copy the asset that wasn't copied
	-- locally but has a copy_id and an invalid sha that doesn't have a master
	-- locally on disk.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempId'
	local copiedRevisionId = 'tempRevisionId'

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )
	assert( catalogSpec )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	-- In this case the master wasn't downloaded or was purged.
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	-- Also add the sha error because the asset that was copied resulted in
	-- the sha hash as being different than was was original asset (who's
	-- sha hash was never checked when it was originally uploaded).
	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = copiedAssetId,
		importSource = {
			status =  {
				error = {
					all = {
						reason = {
							sha256 = "does not match"
						}
					}
				}
			}
		}
	} ) )

	-- Since this scenario is just syncing down this copy remove all of the _localOnly data
	rxTest:waitFor( catalog:docStore():applyOverlay {
			_id = copiedAssetId,
			_localOnly = { _delete = true },
		}
	)

	local uploading = rxTest:observe( uploader:isDoingWork()
		:replay(100)
		:refCount()
	)

	-- Make sure the binary uploader doesn't think it needs to upload the synced down asset
	rxTest:observe( catalog:indexStore():queryOnce( {
		sql = "SELECT docId FROM needsBinaryUpload",
		singleColumnQuery = true,
	} ), { } )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 5, false )

	-- Make sure the uploader completed the attempted upload (actually marked the asset as invalid).
	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	rxTest:expectSingleValue( catalog:indexStore():queryOnce( {
		sql = "SELECT docId FROM needsBinaryUpload",
		singleColumnQuery = true,
	} ), { } )

	-- Make sure we didn't run into any unexpected errors.
	local markInvalidErrorCount
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	errorsRecordedInDiagnosticLog, markInvalidErrorCount = removeMarkingOriginalInvalidError( errorsRecordedInDiagnosticLog )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

	-- Make sure WFCatBinaryUploader didn't mark the asset as invalid
	rxTest:expectEqual( 0, markInvalidErrorCount )

end

end -- not WFPlatformInfo.isAndroid

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_localMasterRemoteProxy( rxTest )

	-- Shouldn't happen from a copy operation, but can happen after importing an
	-- asset that was simultaneously synced from Lightroom on another client and
	-- the sha256 matches up with that one prior to upload.

	-- This test could named rxTestMockedUploadAddAssetSimulatenouslySyncedByLightroom,
	-- and be rewritten to not use createCopiesOfAssets, but this was easier.

	-- Expected result is to upload the master and not do a copy.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempAssetId'
	local copiedRevisionId = 'tempRevisionId'
	local copiedOriginalPath = 'tempOriginalPath'

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( originalFilePath ).fileSize

	local contentType = 'image/jpeg'

	local _, mainPartsRequestUrl = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{asset_id}/revisions/{revision_id}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetUploadMock( assetUploadUrl, originalFilePath, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, assetId, revisionId ) )
	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )
	rxTest:waitFor( removeOriginalPathFromDoc( catalog, assetId ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId, copiedOriginalPath = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	rxTest:expectEqual( nil, copiedOriginalPath )

	-- This can't happen when using createCopiesOfAssets, but add a local original path now to simulate
	-- the case described at the top of this test.
	rxTest:waitFor( addOriginalToDocPath( catalog, copiedAssetId, sha256, originalFilePath ) )
	copiedOriginalPath = originalFilePath

	-- update the expected url/response pairs

	mainPartsRequestUrl = mainPartsRequestUrl:replace( '{asset_id}', copiedAssetId )
	mainPartsRequestUrl = mainPartsRequestUrl:replace( '{revision_id}', copiedRevisionId )

	urlResponses[ 1 ] = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	urlResponses[ 2 ] = WFCatBinaryUploaderTestUtils.generateAssetUploadMock( assetUploadUrl, originalFilePath, fileSize, contentType )
	urlResponses[ 3 ] = WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_localProxyRemoteProxy( rxTest )

	-- Happens when a lightroom asset is copied.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local proxyId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempAssetId'
	local copiedRevisionId = 'tempRevisionId'
	local copiedOriginalPath = 'tempOriginalPath'
	local copiedProxyId = KSIdGenerator.uuidInLowercaseHex()

	local proxyFilePath = getPathsFor( rxTest, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	-- Handle case where proxy has been downloaded.

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, nil, { proxyFilePath = proxyFilePath } ) )
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )
	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, assetId, revisionId, proxyId ) )
	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, assetId ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId, copiedOriginalPath = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	rxTest:expectEqual( nil, copiedOriginalPath )

	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyProxyResponse( catalogId, copiedAssetId, copiedRevisionId, copiedProxyId, assetId, revisionId, proxyId ) )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with the xmp link in it to let the
	-- uploader know it can continue with the proxy
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 2 )

	-- Make it look like metadatapuller got an update with the proxy link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_noLocalProxyRemoteProxy( rxTest )

	-- Happens when a lightroom asset is copied.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local sourceProxyId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempAssetId'
	local copiedRevisionId = 'tempRevisionId'
	local copiedOriginalPath = 'tempOriginalPath'
	local copiedProxyId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	-- Handle case where proxy wasn't downloaded or was purged. (Mock by never attaching proxy file to document in the first place)

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, sourceAssetId, sourceRevisionId, sha256 ) )
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, sourceAssetId, sourceRevisionId ) )
	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId ) )
	rxTest:waitFor( addWillNotUploadMasterToDoc( catalog, sourceAssetId ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { sourceAssetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId, copiedOriginalPath = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	rxTest:expectEqual( nil, copiedOriginalPath )

	-- update the expected url/response pairs
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, copiedAssetId, copiedRevisionId, sourceAssetId, sourceRevisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyProxyResponse( catalogId, copiedAssetId, copiedRevisionId, copiedProxyId, sourceAssetId, sourceRevisionId, sourceProxyId ) )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like the metadata puller got an update with the xmp link in it to let the
	-- uploader know that it can upload or copy the proxy
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 2 )

	-- Make it look like metadatapuller got an update with the proxy link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

local function rxTestMockedUploadCopy_xmpSidecarRemote( rxTest, xmpSidecarExists )

	-- copy master also copies rels/xmp .

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempId'
	local copiedRevisionId = 'tempRevisionId'

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	if xmpSidecarExists == nil then
		rxTest:waitFor( catalog:docStore():applyOverlay {
				_id = assetId,
				_localOnly = {
					xmpSidecarExists = { _delete = true },
				},
			}
		)
	else
		rxTest:waitFor( catalog:docStore():applyOverlay {
				_id = assetId,
				_localOnly = {
					xmpSidecarExists = xmpSidecarExists,
				},
			}
		)
	end

	if xmpSidecarExists == false then
		-- it it's false we're trying to explicitly test the case where we know on the copy that there is no xmp sidecar file so the
		-- the xmp that *will* exist on Oz for the source asset must have been extracted by Oz. So make the copies, add the master
		-- and xmp link to doc, then expect only a copyMaster upload

		rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
			:map( function( copiedAssetIds )
				copiedAssetId = copiedAssetIds[ 1 ]
				return #copiedAssetIds
			end ),
		1 )

		copiedRevisionId = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

		rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )


		-- Add the expected url/response pairs with copiedAssetId and copiedRevisionId with master copy + download and upload of xmp data
		catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )

	else
		-- if it's nil or true we want to add the master and xmp links and we'll assume that we have to upload/copy the xmp.
		rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

		rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
			:map( function( copiedAssetIds )
				copiedAssetId = copiedAssetIds[ 1 ]
				return #copiedAssetIds
			end ),
		1 )

		copiedRevisionId = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

		-- Add the expected url/response pairs with copiedAssetId and copiedRevisionId with copy of xmp data and the subsequent copy of the master (with no X-Read-Meta header)
		local errorCode = nil
		local readMetadataNone = true
		catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
		catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId,
				errorCode, readMetadataNone ) )

	end


	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur (the xmp sidecar)
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	if not ( xmpSidecarExists == false ) then  -- have to account for true or nil cases

		-- Make it look like metadatapuller got an update with the xmp link in it to let the
		-- uploader know the xmp has been succesfully copied and the master can be uploaded (with no X-Read-Meta)
		rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

		-- expect the second PUT to occur (the master)
		httpRequestsProcessed:expectDataWithinTime( 2, 2 )

	end

	-- Make it look like metadatapuller got an update with the master link in it to let the uploader know the
	-- master has been successfully copied. In the xmp sidecar case we end up seting the xmp link again here,
	-- but that's a no-op and is no big deal
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )


	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpSidecarRemote_exists_true( rxTest )
	return rxTestMockedUploadCopy_xmpSidecarRemote( rxTest, true )
end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpSidecarRemote_exists_false( rxTest )
	return rxTestMockedUploadCopy_xmpSidecarRemote( rxTest, false )
end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpSidecarRemote_exists_nil( rxTest )
	return rxTestMockedUploadCopy_xmpSidecarRemote( rxTest, nil )
end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelopSettingsLocal( rxTest )

	-- Uploads rels/xmp/develop instead of copying it.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local xmpDevelopSha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempId'
	local copiedRevisionId = 'tempRevisionId'

	local originalFilePath, _, _, originalsTestFolder = getPathsFor( rxTest, "small.JPG" )

	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'small.XMP' )
	local xmpDevelopFileSize = generateSidecarFile( rxTest, xmpDevelopFilePath )
	local copiedXmpDevelopFilePath

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	local assetOptions = {
		xmpDevelop = { sha256 = xmpDevelopSha256, filePath = xmpDevelopFilePath, fileSize = xmpDevelopFileSize },
	}
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) )

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	local _

	copiedRevisionId, _, _, _, copiedXmpDevelopFilePath = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	if WFPlatformInfo.isAndroid then
		copiedXmpDevelopFilePath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'carouselDocuments' ), copiedXmpDevelopFilePath )
	end

	-- Update the expected url/response pairs with copiedAssetId and copiedRevisionId.
	local errorCode = nil
	local readMetadataNone = true
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpDevelopResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId,
			errorCode, readMetadataNone ) )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	-- wait for the PUTs to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- make it look like the puller got an update with the xmp link and can now upload the develop settings
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 2 )

	-- make it look like the puller got the xmp develop link
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId, xmpDevelopSha256 ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 3 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know the master has been succesfully uploaded.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )


	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelopSettingsRemote( rxTest )

	-- Copies rels/xmp/develop as there is no local file at time of upload.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local xmpDevelopSha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local copiedAssetId = 'tempId'
	local copiedRevisionId = 'tempRevisionId'

	local originalFilePath = getPathsFor( rxTest, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( catalog:docStore():applyOverlay {
			_id = assetId,
			develop = {
				xmpCameraRaw = {
					sha256 = xmpDevelopSha256,
				},
			},
			_localOnly = {
				xmpSidecarExists = false,
			},
		}
	)

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	copiedRevisionId = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )

	-- Update the expected url/response pairs with copiedAssetId and copiedRevisionId.
	local errorCode = nil
	local readMetadataNone = true
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyXmpDevelopResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId ) )
	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateCopyMasterResponse( catalogId, copiedAssetId, copiedRevisionId, assetId, revisionId,
			errorCode, readMetadataNone ) )

	-- Update copied document with _serverProvidedMetadata create links as if the metadata was downloaded.
	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- make it look like the puller received the xmp link and the uploader can proceed to the
	-- develop settings
	rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 2 )

	-- make it look like the puller received the xmp develop link
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId, xmpDevelopSha256 ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 3 )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know the master is up and we should be done
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )

	uploading:expectDataWithinTime( 5, false )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

--[[
	\brief Run mock copy asset test in which xmpDevelop is uploaded rather than copied
		from source asset.

	\param rxTest (WFRxTestCaseRunner)
	\param options (table) - Optional modifiers:

		changeSource (string) - Apply some change to the source asset before copying

			if "noLink" then remove /rels/xmp/develop link entirely

			if "invalid" then /rels/xmp/develop link as invalid

			if "diffSha56" then mark /rels/xmp/develop link with sha256 different than
				the one in develop payload

			if "noSha256" then remove sha256 from /rels/xmp/develop link

		changeCopy (string) - Apply change to the copied asset. Same options are
			available as changeSource option, other than "noLink". By default the
			copied asset has no /rels/xmp/develop link unless one of the other options
			are applied

		atomicDevelopSettings (boolean) - enable atomicDevelopSettings capability

		updateCopiedAsset (boolean) - apply an unsynced change to copied asset

]]
local function testMockedCopyXmpDevelopUpload( rxTest, options )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, options )

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceAssetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, sourceAssetId )
	rxTest:waitFor( catalog:docStore():setDocValue( sourceAssetDoc ) )

	local overlay = { _id = sourceAssetId }

	-- some options passed in change the source asset /rels/xmp/develop link such that after the
	-- copy operation has completed the binary needs to be uploaded rather than copied on Oz.
	-- The reasons could be binary was never uploaded to Oz (noLink), binary uploaded to Oz is
	-- invalid (invalid), binary uploaded to Oz has a different sha256 than the local develop
	-- settings (diffSha256) or could be that link was synced from Oz along time ago, predating
	-- when sha256 was included with the link (noSha256). Could also be thta the source is not
	-- modified and the modifications are made to the copy only.

	if options.changeSource == 'noLink' then
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { _delete = true } } }
	elseif options.changeSource == 'invalid' then
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { invalid = true, sha256 = { _delete = true } } } }
	elseif options.changeSource == 'diffSha256' then
		local newSha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { sha256 = newSha256 } } }
	elseif options.changeSource == 'noSha256' then
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { sha256 = { _delete = true } } } }
	else
		overlay = nil
	end

	if overlay then
		rxTest:waitFor( catalog:docStore():applyOverlay( overlay ) )
	end

	local copiedAssetId
	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( catalog, { sourceAssetId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return #copiedAssetIds
		end ),
	1 )

	local copiedRevisionId, _, _, _, xmpDevelopFilePath = getCopiedAssetInfo( rxTest, catalog, copiedAssetId )
	local xmpDevelopSha256 = sourceAssetDoc.develop.xmpCameraRaw.sha256

	if WFPlatformInfo.isMobile() then
		local _, absolutePath =
				OzFilePaths.getXmpDevelopPath( catalogId, { _id = copiedAssetId }, xmpDevelopSha256, true )
		xmpDevelopFilePath = absolutePath
	end

	-- make most binaries appear to be uploaded to copied asset (just testing upload of xmp develop)
	-- (copying the links over from the source document is a little hacky, but also simple and it works.)
	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = copiedAssetId,
		_serverProvidedMetadata = {
			links = sourceAssetDoc._serverProvidedMetadata.links,
		}
	} )

	-- once the copy is complete delete the source document. Do not want attempt to upload binaries
	-- for that document interfering with tests

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = sourceAssetId,
		_deleted = true,
		_serverProvidedMetadata = { _delete = true },
		_localOnly = { _delete = true },
	} )

	overlay = { _id = copiedAssetId }

	-- some options passed in alter the /rels/xmp/develop link on the copied asset such that
	-- the binary needs to be uploaded instead of copied on Oz. Even if there is no option
	-- passed, the /rels/xmp/develop link is removed from _serverProvidedMetadata otherwise
	-- the binary would not need to be uploaded OR copied. The options are very similar to
	-- the options for changeSource (invalid, diffSha256, noSha256) see earlier comment.
	-- (noLink is not an option since that is the default case.)

	if options.changeCopy == 'invalid' then
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { invalid = true, sha256 = { _delete = true } } } }
	elseif options.changeCopy == 'diffSha256' then
		local newSha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { sha256 = newSha256 } } }
	elseif options.changeCopy == 'noSha256' then
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { sha256 = { _delete = true } } } }
	else
		overlay._serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { _delete = true } } }
	end

	rxTest:waitFor( catalog:docStore():applyOverlay( overlay ) )
		-- application of overlay is required to either alter or completely remove
		-- the /rels/xmp/develop link to trigger the binary upload.

	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, copiedAssetId, copiedRevisionId ) )
		-- add create links to copied document

	-- in atomicDevelopSettings case when sha256 is missing, there is a second requirement that
	-- an unsynced local revision exist to trigger binary upload instead of copy

	if options.updateCopiedAsset then
		rxTest:waitFor( catalog:docStore():applyOverlay {
			_id = copiedAssetId,
			develop = { fromDefaults = true },
		} )
	end

	local copiedAssetDoc
	rxTest:waitFor( catalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			copiedAssetDoc = docValue
			copiedRevisionId = copiedAssetDoc._revId
			return docValue
		end )
	)

	local mocks = {}

	if options.atomicDevelopSettings then

		local localOnly = {
			developSettingsSha256 = xmpDevelopSha256,
			xmpDevelopSettingsFilePath = xmpDevelopFilePath,
			xmpDevelopSettingsFileSize = WFFileUtils.calculatePathSize( xmpDevelopFilePath ),
		}
		WFCatBinaryUploaderTestUtils.getXmpDevelopDirectUploadMocksForAsset( catalog, copiedAssetDoc, localOnly, mocks, options )

	else

		table.insert( mocks, WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponse( catalogId, copiedAssetId,
				copiedRevisionId, xmpDevelopFilePath ) )

	end

	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	httpRequestsProcessed:expectDataWithinTime( 2, #mocks )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_sourceNoLink( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { changeSource = 'noLink', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_sourceInvalid( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { changeSource = 'invalid', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_copyInvalid( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { changeCopy = 'invalid', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_sourceNoSha256( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { changeSource = 'noSha256', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_ADS_sourceNoLink( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { atomicDevelopSettings = true, changeSource = 'noLink', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_ADS_sourceNoSha256( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { atomicDevelopSettings = true, changeSource = 'noSha256', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_ADS_copyNoSha256( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { atomicDevelopSettings = true, changeCopy = 'noSha256',
		updateCopiedAsset = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_ADS_sourceDiffSha256( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { atomicDevelopSettings = true, changeSource = 'diffSha256', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_xmpDevelop_ADS_copyDiffSha256( rxTest )

	testMockedCopyXmpDevelopUpload( rxTest, { atomicDevelopSettings = true, changeCopy = 'diffSha256', } )

end

--==============================================================================
-- @mark Direct upload
--==============================================================================

--[[!
	\brief Helper function to setup mocked tests for binary upload.

	\param rxTest (rxTest)
	\param options (optional table) Optional document modifiers:

		atomicDevelopSettings (boolean): if true then testing then enable atomic
			develop settings capability and set up mock expectations accordingly.

		includeXmpSidecar (boolean): if true include upload of xmp metadata
			sidecar in test

		includeAux (boolean): if true include upload of aux mask and filter binary

		includeXmpDevelop (boolean): if true include upload of external xmp develop
			binary

		includeOriginal (boolean): if true include upload of original binary

		includeProxy (boolean): if true include upload of proxy binary

		latestRevisionId (string, uuid): if included then certain binaries will upload
			to a child revision created outside of this function, so use this
			latestRevisionId when setting up the mock expectations for Oz API calls.

		spaceId (string, uuid): if passed in then mock tests are run against a space
			instead of a catalog.

		contributor (boolean): if true then test space upload with contributor user
			instead of editor

		useAbsolutePathForAuxRelativePath (boolean): if true then test is for bug fix
			WF-2129, see comments elsewhere in this file on that story.

		missingOriginalDCUTemplate (boolean): if true then test reloading document to
			get the template link. If "error" then will return error on reloading
			document. If "stillMissing" will reload the document without the template
			link.

		missingDevelopSettingsDCUTemplates (boolean): if true then test reloading document to
			get the xmp camera raw and/or aux template link. If "error" then will return error
			on reloading document.

		priorXmpDevelopUploaded (string): if set then asset has an /res/xmp/develop link
			before the new xmp develop is uploaded. If set to "sha256" then the link will have
			a different sha256 than the locally set xmpCameraRaw sha256. If set to "nosha256"
			then the link will not have sha256 present.

		ignoreXmpDevelopTrashAnnotation (boolean): if true then apply trash uploaded
			/rels/xmp/develop = true annotation in _localOnly. Only makes sense to enable if
			includeXmpDevelop is also set to true.

	\return catalog, catalogSpec, uploader, catalogId, assetId, revisionId, auxes,
		xmpDevelopSha256, sha256, mocks, syncDiagnosticsObserver, expectedSyncDiagnostics

--]]
local function setupMockedUploadTest( rxTest, options )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = options.spaceId
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local reloadLinksError = ( options.missingOriginalDCUTemplate == 'error' or options.missingDevelopSettingsDCUTemplates == 'error' )
		-- all tests that set up this error condition expect NO binaries to successfully upload

	local setupOptions = {
		unordered = not reloadLinksError,
		atomicDevelopSettings = options.atomicDevelopSettings,
	}
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, setupOptions )

	-- setup sync diagnostics

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.01 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	local expectedSyncDiagnostics = {
		documentsNeedingBinaryUpload = 0,
		inFlightUploads = {},
		isCurrentlySyncing = false,
		numberOfInFlightUploads = 0,
		numberOfScheduledBinaryUploads = 0,
		recentlyUploaded = { },
		summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
		uploadsByType = { },
	}

	if options.includeXmpSidecar then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload + 1
	end
	if options.includeAux then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload + 4
	end
	if options.includeXmpDevelop then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload + 1
	end
	if options.includeOriginal then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload + 1
	end
	if options.includeProxy then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload + 1
	end

	-- configure the asset

	local assetDocOpts = {}

	if options.includeXmpSidecar then
		assetDocOpts.noXmpSidecarLink = true
	else
		assetDocOpts = KSTableUtils.merge( {}, assetDocOpts, { localXmpSidecarDoesNotExist = true, noXmpSidecarLink = true } )
	end

	if options.includeOriginal then
		assetDocOpts.noMasterLink = true
		assetDocOpts.noLocalOriginal = options.noLocalOriginal
		assetDocOpts.noProxyLink = options.noProxyLink
		assetDocOpts.noLocalProxy = options.noLocalProxy
	else
		assetDocOpts.noLocalOriginal = true
	end

	if options.includeProxy then
		assetDocOpts = KSTableUtils.merge( {}, assetDocOpts, { noMasterLink = true, noLocalOriginal = true, noProxyLink = true } )
	else
		assetDocOpts.noLocalProxy = true
	end

	if options.includeXmpDevelop then
		if not options.priorXmpDevelopUploaded then
			assetDocOpts.noDevelopSettingsLink = true
			assetDocOpts.noLocalDevelopSettings = options.noLocalDevelopSettings
		elseif options.priorXmpDevelopUploaded == 'nosha256' then
			assetDocOpts.ozDevelopSettingsWithoutSha256 = true
		end
	end

	if options.includeAux then
		assetDocOpts = KSTableUtils.merge( {}, assetDocOpts, { hasDevelopAux = true, noDevelopAuxLinks = true } )
	end

	if options.noDevelopUserUpdated then
		assetDocOpts.noDevelopUserUpdated = true
	end

	if spaceId then

		assetDocOpts.spaceId = options.spaceId

		if not options.contributor then
			assetDocOpts.editable = true
		end

	end

	assetDocOpts.sourceAssetId = options.sourceAssetId

	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, assetDocOpts )

	if options.includeProxy then
		-- will not attempt to upload proxy unless this is set
		assetDoc.importSource.willNotUploadMaster = true
	end

	local revisionId = assetDoc._newRevId
	if spaceId then
		WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUploadForIACAsset( assetDoc, spaceId, assetId, revisionId )
	else
		WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )
	end

	if options.priorXmpDevelopUploaded == 'sha256' then
		assetDoc._serverProvidedMetadata.links[ '/rels/xmp/develop' ].sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	end

	-- configure mocks, expecting the upload of the aux (if included), the xmp_develop file
	-- (if included) and the original (if included)

	local auxes, xmpDevelopSha256, sha256, reloadAssetMock
	local mocks = {}

	if options.missingOriginalDCUTemplate or options.missingDevelopSettingsDCUTemplates then

		local links = assetDoc._serverProvidedMetadata.links

		if options.missingOriginalDCUTemplate == 'stillMissing' then
			links[ '/rels/main_create/parts' ] = nil
				-- remove link BEFORE making mock as it should be missing even after refresh
		end

		reloadAssetMock = WFCatBinaryUploaderTestUtils.generateReloadAssetForTemplateLinkMock( catalog, assetId, links, reloadLinksError )
		mocks[ 1 ] = reloadAssetMock

		if options.missingOriginalDCUTemplate then
			links[ '/rels/main_create/parts' ] = nil
		end

		if options.missingDevelopSettingsDCUTemplates then
			links[ '/rels/xmp_develop_create/parts' ] = nil
			links[ '/rels/aux_create/parts' ] = nil
		end

		if reloadLinksError then

			mocks[ 2 ] = KSTableUtils.deepCopy( reloadAssetMock )
			mocks[ 3 ] = KSTableUtils.deepCopy( reloadAssetMock )
				-- in error case there will be three retries, so mock that

			for _, mock in ipairs( mocks ) do
				catalogSpec:expectHttpRequest( mock )
			end

			-- add document to the doc store
			rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

			return catalog, catalogSpec, uploader, catalogId, assetId, revisionId, auxes, xmpDevelopSha256, sha256,
				mocks, syncDiagnosticsObserver, expectedSyncDiagnostics
					-- in case of reloadLinksError, NO binaries will be uploaded, so do not need to continue
					-- mock and sync diagnostics configuration

		end

	end

	-- configure mocks, expecting the upload of the aux (if included), the xmp_develop file
	-- (if included) and the original (if included)

	if options.includeXmpSidecar then

		if spaceId then
			table.insert( mocks, WFCatBinaryUploaderTestUtils.generateCreateXmpSidecarResponseForInAppContribute(
					spaceId, assetId, revisionId, localOnly.xmpSidecarFilePath ) )
		else
			table.insert( mocks, WFCatBinaryUploaderTestUtils.generateCreateXmpSidecarResponse( catalogId, assetId,
					revisionId, localOnly.xmpSidecarFilePath ) )
		end

	end

	if options.includeAux then

		auxes = WFCatBinaryUploaderTestUtils.getAuxDirectUploadMocksForAsset( catalog, assetDoc, mocks, options )

		if options.useAbsolutePathForAuxRelativePath then
			local baseAuxPath = WFCatAssetAux.getBasePath( catalog )
			for _, pathInfo in pairs( assetDoc._localOnly.files.aux ) do
				pathInfo.relativePath = KSPathUtils.child( baseAuxPath, pathInfo.relativePath )
			end
		end

	end

	if options.includeXmpDevelop then

		if options.atomicDevelopSettings then

			if spaceId and options.sourceAssetId then

				table.insert( mocks, WFCatBinaryUploaderTestUtils.generateCopyXmpDevelopResponseWithUrlForInAppContribute( spaceId,
						assetId, revisionId, assetDoc._localOnly.sourceXmpDevelopLink ) )
				xmpDevelopSha256 = assetDoc._localOnly.sourceXmpDevelopSha256

			else

				xmpDevelopSha256 = WFCatBinaryUploaderTestUtils.getXmpDevelopDirectUploadMocksForAsset( catalog, assetDoc, localOnly,
						mocks, options )

			end

		else
			table.insert( mocks, WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponse( catalogId, assetId,
					options.latestRevisionId or revisionId, localOnly.xmpDevelopSettingsFilePath ) )
			xmpDevelopSha256 = localOnly.developSettingsSha256
		end

	end

	if options.includeOriginal then

		sha256 = localOnly.originalSha256

		if spaceId then

			if options.sourceAssetId then

				local readMetadataNone = not not options.includeXmpSidecar

				local originalMock = WFCatBinaryUploaderTestUtils.generateCopyMasterResponseWithUrlForInAppContribute( spaceId, assetId,
						options.latestRevisionId or revisionId, assetDoc._localOnly.sourceOriginalLink, readMetadataNone )

				table.insert( mocks, originalMock )

			else

				local originalMock = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta(
						spaceId, assetId, options.latestRevisionId or revisionId, localOnly.originalFilePath )
				table.insert( mocks, originalMock )
					-- direct upload not supported for originals in spaces

			end

		elseif options.missingOriginalDCUTemplate == 'stillMissing' then

			local originalMock = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseWithXReadMeta(
					catalogId, assetId, options.latestRevisionId or revisionId, localOnly.originalFilePath )
			table.insert( mocks, originalMock )
				-- will fall back to direct upload

		else

			WFCatBinaryUploaderTestUtils.getOriginalDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks,
					options.latestRevisionId or revisionId, options.includeXmpSidecar )

		end

	end

	if options.includeProxy then

		sha256 = derefOpt( assetDoc, 'importSource', 'sha256' )
		local proxyMock = WFCatBinaryUploaderTestUtils.generateCreateProxyResponse( catalogId, assetId,
				options.latestRevisionId or revisionId, localOnly.proxyContentId, localOnly.proxyFilePath )
		table.insert( mocks, proxyMock )

	end

	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	if options.ignoreXmpDevelopTrashAnnotation then

		rxTest:expectEqual( true, options.includeXmpDevelop )

		assetDoc = WFTableMerge.applyOverlay( assetDoc, {
			_localOnly = {
				universal = {
					trash = {
						uploaded = {
							[ '/rels/xmp/develop' ] = true
						}
					}
				}
			}
		} )

	end

	-- add document to the doc store

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	return catalog, catalogSpec, uploader, catalogId, assetId, revisionId, auxes, xmpDevelopSha256, sha256,
		mocks, syncDiagnosticsObserver, expectedSyncDiagnostics

end

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

--[[!
	\brief Runs mock tests of upload of direct upload of binaries without atomic
		develop settings feature, meaning that aux and original binaries are
		uploaded directly but not xmp deveop. Can optionally include many different
		combinations of binaries, see optiosn below

	\param rxTest (rxTest)
	\param options (optional table) Optional document modifiers:

		includeXmpSidecar (boolean): if true include upload of xmp metadata
			sidecar in test

		includeAux (boolean): if true include upload of aux mask binary

		includeXmpDevelop (boolean): if true include upload of external xmp develop
			binary

		includeOriginal (boolean): if true include upload of original binary

		includeProxy (boolean): if true include upload of proxy binary

		simulateBinaryMissingError (boolean): if true then test that successful upload
			of binary will NOT modify error annotation without atomic develop settings
			feature enabled.

	\return catalog, catalogSpec, uploader, assetId

--]]
local function testMockedDirectUploadWithoutADS( rxTest, options )

	local revisionId = KSIdGenerator.uuidInLowercaseHex()

	if options.includeAux or options.includeXmpDevelop then
		-- will be uploading binaries to a later revision id
		options.latestRevisionId = revisionId
	end

	local catalog, catalogSpec, uploader, catalogId, assetId, firstRevisionId, auxes, xmpDevelopSha256, sha256,
		mocks, syncDiagnosticsObserver, expectedSyncDiagnostics
			= setupMockedUploadTest( rxTest, options )

	if options.simulateBinaryMissingError then

		-- test that non-ADS upload will NOT modify a BinaryMissingError annotation from document

		applyErrorOverlay( rxTest, catalog, assetId, 'BinaryMissingError' )

	end

	-- upload of aux and xmp develop will not sync until all revisions are synced to Oz
	addRevisionNotSyncedToOz( rxTest, catalog, assetId, revisionId )

	-- start uploading

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	uploader:startContinuousUploads()

	local totalUploaded = 0
	local totalS3Uploads = 0

	local uploading = rxTest:observe( uploader:isDoingWork() )

	if options.includeXmpSidecar then

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		expectedSyncDiagnostics.recentlyUploaded[ assetId ] = { }
		expectedSyncDiagnostics.recentlyUploaded[ assetId ].xmp_sidecar = { [ true ] = true }
		expectedSyncDiagnostics.summaryStr = string.format( 'Catalog: Uploaded %d binaries to Oz.', totalUploaded )
		expectedSyncDiagnostics.isCurrentlySyncing = true
		expectedSyncDiagnostics.uploadsByType.catalog = { }
		expectedSyncDiagnostics.uploadsByType = { catalog = { } }
		expectedSyncDiagnostics.uploadsByType.catalog.xmp_sidecar = { total = 1, }
		expectedSyncDiagnostics.uploadsByType.catalog.total = totalUploaded

		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- make it look like the metadatapuller got an updates with the xmp sidecar link

		rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, assetId, firstRevisionId ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	if options.includeAux then

		-- uploader should upload aux binaries before xmp develop but not until all revisions have synced to Oz
		-- and documentsNeedingBinaryUpload count in diagnostics should not reduce until until links are synced back from Oz

		-- should not have changed
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- syncing should stop (or not have started)
		uploading:expectDataWithinTime( 5, false )

		-- aux will not upload until latest revision is synced to Oz
		revisionSyncedToOz( rxTest, catalog, assetId, { firstRevisionId, revisionId } )

		-- syncing should start again
		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 4
		totalS3Uploads = totalS3Uploads + 4
		expectedSyncDiagnostics.recentlyUploaded[ assetId ] = expectedSyncDiagnostics.recentlyUploaded[ assetId ] or { }
		expectedSyncDiagnostics.recentlyUploaded[ assetId ].aux = { [ auxes[1].sha256 ] = true, [ auxes[2].sha256 ] = true,
																	[ auxes[3].sha256 ] = true, [ auxes[4].sha256 ] = true }
		expectedSyncDiagnostics.summaryStr = string.format( 'Catalog: Uploaded %d binaries to Oz.', totalUploaded )
		expectedSyncDiagnostics.isCurrentlySyncing = true
		expectedSyncDiagnostics.uploadsByType.catalog = expectedSyncDiagnostics.uploadsByType.catalog or {}
		expectedSyncDiagnostics.uploadsByType.catalog[ 'S3 Uploads' ] = totalS3Uploads
		expectedSyncDiagnostics.uploadsByType.catalog.aux = { ["S3 Uploads"] = 4, total = 4, }
		expectedSyncDiagnostics.uploadsByType.catalog.total = totalUploaded

		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- make it look like the metadatapuller got an updates with the aux links

		rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[1].id, auxes[1].sha256 ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

		rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[2].id, auxes[2].sha256 ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1
		
		rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[3].id, auxes[3].sha256 ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1
		
		rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[4].id, auxes[4].sha256 ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	end

	if options.includeXmpDevelop then

		if not options.includeAux then

			-- should not have changed, have not synced latest revision to Oz yet
			syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

			-- syncing should stop (or not have started)
			uploading:expectDataWithinTime( 5, false )

			-- xmpDevelop will not upload until latest revision is synced to Oz
			revisionSyncedToOz( rxTest, catalog, assetId, { firstRevisionId, revisionId } )

		end

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		expectedSyncDiagnostics.recentlyUploaded[ assetId ] = expectedSyncDiagnostics.recentlyUploaded[ assetId ] or { }
		expectedSyncDiagnostics.recentlyUploaded[ assetId ].xmp_develop = { [ xmpDevelopSha256 ] = true }
		expectedSyncDiagnostics.summaryStr = string.format( 'Catalog: Uploaded %d binaries to Oz.', totalUploaded )
		expectedSyncDiagnostics.isCurrentlySyncing = true
		expectedSyncDiagnostics.uploadsByType.catalog = expectedSyncDiagnostics.uploadsByType.catalog or {}
		expectedSyncDiagnostics.uploadsByType.catalog.xmp_develop = { total = 1, }
		expectedSyncDiagnostics.uploadsByType.catalog.total = totalUploaded

		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- make it look like the metadatapuller got an update with the xmp_develop link in it
		rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	expectedSyncDiagnostics.recentlyUploaded[ assetId ] = expectedSyncDiagnostics.recentlyUploaded[ assetId ] or { }
	expectedSyncDiagnostics.isCurrentlySyncing = true
	expectedSyncDiagnostics.uploadsByType.catalog = expectedSyncDiagnostics.uploadsByType.catalog or {}

	if options.includeOriginal then

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		totalS3Uploads = totalS3Uploads + 1
		expectedSyncDiagnostics.recentlyUploaded[ assetId ].master = { [ sha256 ] = true }
		expectedSyncDiagnostics.summaryStr = string.format( 'Catalog: Uploaded %d binaries to Oz.', totalUploaded )
		expectedSyncDiagnostics.uploadsByType.catalog[ 'S3 Uploads' ] = totalS3Uploads
		expectedSyncDiagnostics.uploadsByType.catalog.master = { ["S3 Uploads"] = 1, total = 1, }
		expectedSyncDiagnostics.uploadsByType.catalog.total = totalUploaded

		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- Make it look like metadatapuller got an update with a master link in it to let the
		-- uploader know there is no more work to do.
		rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	if options.includeProxy then

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		expectedSyncDiagnostics.recentlyUploaded[ assetId ].proxy = { [ sha256 ] = true }
		expectedSyncDiagnostics.summaryStr = string.format( 'Catalog: Uploaded %d binaries to Oz.', totalUploaded )
		expectedSyncDiagnostics.uploadsByType.catalog.proxy = { total = 1, }
		expectedSyncDiagnostics.uploadsByType.catalog.total = totalUploaded

		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- Make it look like metadatapuller got an update with a proxy link in it to let the
		-- uploader know there is no more work to do.
		rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, assetId, revisionId, sha256 ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	completionStream:expectDataWithinTime( 2, assetId )
	completionStream:dispose()

	syncDiagnosticsObserver:dispose()

	local mocksIndex = 1

	-- check that requests were processed in the expected order

	if options.includeXmpSidecar then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, 1 )
		mocksIndex = mocksIndex + 1

	end

	if options.includeAux then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived < mocks[ mocksIndex + 1 ].orderReceived, true )
		rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived < mocks[ mocksIndex + 2 ].orderReceived, true )

		mocksIndex = mocksIndex + 3

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived < mocks[ mocksIndex + 1 ].orderReceived, true )
		rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived < mocks[ mocksIndex + 2 ].orderReceived, true )

		mocksIndex = mocksIndex + 3
		
		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived < mocks[ mocksIndex + 1 ].orderReceived, true )
		rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived < mocks[ mocksIndex + 2 ].orderReceived, true )

		mocksIndex = mocksIndex + 3
		
		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived < mocks[ mocksIndex + 1 ].orderReceived, true )
		rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived < mocks[ mocksIndex + 2 ].orderReceived, true )

		mocksIndex = mocksIndex + 3

		rxTest:expectEqual( mocks[ 3 ].orderReceived < mocksIndex, true )
		rxTest:expectEqual( mocks[ 6 ].orderReceived < mocksIndex, true )
		rxTest:expectEqual( mocks[ 9 ].orderReceived < mocksIndex, true )
		rxTest:expectEqual( mocks[ 12 ].orderReceived < mocksIndex, true )

	end

	if options.includeXmpDevelop then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
		mocksIndex = mocksIndex + 1

	end

	if options.includeOriginal then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
		rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived, mocksIndex + 1 )
		rxTest:expectEqual( mocks[ mocksIndex + 2 ].orderReceived, mocksIndex + 2 )

		mocksIndex = mocksIndex + 3

	end

	if options.includeProxy then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
		mocksIndex = mocksIndex + 1

	end

	-- confirm that BinaryMissingError annotation was NOT modified if that is expected

	if options.simulateBinaryMissingError then

		rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
			:map( function( docValue )
				return KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'subtype' )
			end ),
		'BinaryMissingError' )

	end

	return catalog, catalogSpec, uploader, assetId

end

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

-- rxTestMockedUpload_classic_proxy and rxTestUpload_classic_proxy_testNightly is testing
-- proxy upload in the case of classic

if  WFPlatformInfo.isDesktop then

	function WFCatBinaryUploaderTest.rxTestMockedUpload_classic_proxy_testSerially( rxTest )

		-- This test demonstrates the uploader will upload proxy in classic ( willNotUploadMaster is true)
		-- even with original info in localonly

		rxTest:setTimeLimit( 10 )

		WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', 'true' )

		rxTest:finally( function()
			WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', nil )
		end )

		local catalogId = KSIdGenerator.uuidInLowercaseHex()
		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

		local assetDocOpts = { localXmpSidecarDoesNotExist = true, noXmpSidecarLink = true, noMasterLink = true, noProxyLink = true, }
		local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, assetDocOpts )

		-- modify importSource part
		local proxyFilePath = WFCatAssetUtils.getFullProxyPathFromDocValue( assetDoc )
		local proxySha256 = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'proxy', '2560', 'sha256' )

		local importSourceChanges = {
			_id = assetId,
			importSource = {
				willNotUploadMaster = true,
			},
		}

		assetDoc = WFTableMerge.applyOverlay( assetDoc, importSourceChanges )

		local revisionId = KSIdGenerator.uuidInLowercaseHex()

		WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

		-- expect the upload of the proxy

		local proxyMock = WFCatBinaryUploaderTestUtils.generateCreateProxyResponse( catalogId, assetId,
					 revisionId, proxySha256, proxyFilePath )

		local proxyUrl = proxyMock and proxyMock.url
		local proxy_id = KSPathUtils.leafName( proxyUrl )

		catalogSpec:expectHttpRequest( proxyMock )

		rxTest:waitFor( catalog:docStore():applyOverlay( assetDoc ) )

		uploader:startContinuousUploads()

		local uploading = rxTest:observe( uploader:isDoingWork() )
		uploading:expectDataWithinTime( 5, true )

		local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

		-- wait for the first call to perform proxy upload
		httpRequestsProcessed:expectDataWithinTime( 5, 1 )

		-- setup sync diagnostics

		local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.01 )
			:map( function( diagnostics )
				return diagnostics
			end ) )

		-- Make sure we're uploading proxy and not the original
		syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					proxy = {
						[ proxy_id ] = true,
					},
				},
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
			uploadsByType = {
			},
		} )

		-- Make sure we didn't run into any unexpected errors.
		local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
		rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

	end

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

	function WFCatBinaryUploaderTest.rxTestUpload_classic_proxy_testNightly( rxTest )

		rxTest:setTimeLimit( 360 )

		WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', 'true' )

		rxTest:finally( function()
			WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', nil )
		end )

		local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

		-- Create a fake proxies folder for documents we set directly, copy image to that directory.
		local originalFilePath, proxyFilePath, sha256 = getPathsFor( rxTest, 'small.JPG', { original = true, proxy = true } )
		local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath, sha256, proxyFilePath )

		-- modify importSource part
		local importSourceChanges = {
			_id = assetId,
			importSource = {
				willNotUploadMaster = true,
			},
		}

		assetDoc = WFTableMerge.applyOverlay( assetDoc, importSourceChanges )
		rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

		-- start up sync engine
		rxTest:waitFor( pusher:sendUpdatesContinuously() )
		puller:getUpdatesContinuously()
		uploader:startContinuousUploads()

		local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:map( function( docValue )
				if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560', 'href')
				and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/2048', 'href')
				-- no master link
				and not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') then
					return true
				end
			end ) )

		docObserver:expectDataWithinTime( 360, true )

	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_original( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_auxXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeAux = true, includeXmpDevelop = true, includeOriginal = true,  } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_xmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeXmpDevelop = true, includeOriginal = true,  } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_xmpDevelop_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeXmpDevelop = true, simulateBinaryMissingError = true,  } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_auxXmpDevelopProxy( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeAux = true, includeXmpDevelop = true, includeProxy = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_xmpDevelopProxy( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeXmpDevelop = true, includeProxy = true,  } )

end

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

--[[!
	\brief Runs mock tests of upload of atomic develop settings upload which
		requires direct upload of most binary types. Can optionally include
		many different binary types, see options below.

	\param rxTest (rxTest)
	\param options (optional table) Optional document modifiers:

		includeXmpSidecar (boolean): if true include upload of xmp metadata
			sidecar in test

		includeAux (boolean): if true include upload of aux mask binary

		includeXmpDevelop (boolean): if true include upload of external xmp develop
			binary

		noLocalDevelopSettings (boolean): Only when includeXmpDevelop is true,
			if this value is true, there would be no local xmpDevelop binary

		includeOriginal (boolean): if true include upload of original binary

		noLocalOriginal (boolean): Only when includeOriginal is true, if this
			value is true, there would be no local original binary

		noProxyLink (boolean): Only when includeOriginal is true, if this
			value is true, there would be no proxy link

		noLocalProxy (boolean): Only when includeOriginal is true, if this
			value is true, there would be no local proxy binary

		includeProxy (boolean): if true include upload of proxy binary

		fileStatusUploaded (boolean): if true then the requests to get direct upload links
			API on Oz is mocked out to return file_status = uploaded, meaning that binary
			upload has already occurred and should not be repeated.

		simulateErrorAnnotation (string): if true then test that successful upload
			of xmp develop or aux modifies BinaryMissingError annotation, changing it to
			DirectUploadComplete (and that successful uploads of other binary types will NOT
			modify this annotation) if the string "BinaryMissingError" is provided.
			If a different string is provided, tests that NO binary upload completion will
			modify that error annotation.

		spaceId (string, uuid): if passed in then mock tests are run against a space
			instead of a catalog.

		sourceAssetId (string, uuid): if provide, the asset is related to source assetId.
			docValue would contain sourceAssetId and source binary links

		contributor (boolean): if true then test space upload with contributor user
			instead of editor

		missingOriginalDCUTemplate (boolean): if true then test reloading document to
			get the template link. If "error" then will return error on reloading
			document. If "stillMissing" will reload the document without the template
			link.

		missingDevelopSettingsDCUTemplates (boolean): if true then test reloading document to
			get the xmp camera raw and/or aux template link. If "error" then will return error
			on reloading document.

		priorXmpDevelopUploaded (string): if set then asset has an /res/xmp/develop link
			before the new xmp develop is uploaded. If set to "sha256" then the link will have
			a different sha256 than the locally set xmpCameraRaw sha256. If set to "nosha256"
			then the link will not have sha256 present.

		useTempLinks (boolean): if true then apply temp links to asset document for xmp and aux
			which will cause uploader to consider those binaries uploaded.

		ignoreXmpDevelopTrashAnnotation (boolean): if true test that xmp develop correctly
			ignores trash annotation indicating should wait for copy of xmp_develop and instead
			upload it when appropriate. Only makes sense to enable if includeXmpDevelop is also
			set to true.

		hasMetadataNotSyncedToOz (boolean): if true then make asset appear such that
			some revision has been applied to it that has not synced to Oz.

--]]
local function testMockedAtomicDevelopSettingsUpload( rxTest, options )

	local revisionId = KSIdGenerator.uuidInLowercaseHex()

	options.latestRevisionId = revisionId
	options.atomicDevelopSettings = true

	local catalog, _, uploader, catalogId, assetId, firstRevisionId, auxes, xmpDevelopSha256, sha256,
		mocks, syncDiagnosticsObserver, expectedSyncDiagnostics
			= setupMockedUploadTest( rxTest, options )

	local spaceId = options.spaceId

	local revisionIds = { firstRevisionId, revisionId }

	if options.simulateErrorAnnotation then

		-- test that completion of direct upload of aux or xmp_develop will modify
		-- BinaryMissingError annotation, changing it to DirectUploadComplete, OR
		-- that direct upload of other types will NOT modify BinaryMissingError OR
		-- that aux or xmp_develop upload will not modify other error annotations.

		applyErrorOverlay( rxTest, catalog, assetId, options.simulateErrorAnnotation )

	end

	local uploadError = ( options.missingOriginalDCUTemplate == 'error' or options.missingDevelopSettingsDCUTemplates == 'error' )
		-- in case where retrieval of asset link fails, to keep test manageable this always
		-- means NO binaries will upoad

	-- upload of original or proxy will not sync until all revisions are synced to Oz
	addRevisionNotSyncedToOz( rxTest, catalog, assetId, revisionId )

	-- start uploading

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )

	if uploadError then

		if options.includeOriginal then

			-- original will not attempt upload until latest revision is synced to Oz
			revisionSyncedToOz( rxTest, catalog, assetId, revisionIds )

		end

		-- nothing will be uploadable due to error
		uploading:expectDataWithinTime( 2, false )
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- expect ResourceNotFoundError to be logged three times
		-- one for each retry.

		rxTest:observe( Observable.timer( .1, .1, WFRxScheduler.sharedScheduler() )
			:map( function()

				local log = getDiagnosticLogErrorsIgnoringAccountError( uploader )
				local count = -1
				local i = -1

				while i do
					count = count + 1
					i = string.find( log, 'ResourceNotFoundError', i + 1 )
				end

				return count

			end ) )
			:expectDataWithinTime( 5, 3 )
			:dispose()

		-- still nothing is uploadable
		uploading:expectDataWithinTime( 2, false )
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		return
			-- all done! nothing more to test!

	end

	local totalUploaded = 0
	local totalS3Uploads = 0
	local uploadSummaryStr =  "Uploaded 0 binaries to Oz."
	local uploadsByType = { total = totalUploaded }
	local recentlyUploaded = { }

	expectedSyncDiagnostics.isCurrentlySyncing = true

	local function formatExpectedSyncDiagnostics()

		expectedSyncDiagnostics.recentlyUploaded[ assetId ] = recentlyUploaded

		if spaceId then
			expectedSyncDiagnostics.summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": " .. uploadSummaryStr
			expectedSyncDiagnostics.uploadsByType[ spaceId ] = uploadsByType
		else
			expectedSyncDiagnostics.summaryStr = "Catalog: " .. uploadSummaryStr
			expectedSyncDiagnostics.uploadsByType.catalog = uploadsByType
		end

	end

	if options.includeXmpSidecar then

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		recentlyUploaded.xmp_sidecar = { [ true ] = true }
		uploadSummaryStr = string.format( 'Uploaded %d binaries to Oz.', totalUploaded )
		uploadsByType.xmp_sidecar = { total = 1, }
		uploadsByType.total = totalUploaded

		formatExpectedSyncDiagnostics()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- make it look like the metadatapuller got an updates with the xmp sidecar link

		rxTest:waitFor( addXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )
		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	-- order of upload is not deterministic between aux and xmp, but they will upload before
	-- receving confirmation that revision has synced to Oz, not after as they did before
	-- atomic develop settings

	if options.includeAux then
		totalUploaded = totalUploaded + 4
		totalS3Uploads = totalS3Uploads + 4
		recentlyUploaded.aux = { [ auxes[1].sha256 ] = true, [ auxes[2].sha256 ] = true,
								[ auxes[3].sha256 ] = true, [ auxes[4].sha256 ] = true }
		uploadsByType.aux = { ["S3 Uploads"] = 4, total = 4, }
	end

	if options.includeXmpDevelop then
		totalUploaded = totalUploaded + 1
		totalS3Uploads = totalS3Uploads + 1
		if options.sourceAssetId then
			recentlyUploaded.xmp_develop = { [ true ] = true }
			uploadsByType.xmp_develop = { ["Oz Copies"] = 1, total = 1, }
		else
			recentlyUploaded.xmp_develop = { [ xmpDevelopSha256 ] = true }
			uploadsByType.xmp_develop = { ["S3 Uploads"] = 1, total = 1, }
		end
	end

	if options.includeAux or options.includeXmpDevelop then

		uploadSummaryStr = string.format( 'Uploaded %d binaries to Oz.', totalUploaded )
		if options.sourceAssetId then
			uploadsByType[ 'Oz Copies' ] = totalS3Uploads
		else
			uploadsByType[ 'S3 Uploads' ] = totalS3Uploads
		end
		uploadsByType.total = totalUploaded

		-- uploader should upload aux and xmp binaries but not reduce documentsNeedingBinaryUpload count
		-- until links are synced back from Oz

		uploading:expectDataWithinTime( 5, true )

		formatExpectedSyncDiagnostics()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	end

	if options.includeAux then

		-- make it look like the metadatapuller got an updates with the aux links

		if options.useTempLinks then

			rxTest:waitFor( addAuxTempLinkToDoc( catalog, assetId, auxes[1].sha256 ) )
			rxTest:waitFor( addAuxTempLinkToDoc( catalog, assetId, auxes[2].sha256 ) )
			rxTest:waitFor( addAuxTempLinkToDoc( catalog, assetId, auxes[3].sha256 ) )
			rxTest:waitFor( addAuxTempLinkToDoc( catalog, assetId, auxes[4].sha256 ) )
				-- temp links appear until revision is accepted by Oz and syncing these will complete upload
				-- contributor will can see temp links as well, so this trumps options.contributor

			-- temp links leave entries in needsBinaryUpload until the permanent link is synced
			-- so do not decrement documentsNeedingBinaryUpload in this case

		elseif options.contributor then

			rxTest:waitFor( addAuxExistsToDoc( catalog, assetId, auxes[1].id ) )
			rxTest:waitFor( addAuxExistsToDoc( catalog, assetId, auxes[2].id ) )
			rxTest:waitFor( addAuxExistsToDoc( catalog, assetId, auxes[3].id ) )
			rxTest:waitFor( addAuxExistsToDoc( catalog, assetId, auxes[4].id ) )
				-- contributor just gets exists = true as is unable to download aux after uploading

			expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 4

		else

			rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[1].id, auxes[1].sha256 ) )
			rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[2].id, auxes[2].sha256 ) )
			rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[3].id, auxes[3].sha256 ) )
			rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, auxes[4].id, auxes[4].sha256 ) )

			expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 4

		end

	end

	if options.includeXmpDevelop then

		-- make it look like the metadatapuller got an update with the xmp_develop link in it

		if options.useTempLinks then

			rxTest:waitFor( addXmpDevelopTempLinkToDoc( catalog, assetId, xmpDevelopSha256 ) )
				-- temp links appear until revision is accepted by Oz and syncing these will complete upload
				-- contributor will can see temp links as well, so this trumps options.contributor

			-- temp links leave entries in needsBinaryUpload until the permanent link is synced
			-- so do not decrement documentsNeedingBinaryUpload in this case

		elseif options.contributor then

			rxTest:waitFor( addXmpDevelopExistsToDoc( catalog, catalogId, assetId, xmpDevelopSha256 ) )
				-- contributor just gets exists = true as is unable to download xmp develop after uploading

			expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

		else

			rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )

			expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

		end

	end

	if options.includeAux or options.includeXmpDevelop then

		-- NOW uploader decremented documentsNeedingBinaryUpload in sync diagnostics

		uploading:expectDataWithinTime( 5, true )

		formatExpectedSyncDiagnostics()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- uploader should keep working but NOT upload original or proxy yet

		uploading:expectDataWithinTime( 5, true )

		formatExpectedSyncDiagnostics()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	end

	-- uploader should not be uploading

	uploading:expectDataWithinTime( 5, false )

	if options.includeOriginal or options.includeProxy then

		-- original or proxy will not upload until latest revision is synced to Oz
		revisionSyncedToOz( rxTest, catalog, assetId, revisionIds )

		if options.missingOriginalDCUTemplate == 'stillMissing' then

			local observeIsSkipDirectUpload = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
				:pluck( '_localOnly', 'binaryUpload', 'master', 'isSkipDirectUpload' )
			)
			observeIsSkipDirectUpload:expectDataWithinTime( 5, true )
				-- when links are reloaded without original direct upload template
				-- should apply isSkipDirectUpload to avoid looping on trying to
				-- load the template link

		end

	end

	if options.includeOriginal then

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		recentlyUploaded.master = { [ sha256 ] = true }
		uploadSummaryStr = string.format( 'Uploaded %d binaries to Oz.', totalUploaded )
		uploadsByType.master = { total = 1, }
		uploadsByType.total = totalUploaded

		-- direct upload not supported for originals in spaces and not done when no
		-- direct upload template link can be found
		if spaceId == nil and options.missingOriginalDCUTemplate ~= 'stillMissing' then
			totalS3Uploads = totalS3Uploads + 1
			uploadsByType[ 'S3 Uploads' ] = totalS3Uploads
			uploadsByType.master["S3 Uploads"] = 1
		elseif spaceId and options.sourceAssetId then
			totalS3Uploads = totalS3Uploads + 1
			uploadsByType[ 'Oz Copies' ] = totalS3Uploads
			uploadsByType.master["Oz Copies"] = 1
		end

		formatExpectedSyncDiagnostics()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		if options.missingOriginalDCUTemplate == 'stillMissing' then

			local observeIsSkipDirectUpload = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
				:pluck( '_localOnly', 'binaryUpload', 'master', 'isSkipDirectUpload' )
			)
			observeIsSkipDirectUpload:expectDataWithinTime( 5, nil )
				-- when original upload completes, isSkipDirectUpload should be cleared

		end

		-- Make it look like metadatapuller got an update with a master link in it to let the
		-- uploader know there is no more work to do.

		if options.contributor then

			rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )
			rxTest:waitFor( addXmpExistsToDoc( catalog, catalogId, assetId ) )
				-- contributor just gets exists = true as is unable to download after uploading

		else

			rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

		end

		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	if options.includeProxy then

		uploading:expectDataWithinTime( 5, true )

		totalUploaded = totalUploaded + 1
		recentlyUploaded.proxy = { [ sha256 ] = true }
		uploadSummaryStr = string.format( 'Uploaded %d binaries to Oz.', totalUploaded )
		uploadsByType.proxy = { total = 1, }
		uploadsByType.total = totalUploaded

		formatExpectedSyncDiagnostics()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		-- Make it look like metadatapuller got an update with a master link in it to let the
		-- uploader know there is no more work to do.

		if options.contributor then

			rxTest:waitFor( addProxyExistsToDoc( catalog, catalogId, assetId ) )
				-- contributor just gets exists = true as is unable to download after uploading

		else

			rxTest:waitFor( addProxyLinkToDoc( catalog, catalogId, assetId, revisionId, sha256 ) )

		end

		expectedSyncDiagnostics.documentsNeedingBinaryUpload = expectedSyncDiagnostics.documentsNeedingBinaryUpload - 1

	end

	expectedSyncDiagnostics.isCurrentlySyncing = false

	formatExpectedSyncDiagnostics()
	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	completionStream:expectDataWithinTime( 2, assetId )
	completionStream:dispose()

	syncDiagnosticsObserver:dispose()

	local mocksIndex = 1
	local orderOfLastDevelopSettingMockReceived

	-- check that requests were processed in the expected order

	if options.missingOriginalDCUTemplate or options.missingDevelopSettingsDCUTemplates then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
		mocksIndex = mocksIndex + 1

	end

	if options.includeXmpSidecar then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
		mocksIndex = mocksIndex + 1

	end

	if options.includeAux then

		for i = 1, 4 do
		
			if options.fileStatusUploaded then
				mocksIndex = mocksIndex + 1
					-- in this case we only call the get links API and get response indicating file upload is complete
			else
				rxTest:expectEqual( mocks[ mocksIndex ].orderReceived < mocks[ mocksIndex + 1 ].orderReceived, true )
				rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived < mocks[ mocksIndex + 2 ].orderReceived, true )
				mocksIndex = mocksIndex + 3
			end

			if i == 1 then
				orderOfLastDevelopSettingMockReceived = mocks[ mocksIndex - 1 ].orderReceived
			else
				orderOfLastDevelopSettingMockReceived = math.max( orderOfLastDevelopSettingMockReceived, mocks[ mocksIndex - 1 ].orderReceived )
			end

		end

	end

	if options.includeXmpDevelop then

		if options.fileStatusUploaded or ( spaceId and options.sourceAssetId ) then
			mocksIndex = mocksIndex + 1
		else
			rxTest:expectEqual( mocks[ mocksIndex ].orderReceived < mocks[ mocksIndex + 1 ].orderReceived, true )
			rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived < mocks[ mocksIndex + 2 ].orderReceived, true )
			mocksIndex = mocksIndex + 3
		end

		orderOfLastDevelopSettingMockReceived = math.max( orderOfLastDevelopSettingMockReceived or 0, mocks[ mocksIndex - 1 ].orderReceived )

	end

	if orderOfLastDevelopSettingMockReceived and ( options.includeOriginal or options.includeProxy ) then

		-- aux and xmp should have completed before original or proxy upload began

		rxTest:expectEqual( orderOfLastDevelopSettingMockReceived < mocks[ mocksIndex ].orderReceived, true )

	end

	if options.fileStatusUploaded then

		rxTest:expectEqual( uploader:DEBUG_getDirectUploadFailureCountCounter(), 0 )
			-- if file_status=uploaded is handled correctly for aux and xmp_direct, then this status being returned
			-- from Oz should NOT result in increment of the failure counter. If it was handled incorrectly and
			-- an upload was actually attempted, that would have raised an error that would be caught and as a
			-- result of that error _directUploadConsecutiveFailureCounter would be incremented and be greater
			-- than zero.

	end

	if options.includeOriginal then

		if spaceId or options.missingOriginalDCUTemplate == 'stillMissing' then

			rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
				-- direct upload not supported for originals in spaces so there will just be the single, PUT API call

			mocksIndex = mocksIndex + 1

		else

			rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
			rxTest:expectEqual( mocks[ mocksIndex + 1 ].orderReceived, mocksIndex + 1 )
			rxTest:expectEqual( mocks[ mocksIndex + 2 ].orderReceived, mocksIndex + 2 )

			mocksIndex = mocksIndex + 3

		end

	end

	if options.includeProxy then

		rxTest:expectEqual( mocks[ mocksIndex ].orderReceived, mocksIndex )
		mocksIndex = mocksIndex + 1

	end

	-- check that BinaryMissingError annotation was cleared if that is expected
	-- (or NOT cleared if that is expected)

	if options.simulateErrorAnnotation then

		local expectedValue = {
			subtype = options.simulateErrorAnnotation,
			status = 404,
			retries = 2,
		}

		if options.simulateErrorAnnotation == 'BinaryMissingError' and ( options.includeAux or options.includeXmpDevelop ) then
			expectedValue = {
				subtype = 'DirectUploadComplete',
				status = 202,
				retries = 1,
			}
		end

		rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
			:map( function( docValue )
				return {
					subtype = KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'subtype' ),
					status = KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'status' ),
					retries = KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'retries' ),
				}
			end ),
		expectedValue )

	end

	if options.missingOriginalDCUTemplate or options.missingDevelopSettingsDCUTemplates then

			-- check that links overlay correctly removed /rels/proxy_create link and added the foo = bar link

			local assetDoc = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )
			local links = derefOpt( assetDoc, '_serverProvidedMetadata', 'links' )

			rxTest:expectEqual( nil, links[ '/rels/proxy_create' ] )
			rxTest:expectEqual( { href = '/bar' }, links.foo )

		end

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_original( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_original_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeOriginal = true, simulateErrorAnnotation = 'BinaryMissingError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_proxy_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeProxy = true, simulateErrorAnnotation = 'BinaryMissingError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_original_missingTemplate( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeOriginal = true, missingOriginalDCUTemplate = true } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_original_missingTemplate_still( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeOriginal = true, missingOriginalDCUTemplate = 'stillMissing' } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_original_missingTemplate_error( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeOriginal = true, missingOriginalDCUTemplate = 'error' } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_auxXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, includeOriginal = true, spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_contributor_auxXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, includeOriginal = true, spaceId = spaceId,
			contributor = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, includeOriginal = true, spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelopOriginal_fromSource( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()

	testMockedAtomicDevelopSettingsUpload( rxTest, { spaceId = spaceId, sourceAssetId = sourceAssetId,
													includeXmpDevelop = true, noLocalDevelopSettings = true,
													includeOriginal = true, noLocalOriginal = true,
													noProxyLink = true, noLocalProxy = true } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_contributor_xmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, includeOriginal = true, spaceId = spaceId, contributor = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelopProxy( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, includeProxy = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelopProxy( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, {  includeAux = true, includeXmpDevelop = true, includeProxy = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop_tempLinks( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, useTempLinks = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop_missingTemplate( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true,
		missingDevelopSettingsDCUTemplates = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop_missingTemplate_error( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true,
		missingDevelopSettingsDCUTemplates = 'error', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_auxXmpDevelop( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_contributor_auxXmpDevelop( rxTest )


	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, spaceId = spaceId, contributor = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_auxXmpDevelop_tempLinks( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, useTempLinks = true, spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_contributor_auxXmpDevelop_tempLinks( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, useTempLinks = true, spaceId = spaceId,
		contributor = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_auxXmpDevelop_missingTemplate( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, spaceId = spaceId,
		missingDevelopSettingsDCUTemplates = true } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_auxXmpDevelop_missingTemplate_error( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, spaceId = spaceId,
		missingDevelopSettingsDCUTemplates = 'error' } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop_fileStatusUploaded( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, fileStatusUploaded = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop_IAC_fileStatusUploaded( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, fileStatusUploaded = true,
		spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_auxXmpDevelop_noDevelopUserUpdated( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, noDevelopUserUpdated = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_auxXmpDevelop_noDevelopUserUpdated( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, includeXmpDevelop = true, noDevelopUserUpdated = true,
		spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_aux_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, simulateErrorAnnotation = 'BinaryMissingError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_aux_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, simulateErrorAnnotation = 'BinaryMissingError',
		spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_aux_ResourceNotFoundError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, simulateErrorAnnotation = 'ResourceNotFoundError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_aux_ResourceNotFoundError( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeAux = true, simulateErrorAnnotation = 'ResourceNotFoundError',
		spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_missingTemplate( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, missingDevelopSettingsDCUTemplates = true } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_missingTemplate_error( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, missingDevelopSettingsDCUTemplates = 'error' } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_missingSha256( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, priorXmpDevelopUploaded = 'nosha256', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_differentSha256( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, priorXmpDevelopUploaded = 'sha256', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, simulateErrorAnnotation = 'BinaryMissingError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_ResourceNotFoundError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, simulateErrorAnnotation = 'ResourceNotFoundError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_trashWithSha256( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, ignoreXmpDevelopTrashAnnotation = true,
		priorXmpDevelopUploaded = 'sha256', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpDevelop_trashSha256Missing( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, ignoreXmpDevelopTrashAnnotation = true,
			priorXmpDevelopUploaded = 'nosha256',  } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop_missingTemplate( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, spaceId = spaceId,
		missingDevelopSettingsDCUTemplates = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop_missingTemplate_error( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, spaceId = spaceId,
		missingDevelopSettingsDCUTemplates = 'error', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop_missingSha256( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, priorXmpDevelopUploaded = 'nosha256', spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop_differentSha256( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, priorXmpDevelopUploaded = 'sha256', spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, simulateErrorAnnotation = 'BinaryMissingError', spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_IAC_xmpDevelop_ResourceNotFoundError( rxTest )

	rxTest:setTimeLimit( 10 )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpDevelop = true, simulateErrorAnnotation = 'ResourceNotFoundError', spaceId = spaceId, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_Failure_BlockedByEnterprise( rxTest )

	rxTest:setTimeLimit( 120 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_9876.CR2" )
	local fileSize = WFFileUtils.fileAttributes( originalFilePath ).fileSize
	local contentType = 'application/octet-stream'

	-- There is a failure in windows machine due to drive letter's upper case and lower case issue. We manually fix it by always changing it to uppercase for the first character in originalFilePath.
	if WFPlatformInfo.isWindows then
		originalFilePath = originalFilePath:gsub("^%l", string.upper)
	end

	local _, mainPartsRequestUrl = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		assetId = assetId,
		revisionId = revisionId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local kMasterUrl= string.format('/v2/catalogs/%s/assets/%s/revisions/%s/master', catalogId, assetId, revisionId )

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	local errKey = 'content_type'
	local errStr = 'should match the subtype of the asset'
	local errDesc = 'Invald content type'
	local errStatus = '400'

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, errDesc, errStatus),

		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, errDesc, errStatus),

		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, errDesc, errStatus),

		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 1, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 2, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 3, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 4, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 5, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateLastChunkResponse( originalFilePath, 6, kMasterUrl ),
	}

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 15, true )
	uploading:dispose()

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )
	completionStream:expectDataWithinTime( 60, assetId )
	completionStream:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_Failure_FetchMainPartsUrl( rxTest )

	rxTest:setTimeLimit( 20 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( originalFilePath ).fileSize
	local contentType = 'image/jpeg'

	-- There is a failure in windows machine due to drive letter's upper case and lower case issue. We manually fix it by always changing it to uppercase for the first character in originalFilePath.
	if WFPlatformInfo.isWindows then
		originalFilePath = originalFilePath:gsub("^%l", string.upper)
	end

	local _, mainPartsRequestUrl = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		assetId = assetId,
		revisionId = revisionId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	local errKey = 'content_type'
	local errStr = 'should match the subtype of the asset'
	local errDesc = "Can't Fetch MainPartsUrl"
	local errStatus = '401'

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( mainPartsRequestUrl, nil, errKey, errStr, errDesc, errStatus),
		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetUploadMock( assetUploadUrl, originalFilePath, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( string.format( "main/parts?file_size=%d", fileSize), contentType ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.001 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	syncDiagnosticsObserver:expectDataWithinTime( 15,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
			uploadsByType = {}
		} )

	uploader:startContinuousUploads()

	syncDiagnosticsObserver:expectDataWithinTime( 15,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 2 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 2,
					master = {
						["S3 Uploads"] = 2,
						total = 2,
					},
					total = 2,
				},
			},
		} )

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 15, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 15, 3 )

	completionStream:expectDataWithinTime( 15, assetId )
	completionStream:dispose()

	-- Upload Finished

	syncDiagnosticsObserver:expectDataWithinTime( 15,
		{
			documentsNeedingBinaryUpload = 1, -- haven't yet received Oz response with the master link
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 2 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 2,
					master = {
						["S3 Uploads"] = 2,
						total = 2,
					},
					total = 2,
				},
			},
		} )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 15, 0 )

	uploading:expectDataWithinTime( 15, false )

	syncDiagnosticsObserver:expectDataWithinTime( 15,
		{
			documentsNeedingBinaryUpload = 0,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 2 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 2,
					master = {
						["S3 Uploads"] = 2,
						total = 2,
					},
					total = 2,
				}
			}
		} )

	syncDiagnosticsObserver:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_Failure_OtherThanBlockedByEnterprise( rxTest )

	rxTest:setTimeLimit( 30 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( originalFilePath ).fileSize
	local contentType = 'image/jpeg'

	-- There is a failure in windows machine due to drive letter's upper case and lower case issue. We manually fix it by always changing it to uppercase for the first character in originalFilePath.
	if WFPlatformInfo.isWindows then
		originalFilePath = originalFilePath:gsub("^%l", string.upper)
	end

	local _, mainPartsRequestUrl = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		assetId = assetId,
		revisionId = revisionId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	local errKey = 'content_type'
	local errStr = 'should match the subtype of the asset'
	local errDesc = "Can't reach AWS S3"
	local errStatus = '400'

	local completeUrl = string.format( "https://photos-stage.adobe.io/v2/main/parts?file_size=%d", fileSize )
	local postErrDesc = "Can't post complete URL"
	local postErrStatus = '401'

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, errDesc, errStatus),

		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetUploadMock( assetUploadUrl, originalFilePath, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( completeUrl, "", errKey, errStr, postErrDesc, postErrStatus),

		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetUploadMock( assetUploadUrl, originalFilePath, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.001 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	syncDiagnosticsObserver:expectDataWithinTime( 25,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
			uploadsByType = {}
		} )

	uploader:startContinuousUploads()

	syncDiagnosticsObserver:expectDataWithinTime( 25,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 2 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 2,
					master = {
						["S3 Uploads"] = 2,
						total = 2,
					},
					total = 2,
				},
			},
		} )

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 25, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 25, 3 )

	completionStream:expectDataWithinTime( 25, assetId )
	completionStream:dispose()

	-- Upload Finished

	syncDiagnosticsObserver:expectDataWithinTime( 25,
		{
			documentsNeedingBinaryUpload = 1, -- haven't yet received Oz response with the master link
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 3 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 3,
					master = {
						["S3 Uploads"] = 3,
						total = 3,
					},
					total = 3,
				},
			},
		} )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 25, 0 )

	uploading:expectDataWithinTime( 25, false )

	syncDiagnosticsObserver:expectDataWithinTime( 25,
		{
			documentsNeedingBinaryUpload = 0,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 3 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 3,
					master = {
						["S3 Uploads"] = 3,
						total = 3,
					},
					total = 3,
				}
			}
		} )

	syncDiagnosticsObserver:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_Failure_FallBackToOzUpload( rxTest )

	rxTest:setTimeLimit( 120 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_9876.CR2" )
	local fileSize = WFFileUtils.fileAttributes( originalFilePath ).fileSize
	local contentType = 'application/octet-stream'

	-- There is a failure in windows machine due to drive letter's upper case and lower case issue. We manually fix it by always changing it to uppercase for the first character in originalFilePath.
	if WFPlatformInfo.isWindows then
		originalFilePath = originalFilePath:gsub("^%l", string.upper)
	end

	local _, mainPartsRequestUrl = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		assetId = assetId,
		revisionId = revisionId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local kMasterUrl= string.format('/v2/catalogs/%s/assets/%s/revisions/%s/master', catalogId, assetId, revisionId )

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	local errKey = 'content_type'
	local errStr = 'should match the subtype of the asset'
	local errStatus = '400'

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, "Can't reach AWS S3", errStatus ),

		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, 'Invalid content type', errStatus ),

		WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType ),
		WFCatBinaryUploaderTestUtils.generateErrorResponseMock( assetUploadUrl, originalFilePath, errKey, errStr, 'Invalid content type', errStatus ),

		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 1, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 2, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 3, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 4, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateChunkResponse( originalFilePath, 5, kMasterUrl ),
		WFCatBinaryUploaderTestUtils.generateLastChunkResponse( originalFilePath, 6, kMasterUrl ),
	}

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 15, true )

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )
	completionStream:expectDataWithinTime( 60, assetId )
	completionStream:dispose()

	-- Add another asset, and it should use oz-upload to upload
	local assetId2 = KSIdGenerator.uuidInLowercaseHex()
	local revisionId2 = KSIdGenerator.uuidInLowercaseHex()
	local a2_sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath2 = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.001 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	table.insert( urlResponses, WFCatBinaryUploaderTestUtils.generateCreateMasterResponseWithXReadMeta( catalogId, assetId2, revisionId2, originalFilePath2 ) )
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId2, revisionId2, a2_sha256, originalFilePath2 ) )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 2,
			inFlightUploads = {},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
				[ assetId2 ] = {
					master = {
						[ a2_sha256 ] = true
					}
				}
			},
			summaryStr = "Catalog: Uploaded 1 binaries to Oz.",
			uploadsByType = {
				catalog = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 2,
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
				[ assetId2 ] = {
					master = {
						[ a2_sha256 ] = true
					}
				}
			},
			summaryStr = "Catalog: Uploaded 1 binaries to Oz.",
			uploadsByType = {
				catalog = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	uploading:expectDataWithinTime( 5, true )

	-- Upload Finished

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 2,
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
				[ assetId2 ] = {
					master = {
						[ a2_sha256 ] = true
					}
				}
			},
			summaryStr = "Catalog: Uploaded 1 binaries to Oz.",
			uploadsByType = {
				catalog = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId2, revisionId2 ) )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
				[ assetId2 ] = {
					master = {
						[ a2_sha256 ] = true
					}
				}
			},
			summaryStr = "Catalog: Uploaded 1 binaries to Oz.",
			uploadsByType = {
				catalog = {
					master = {
						total = 1,
					},
					total = 1,
				}
			}
		} )

	syncDiagnosticsObserver:dispose()

end

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

local function testMockedDirectUpload_DoNotFallBackToOzUpload( rxTest, binaryType )

	rxTest:setTimeLimit( 20 )

	-- setup a catalog with just a single binary to upload

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local setupOptions = {
		atomicDevelopSettings = (binaryType == 'xmp_develop'),
			-- for now xmp direct upload is only applicable when atomic develop settings feature is enabled
		clearFromRecentlyUploadedSeconds = 360,
			-- set to very high level as will manually call clearFromRecentlyUploaded() from this test
			-- and do not want asynchronous clears to interfere with test and allow extra API calls
			-- which can make test fail
	}
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, setupOptions )

	local assetDocOpts

	if binaryType == 'xmp_develop' then

		assetDocOpts = { noDevelopSettingsLink = true }

	elseif binaryType == 'aux' then

		assetDocOpts = { hasMasks = true, noMaskLinks = true }

	end

	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId,  assetDocOpts )

	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	if binaryType == 'aux' then

		-- remove second mask from assetDoc so that only first aux is considered for upload to keep tests simple

		assetDoc.aux[ localOnly.mask2AuxId ] = nil
		assetDoc._localOnly.files.aux[ localOnly.mask2Fingerprint ] = nil

	end

	-- add asset to docStore

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- setup error mocks

	local partsUrl, sha256

	if binaryType == 'xmp_develop' then

		partsUrl = WFCatBinaryUploaderTestUtils.getXmpDevelopDirectUploadPartsUrlForAsset( catalog, assetDoc, localOnly )

		sha256 = localOnly.developSettingsSha256

	elseif binaryType == 'aux' then

		partsUrl = WFCatBinaryUploaderTestUtils.getAuxDirectUploadPartsUrlForAsset( catalog, assetDoc,
			localOnly.mask1Sha256, localOnly.mask1FileSize )

		sha256 = localOnly.mask1Sha256


	end

	local errKey = 'missing_aux_ids'
	local errValue = { KSIdGenerator.uuidInLowercaseHex() }
	local errorDesc = 'Some of the aux files are not uploaded which is required for rendition generation'
	local errorResponse = WFCatBinaryUploaderTestUtils.generateErrorResponseMock( partsUrl, nil, errKey, errValue, errorDesc, '403', 'SillyFakeError' )
		-- the details of the error do not really matter, any error generated trying to direct
		-- upload these binaries will not cause fallback to oz upload

	local errorMocks = {

		errorResponse,
		errorResponse,
		errorResponse,
			-- normally three error responses in a row would trigger fallback to oz direct upload
			-- but with AuxMissingError this does not make sense as it is a logic bug and not
			-- a problem with direct upload itself due to firewall, etc.

	}

	for _, mock in ipairs( errorMocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	-- setup success mocks

	local successMocks = { }

	if binaryType == 'xmp_develop' then

		WFCatBinaryUploaderTestUtils.getXmpDevelopDirectUploadMocksForAsset( catalog, assetDoc, localOnly, successMocks, { atomicDevelopSettings = true } )

	elseif binaryType == 'aux' then

		WFCatBinaryUploaderTestUtils.getAuxDirectUploadMocksForAsset( catalog, assetDoc, successMocks )

	end

	for _, mock in ipairs( successMocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	-- setup sync diagnostics

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.01 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	-- start uploading

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	local expectedSyncDiagnostics = {
		documentsNeedingBinaryUpload = 1,
		inFlightUploads = {},
		isCurrentlySyncing = false,
		numberOfInFlightUploads = 0,
		numberOfScheduledBinaryUploads = 0,
		recentlyUploaded = { },
		summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
		uploadsByType = { },
	}

	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )

	expectedSyncDiagnostics.isCurrentlySyncing = true
	expectedSyncDiagnostics.recentlyUploaded = { [ assetId ] = { [ binaryType ] = { [ sha256 ] = true } } }

	local numUploads = 0

	local function incrementSyncExpectation()
		numUploads = numUploads + 1
		expectedSyncDiagnostics.summaryStr = string.format( "Catalog: Uploaded %d binaries to Oz.", numUploads )
		expectedSyncDiagnostics.uploadsByType = {
			catalog = {
				[ binaryType ] = { total = numUploads, [ "S3 Uploads" ] = numUploads }, total = numUploads, [ "S3 Uploads" ] = numUploads,
			},
		}
	end

	for i = 1, 3 do

		uploading:expectDataWithinTime( 5, true )
		incrementSyncExpectation()
		syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

		uploader:clearFromRecentlyUploaded( 'test', assetId, { type = binaryType, sha256 = sha256 }, true )
			-- after each attempt need to force clear from recently uploaded to avoid 10 second delay in retry

	end

	uploading:expectDataWithinTime( 5, true )

	incrementSyncExpectation()
	syncDiagnosticsObserver:expectDataWithinTime( 10, expectedSyncDiagnostics )

	-- should NOT have fallen back to Oz upload

	rxTest:expectEqual( false, uploader:DEBUG_getFallbackToOzUploadForOriginalsOnly() )

	-- now force fallback to Oz upload, as though this had been triggered by original
	-- binary upload failures, to show that this wil still not impact other binary uploads

	uploader:DEBUG_setFallbackToOzUploadForOriginalsOnly( true )

	-- clear from recent uploads to force binary to upload yet again

	for _, mock in ipairs( successMocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	uploader:clearFromRecentlyUploaded( 'test', assetId, { type = binaryType, sha256 = sha256 }, true )

	-- should upload again

	uploading:expectDataWithinTime( 5, true )

	incrementSyncExpectation()
	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	-- make it look like the metadatapuller got an update with the xmp_develop link in it

	if binaryType == 'xmp_develop' then

		rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, sha256 ) )

	elseif binaryType == 'aux' then

		rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, assetId, revisionId, localOnly.mask1AuxId, sha256 ) )

	end

	expectedSyncDiagnostics.documentsNeedingBinaryUpload = 0
	syncDiagnosticsObserver:expectDataWithinTime( 5, expectedSyncDiagnostics )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	completionStream:expectDataWithinTime( 2, assetId )
	completionStream:dispose()

	syncDiagnosticsObserver:dispose()

end

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

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

function WFCatBinaryUploaderTest.DISABLED_rxTestMockedDirectUpload_xmpDevelop_DoNotFallBackToOzUpload( rxTest )

	testMockedDirectUpload_DoNotFallBackToOzUpload( rxTest, 'xmp_develop' )

end

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

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

function WFCatBinaryUploaderTest.DISABLED_rxTestMockedDirectUpload_aux_DoNotFallBackToOzUpload( rxTest )

	testMockedDirectUpload_DoNotFallBackToOzUpload( rxTest, 'aux' )

end

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

function WFCatBinaryUploaderTest.rxTestDirectUploadAdd_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()

	-- make sure we have direct upload required '/rels/main_create/parts' link

	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )

			local hasMainCreatePartsLink = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/main_create/parts', 'href') ~= nil

			return hasMainCreatePartsLink

		end ) )
		:expectDataWithinTime( 60, true )
		:dispose()

	-- start uploading

	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )

			local masterOnOz = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') ~= nil

			return masterOnOz

		end ) )

	docObserver:expectDataWithinTime( 300, true )

end

--==============================================================================
-- @mark In App Contribute
--==============================================================================

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

local redactionSetsToTest = {
		{
			redactionsTable = { redactKeywords = true },
			redactionsHeader = 'keywords',
		},
		{
			redactionsTable = { redactLocation = true },
			redactionsHeader = 'location',
		},
		{
			redactionsTable = { redactRatingsReviews = true },
			redactionsHeader = 'ratings,reviews'
		},
		{
			redactionsTable = { redactKeywords = true, redactLocation = true },
			redactionsHeader = 'keywords,location'
		},
		{
			redactionsTable = { redactKeywords = true, redactRatingsReviews = true },
			redactionsHeader = 'keywords,ratings,reviews'
		},
		{
			redactionsTable = { redactLocation = true, redactRatingsReviews = true },
			redactionsHeader = 'location,ratings,reviews'
		},
		{
			redactionsTable = { redactKeywords = true, redactLocation = true, redactRatingsReviews = true },
			redactionsHeader = 'keywords,location,ratings,reviews'
		}
	}

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_IAC_contributor_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local setupOptions = {
		atomicDevelopSettings = true,
		inAppContributeRole = 'contributor',
	}
	local _, catalog, _, catalogId, _, _, uploader, spaceId, spaceFeedsManager =
		setupPusherPullerAndUploaderForNightly( rxTest, setupOptions )

	-- Add an asset to SWY album as contributor

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )
	assetDoc._spaceId = spaceId

	local albumAssetDoc = createAlbumAssetDocForSpaceAsset( rxTest, catalog, spaceId, assetId )
		-- asset will not show up in space feed unless both asset and album_asset documents are pushed

	rxTest:waitFor( catalog:docStore():bulkSetDocValue( { assetDoc, albumAssetDoc } ) )

	-- start up sync engine

	spaceFeedsManager:startSendingUpdates()

	local assetBinariesSyncedObservable = catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'exists') then
				return true
			end
		end )

	uploader:startContinuousUploads()

	rxTest:observe( assetBinariesSyncedObservable )
		:expectDataWithinTime(120, true)

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadAdd_InAppContribute( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with the /rels/master exists: true in it to let the
	-- uploader know there is no more work to do. For In App Contribute we don't always get a /rels/master
	-- href link so we look for the exists: true
	rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
		}
		:pluck(1, 1),
	0 )

end

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

function WFCatBinaryUploaderTest.rxTestMockUploadAdd_InAppContribute_redactions( rxTest )

	rxTest:setTimeLimit( 15 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )


	for i, redactionSet in ipairs( redactionSetsToTest ) do

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local revisionId = KSIdGenerator.uuidInLowercaseHex()

		catalogSpec:expectHttpRequest(
			WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath, '201', redactionSet.redactionsHeader )
		)

		rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath, nil, nil, nil, redactionSet.redactionsTable ) )

		uploader:startContinuousUploads()

		local uploading = rxTest:observe( uploader:isDoingWork() )
		uploading:expectDataWithinTime( 5, true )

		-- Make it look like metadatapuller got an update with the /rels/master exists: true in it to let the
		-- uploader know there is no more work to do. For In App Contribute we don't always get a /rels/master
		-- href link so we look for the exists: true
		rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

		local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
		observeBinariesInFlight:expectDataWithinTime( 5, 0 )

		uploading:expectDataWithinTime( 5, false )

		-- Make sure there's nothing in the binary upload table
		rxTest:expectSingleValue( catalog:indexStore():queryOnce {
				sql = "SELECT COUNT(*) FROM needsBinaryUpload",
			}
			:pluck(1, 1),
		0 )

		uploader:stopContinuousUploads()
	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_InAppContribute( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath, nil, sourceAssetId, sourceRevisionId ) )

	watchForXmpAndMasterUploadsForIAC( rxTest, catalog, catalogSpec, uploader, spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
		}
		:pluck(1, 1),
	0 )

end

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

function WFCatBinaryUploaderTest.rxTestMockUploadCopy_InAppContribute_redactions( rxTest )

	rxTest:setTimeLimit( 15 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	for i, redactionSet in ipairs( redactionSetsToTest ) do

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local revisionId = KSIdGenerator.uuidInLowercaseHex()

		rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath, nil, sourceAssetId, sourceRevisionId, redactionSet.redactionsTable ) )

		watchForXmpAndMasterUploadsForIAC( rxTest, catalog, catalogSpec, uploader, spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, '201', redactionSet.redactionsHeader )

	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpDevelop_InAppContribute( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId2 = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
	end )

	local xmpDevelopFileSize = KSData.length( xmpDevelopSettings )

	local xmpDevelopSha256 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings ) )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponseForInAppContribute( spaceId, assetId, revisionId2, xmpDevelopFilePath ),
		WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId2, originalFilePath ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	-- no source asset listings here mean that we're going to need to be asking Oz to extract the metadata from the master
	rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_newRevId = revisionId2,
		userUpdated = KSDate.timeInIso8601FormatNearestSecond(), -- this is just to ensure that the applyOverlay succeeds and sets a new revision
		_serverProvidedMetadata = {
			revision_ids = {
				revisionId,
				revisionId2
			}
		}
	} )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 5, 1 )

	-- Make it look like metadatapuller got an update with xmp_develop. For In App Contribute we don't
	-- always get a /rels/xmp/develop link so the uploader should look for exists: true to determine whether they're on Oz
	rxTest:waitFor( addXmpDevelopExistsToDoc( catalog, catalogId, assetId, xmpDevelopSha256 ) )

	httpRequestsProcessed:expectDataWithinTime( 5, 2 )
	-- Make it look like metadatapuller got an update with rendition links in it to let the
	-- uploader know there is no more work to do. For In App Contribute we don't always get a
	-- /rels/master link so the uploader should look for exists: true to determine whether
	-- they're on Oz
	rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
		}
		:pluck(1, 1),
	0 )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_proxyOnly_InAppContribute( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local proxyId = KSIdGenerator.uuidInLowercaseHex()

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local proxyFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCreateProxyResponseForInAppContribute( spaceId, assetId, revisionId, proxyId, proxyFilePath ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addIACWillNotUploadMasterDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, proxyFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with the /rels/proxy_type/2560 exists: true in it to let the
	-- uploader know there is no more work to do. For In App Contribute we don't always get a /rels/proxy_type/2560
	-- href link so we look for the exists: true
	rxTest:waitFor( addProxyExistsToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
			quiesceUntilUpToDate = true
		}
		:pluck(1, 1),
	0 )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_proxyOnly_InAppContribute_redactions( rxTest )

	rxTest:setTimeLimit( 15 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local proxyFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	uploader:startContinuousUploads()

	for i, redactionSet in ipairs( redactionSetsToTest ) do

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local revisionId = KSIdGenerator.uuidInLowercaseHex()
		local proxyId = KSIdGenerator.uuidInLowercaseHex()

		catalogSpec:expectHttpRequest(
			WFCatBinaryUploaderTestUtils.generateCreateProxyResponseForInAppContribute( spaceId, assetId, revisionId, proxyId, proxyFilePath, '201', redactionSet.redactionsHeader )
		)

		rxTest:waitFor( addIACWillNotUploadMasterDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, proxyFilePath, nil, nil, nil, redactionSet.redactionsTable ) )

		local uploading = rxTest:observe( uploader:isDoingWork() )
		uploading:expectDataWithinTime( 5, true )

		-- Make it look like metadatapuller got an update with the /rels/proxy_type/2560 exists: true in it to let the
		-- uploader know there is no more work to do. For In App Contribute we don't always get a /rels/proxy_type/2560
		-- href link so we look for the exists: true
		rxTest:waitFor( addProxyExistsToDoc( catalog, catalogId, assetId, revisionId ) )

		local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
		observeBinariesInFlight:expectDataWithinTime( 5, 0 )

		uploading:expectDataWithinTime( 5, false )

	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_proxyOnlyCopy_InAppContribute( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local sourceProxyId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local proxyId = KSIdGenerator.uuidInLowercaseHex()

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local proxyFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId ),
		WFCatBinaryUploaderTestUtils.generateCopyProxyResponseForInAppContribute( spaceId, assetId, revisionId, proxyId, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId )
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	rxTest:waitFor( addIACWillNotUploadMasterDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, proxyFilePath, sourceAssetId, sourceRevisionId, sourceProxyId ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- Make it look like metadatapuller got an update with the /rels/xmp exists: true so that we know we can
	-- upload the proxy now
	rxTest:waitFor( addXmpExistsToDoc( catalog, catalogId, assetId, revisionId ) )

	httpRequestsProcessed:expectDataWithinTime( 2, 2 )

	-- Make it look like metadatapuller got an update with the /rels/proxy_type/2560 exists: true in it to let the
	-- uploader know there is no more work to do. For In App Contribute we don't always get a /rels/proxy_type/2560
	-- href link so we look for the exists: true
	rxTest:waitFor( addProxyExistsToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
			quiesceUntilUpToDate = true
		}
		:pluck(1, 1),
	0 )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_proxyOnlyCopy_InAppContribute_redactions( rxTest )

	rxTest:setTimeLimit( 15 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local sourceProxyId = KSIdGenerator.uuidInLowercaseHex()

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local proxyFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	uploader:startContinuousUploads()

	local expectHttpRequestsCount = 0

	for i, redactionSet in ipairs( redactionSetsToTest ) do

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local revisionId = KSIdGenerator.uuidInLowercaseHex()
		local proxyId = KSIdGenerator.uuidInLowercaseHex()

		catalogSpec:expectHttpRequest(
			WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, '201', redactionSet.redactionsHeader )
		)

		catalogSpec:expectHttpRequest(
			WFCatBinaryUploaderTestUtils.generateCopyProxyResponseForInAppContribute( spaceId, assetId, revisionId, proxyId, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId, '201', redactionSet.redactionsHeader )
		)

		rxTest:waitFor( addIACWillNotUploadMasterDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, proxyFilePath, sourceAssetId, sourceRevisionId, sourceProxyId, redactionSet.redactionsTable ) )

		local uploading = rxTest:observe( uploader:isDoingWork() )
		uploading:expectDataWithinTime( 5, true )

		expectHttpRequestsCount = expectHttpRequestsCount + 1

		local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )
		httpRequestsProcessed:expectDataWithinTime( 2, expectHttpRequestsCount )

		-- Make it look like metadatapuller got an update with the /rels/xmp exists: true so that we know we can
		-- upload the proxy now
		rxTest:waitFor( addXmpExistsToDoc( catalog, catalogId, assetId, revisionId ) )

		expectHttpRequestsCount = expectHttpRequestsCount + 1
		httpRequestsProcessed:expectDataWithinTime( 2, expectHttpRequestsCount )

		-- Make it look like metadatapuller got an update with the /rels/proxy_type/2560 exists: true in it to let the
		-- uploader know there is no more work to do. For In App Contribute we don't always get a /rels/proxy_type/2560
		-- href link so we look for the exists: true
		rxTest:waitFor( addProxyExistsToDoc( catalog, catalogId, assetId, revisionId ) )

		local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
		observeBinariesInFlight:expectDataWithinTime( 5, 0 )

		uploading:expectDataWithinTime( 5, false )

		-- Make sure there's nothing in the binary upload table
		rxTest:expectSingleValue( catalog:indexStore():queryOnce {
				sql = "SELECT COUNT(*) FROM needsBinaryUpload",
				quiesceUntilUpToDate = true
			}
			:pluck(1, 1),
		0 )

	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpCopy_InAppContribute( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath, nil, sourceAssetId, sourceRevisionId ) )

	-- make it look like the master file is already there so we don't try to upload it now, we're only concerned about the xmp file
	rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

	catalogSpec:expectHttpRequest(
		WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId )
	)

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	rxTest:waitFor( addXmpExistsToDoc( catalog, catalogId, assetId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
		}
		:pluck(1, 1),
	0 )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpCopy_InAppContribute_redactions( rxTest )

	rxTest:setTimeLimit( 20 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local sourceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local sourceRevisionId = KSIdGenerator.uuidInLowercaseHex()

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	local expectHttpRequestsCount = 0
	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	uploader:startContinuousUploads()

	for i, redactionSet in ipairs( redactionSetsToTest ) do

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local revisionId = KSIdGenerator.uuidInLowercaseHex()

		rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath, nil, sourceAssetId, sourceRevisionId, redactionSet.redactionsTable ) )

		catalogSpec:expectHttpRequest(
			WFCatBinaryUploaderTestUtils.generateCopyXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, nil, redactionSet.redactionsHeader )
	)

		-- make it look like the master file is already there so we don't try to upload it now, we're only concerned about the xmp file
		rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

		local uploading = rxTest:observe( uploader:isDoingWork() )
		uploading:expectDataWithinTime( 5, true )

		expectHttpRequestsCount = expectHttpRequestsCount + 1
		httpRequestsProcessed:expectDataWithinTime( expectHttpRequestsCount )

		rxTest:waitFor( addXmpExistsToDoc( catalog, catalogId, assetId ) )

		local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
		observeBinariesInFlight:expectDataWithinTime( 5, 0 )

		uploading:expectDataWithinTime( 5, false )

		-- Make sure there's nothing in the binary upload table
		rxTest:expectSingleValue( catalog:indexStore():queryOnce {
				sql = "SELECT COUNT(*) FROM needsBinaryUpload",
			}
			:pluck(1, 1),
		0 )

	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUpload_xmpDevelop_InAppContribute_Editor( rxTest )

	rxTest:setTimeLimit( 300 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId2 = KSIdGenerator.uuidInLowercaseHex()
	local revisionId3 = KSIdGenerator.uuidInLowercaseHex()
	local revisionId4 = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	-- xmp_develop 1
	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	local xmpDevelopFileSize = KSData.length( xmpDevelopSettings )

	local xmpDevelopSha256 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings ) )

	-- xmp_develop 2
	local xmpDevelopFilePath2 = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings2 = KSData.make( '<xmp2 />' )
	KSData.saveToFile( xmpDevelopSettings2, xmpDevelopFilePath2 )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
		WFFileUtils.delete( xmpDevelopFilePath2 )
	end )

	local xmpDevelopFileSize2 = KSData.length( xmpDevelopSettings2 )

	local xmpDevelopSha256_2 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings2 ) )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponseForInAppContribute( spaceId, assetId, revisionId2, xmpDevelopFilePath ),
		WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId2, originalFilePath ),
		WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponseForInAppContribute( spaceId, assetId, revisionId4, xmpDevelopFilePath2 ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	-- no source asset listings here mean that we're going to need to be asking Oz to extract the metadata from the master
	rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_newRevId = revisionId2,
		userUpdated = KSDate.timeInIso8601FormatNearestSecond(), -- this is just to ensure that the applyOverlay succeeds and sets a new revision
		_serverProvidedMetadata = {
			revision_ids = {
				revisionId,
				revisionId2
			}
		}
	} )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 20, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 20, 1 )

	-- Make it look like metadatapuller got an update with xmp_develop. For In App Contribute we don't
	-- always get a /rels/xmp/develop link so the uploader should look for exists: true to determine whether they're on Oz
	rxTest:waitFor( addXmpDevelopExistsToDoc( catalog, catalogId, assetId, xmpDevelopSha256 ) )

	httpRequestsProcessed:expectDataWithinTime( 20, 2 )
	-- Make it look like metadatapuller got an update with rendition links in it to let the
	-- uploader know there is no more work to do. For In App Contribute we don't always get a
	-- /rels/master link so the uploader should look for exists: true to determine whether
	-- they're on Oz
	rxTest:waitFor( addMasterExistsToDoc( catalog, catalogId, assetId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 20, 0 )

	uploading:expectDataWithinTime( 20, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
		}
		:pluck(1, 1),
	0 )

	uploader:stopContinuousUploads()

	-- Update xmp_develop and make sure it's uploaded

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256_2, xmpDevelopFilePath2, xmpDevelopFileSize2 ) )

	-- asset is not editable, so the new xmp_develop is not uploaded

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_newRevId = revisionId3,
		userUpdated = KSDate.timeInIso8601FormatNearestSecond(), -- this is just to ensure that the applyOverlay succeeds and sets a new revision
		_serverProvidedMetadata = {
			revision_ids = {
				revisionId,
				revisionId2,
				revisionId3
			}
		}
	} )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 20, false )

	-- asset is editable, make sure new xmp_develop is uploaded

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId3 ) )

	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		_newRevId = revisionId4,
		userUpdated = KSDate.timeInIso8601FormatNearestSecond(), -- this is just to ensure that the applyOverlay succeeds and sets a new revision
		_serverProvidedMetadata = {
			editable = true,
			revision_ids = {
				revisionId,
				revisionId2,
				revisionId3,
				revisionId4
			}
		}
	} )

	uploading:expectDataWithinTime( 20, true )

	-- wait for the third PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 20, 3 )

	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId4, xmpDevelopSha256_2 ) )

	observeBinariesInFlight:expectDataWithinTime( 20, 0 )

	uploading:expectDataWithinTime( 20, false )

	-- Make sure there's nothing in the binary upload table
	rxTest:expectSingleValue( catalog:indexStore():queryOnce {
			sql = "SELECT COUNT(*) FROM needsBinaryUpload",
		}
		:pluck(1, 1),
	0 )

end

--==============================================================================
-- @mark Versions
--==============================================================================

--[[

	\brief Run version binary upload test without direct upload of xmp develop

	\param rxTest (WFRxTestCaseRunner)
	\param spaceId (string) - uuid of space. If provided run tests for in-app
		contribute workflow, if not then run test for catalog workflow.
	\param atomicDevelopSettings (boolean) - if true then simulate edge case in
		which version metadata synced to Oz (would have been in a client with
		ADS disabled) and then binary sync was attempted with ADS enabled. (bug WF-2429)
	\param missingCreateLink (boolean) - if true then simulate edge case in
		which version is create by another device or Oz, but the xmp_develop link is not
		copied yet, and there is no create link. (bug WF-2590)

]]
local function testMockedUploadVersion( rxTest, spaceId, atomicDevelopSettings, missingCreateLink )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local setupOptions = {
		atomicDevelopSettings = atomicDevelopSettings,
	}
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, setupOptions )

	if spaceId then

		-- for space tests need to create valid editor space_user document as WFCatVersions
		-- code checks for this

		local albumId = KSIdGenerator.uuidInLowercaseHex()
		local spaceUserId = KSIdGenerator.uuidInLowercaseHex()

		rxTest:waitFor( catalog:docStore():setDocValue {
			_id = spaceId,
			type = 'space',
			subtype = 'shared',
			_serverProvidedMetadata = {
				albums = {
					{
						id = albumId,
						space_album = { id = KSIdGenerator.uuidInLowercaseHex(), }
					},
				},
				owner = {
					id = KSIdGenerator.uuidInLowercaseHex(),
				},
				this_space_user = {
					id = spaceUserId,
				},
			},
		} )

		rxTest:waitFor( catalog:docStore():setDocValue {
			_id = spaceUserId,
			_spaceId = spaceId,
			type = 'space_user',
			role = 'editor',
			status = 'accepted',
			space = {
				id = spaceId,
			},
			user = {
				id = KSIdGenerator.uuidInLowercaseHex(),
			},
		} )

	end


	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
	end )

	local xmpDevelopFileSize = KSData.length( xmpDevelopSettings )
	local xmpDevelopSha256 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings ) )

	-- create an asset and make it look like it's fully uploaded
	local assetOptions = {
		xmpDevelop = { sha256 = xmpDevelopSha256, filePath = xmpDevelopFilePath, fileSize = xmpDevelopFileSize },
		spaceId = spaceId,
		editable = true,
	}
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) )
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local versionId, versionRevisionId
	rxTest:waitFor( WFCatVersions.createVersionForAsset( catalog, assetId, false, 'Simple Version' )
						:flatMap( function( id )
							versionId = id
							return catalog:docStore():docValue( id )
								:map( function( docValue )
									versionRevisionId = docValue._revId
								end )
						end ) )

	rxTest:waitFor( addCreateLinksToVersionDoc( catalog, catalogId, versionId, versionRevisionId, missingCreateLink ) )

	local versionXmpDevelopFilePath
	rxTest:waitFor( catalog:docStore():docValue( versionId )
					:map( function( docValue )
						versionXmpDevelopFilePath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )
						if WFPlatformInfo.isiOS then
							local catalogBasePath = OzFilePaths.getCatalogBasePath( catalogId )
							-- mobile stores relative paths, so turn this into a full path for mobile
							versionXmpDevelopFilePath = KSPathUtils.child( catalogBasePath, versionXmpDevelopFilePath )
						elseif WFPlatformInfo.isAndroid then
							versionXmpDevelopFilePath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'carouselDocuments' ), versionXmpDevelopFilePath )
						end
						return true
					end ) )
					
	if missingCreateLink then
		-- If create link is missing, version xmp_develop is not ready to upload
		
		rxTest:expectSingleValue( catalog:indexStore():queryOnce({
			sql = "SELECT isReadyToUpload FROM needsBinaryUpload WHERE docId = ?",
			singleColumnQuery = true,
			versionId
		}), { 0 } )
			
		return
	end

	catalogSpec:expectHttpRequest( WFCatBinaryUploaderTestUtils.generateUploadXmpDevelopFileResponseForVersion( catalogId, versionId, versionRevisionId, versionXmpDevelopFilePath ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	-- make it look like the metadatapuller get an update with an xmp develop link so we know we're don
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, versionId, versionRevisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion( rxTest )

	testMockedUploadVersion( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_metadataSyncedFirst_atomicDevelopSettings( rxTest )

	-- for WF-2429
	local spaceId = nil
	local atomicDevelopSettings = true
	testMockedUploadVersion( rxTest, spaceId, atomicDevelopSettings )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_IAC( rxTest )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedUploadVersion( rxTest, spaceId )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_IAC_metadataSyncedFirst_atomicDevelopSettings( rxTest )

	-- for WF-2429
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local atomicDevelopSettings = true
	testMockedUploadVersion( rxTest, spaceId, atomicDevelopSettings )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_IAC_metadataSyncedFirst_missingCreateLink_atomicDevelopSettings( rxTest )

	-- for WF-2590
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local atomicDevelopSettings = true
	local missingCreateLink = true
	testMockedUploadVersion( rxTest, spaceId, atomicDevelopSettings, missingCreateLink )

end

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

--[[

	\brief Run version binary upload test with atomicDevelopSettings enabled

	\param rxTest (WFRxTestCaseRunner)
	\param spaceId (string) - uuid of space. If provided run tests for in-app
		contribute workflow, if not then run test for catalog workflow.
	\param missingTemplateLink (boolean or string) - if non-nil then simulate scenario
		in which asset is missing the direct upload xmp_develop for version template
		link so needs to be reloaded to acquire it. If set to string "error" mock
		attempt to reload asset for template fails with an error.
]]
local function testMockedUploadVersion_atomicDevelopSettings( rxTest, spaceId, missingTemplateLink )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local setupOptions = {
		atomicDevelopSettings = true
	}
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {}, setupOptions )

	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
	end )

	local xmpDevelopFileSize = KSData.length( xmpDevelopSettings )
	local xmpDevelopSha256 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings ) )

	-- create an asset and make it look like it's fully uploaded

	local assetOptions = {
		xmpDevelop = { sha256 = xmpDevelopSha256, filePath = xmpDevelopFilePath, fileSize = xmpDevelopFileSize },
		spaceId = spaceId,
		editable = true,
	}
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) )
	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local mocks = { }

	local errorResponse
	if missingTemplateLink then

		local assetDoc = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )
		local links = derefOpt( assetDoc, '_serverProvidedMetadata', 'links' )

		local removeTemplateOverlay = {
			_id = assetId,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/versions/xmp_develop_create/parts' ] = { _delete = true },
				}
			}
		}

		rxTest:waitFor( catalog:docStore():applyOverlay( removeTemplateOverlay ) )
			-- remove the direct upload version template link

		errorResponse = ( missingTemplateLink == 'error' )
		table.insert( mocks, WFCatBinaryUploaderTestUtils.generateReloadAssetForTemplateLinkMock( catalog, assetId, links, errorResponse ) )
			-- create mock for successfully (or not) reloading the asset from Oz

		if errorResponse then

			table.insert( mocks, KSTableUtils.deepCopy( mocks[ 1 ] ) )
			table.insert( mocks, KSTableUtils.deepCopy( mocks[ 1 ] ) )
				-- in error case there will be three retries, so mock that

		end

	end

	-- create a version that looks like it has not synced to Oz at all.

	local versionId = KSIdGenerator.uuidInLowercaseHex()
	local versionDoc, versionLocalOnly = WFCatVersionTestUtils.createVersionDocument( rxTest, catalog, versionId )
	local versionRevId = versionDoc._newRevId
	versionDoc.asset.id = assetId
	versionDoc._spaceId = spaceId
	versionDoc._serverProvidedMetadata = nil
	rxTest:waitFor( catalog:docStore():setDocValue( versionDoc ) )

	if not errorResponse then

		local options = { atomicDevelopSettings = true }
		WFCatBinaryUploaderTestUtils.getXmpDevelopDirectUploadMocksForVersion( catalog, versionDoc, versionLocalOnly, mocks, options )

	end

	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	if errorResponse then

		-- because of the error the xmp camera raw for the version will not be uploadable
		uploading:expectDataWithinTime( 2, false )

		-- expect ResourceNotFoundError to be logged three times
		-- one for each retry.

		rxTest:observe( Observable.timer( .1, .1, WFRxScheduler.sharedScheduler() )
			:map( function()

				local log = getDiagnosticLogErrorsIgnoringAccountError( uploader )
				local count = -1
				local i = -1

				while i do
					count = count + 1
					i = string.find( log, 'ResourceNotFoundError', i + 1 )
				end

				return count

			end ) )
			:expectDataWithinTime( 5, 3 )
			:dispose()

		-- still nothing is uploadable
		uploading:expectDataWithinTime( 2, false )

	else

		uploading:expectDataWithinTime( 5, true )

		-- wait for all three requests to upload version xmp develop to complete
		httpRequestsProcessed:expectDataWithinTime( 2, 3 )

		-- make it look like the metadatapuller get an update with an xmp develop link so we know we're done
		rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, versionId, versionRevId ) )

		local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
		observeBinariesInFlight:expectDataWithinTime( 5, 0 )

		uploading:expectDataWithinTime( 5, false )

		-- Make sure we didn't run into any unexpected errors.
		local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
		rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

		if missingTemplateLink then

			-- check that links overlay correctly removed /rels/proxy_create link and added the foo = bar link

			local assetDoc = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )
			local links = derefOpt( assetDoc, '_serverProvidedMetadata', 'links' )

			rxTest:expectEqual( nil, links[ '/rels/proxy_create' ] )
			rxTest:expectEqual( { href = '/bar' }, links.foo )

		end

	end

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_atomicDevelopSettings( rxTest )

	testMockedUploadVersion_atomicDevelopSettings( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_missingTemplateLink_atomicDevelopSettings( rxTest )

	local missingTemplateLink = true
	testMockedUploadVersion_atomicDevelopSettings( rxTest, nil, missingTemplateLink )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_missingTemplateLink_error_atomicDevelopSettings( rxTest )

	local missingTemplateLink = 'error'
	testMockedUploadVersion_atomicDevelopSettings( rxTest, nil, missingTemplateLink )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_IAC_atomicDevelopSettings( rxTest )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	testMockedUploadVersion_atomicDevelopSettings( rxTest, spaceId )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_IAC_missingTemplateLink_atomicDevelopSettings( rxTest )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local missingTemplateLink = true
	testMockedUploadVersion_atomicDevelopSettings( rxTest, spaceId, missingTemplateLink )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_IAC_missingTemplateLink_error_atomicDevelopSettings( rxTest )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local missingTemplateLink = 'error'
	testMockedUploadVersion_atomicDevelopSettings( rxTest, spaceId, missingTemplateLink )

end

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

function WFCatBinaryUploaderTest.rxTestGetStandardBinaryUploadCandidatesStream( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	local standardStream = rxTest:observe( uploader:getStandardBinaryUploadCandidatesStream()
		:unpack()
		:map(function( row )
			if row then
				local docId = row[1]
				local binaryType = row[4]
				return { docId, binaryType }
			else
				return {}
			end
		end)
	)

	standardStream:expectDataWithinTime( 5, {} )

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	-- Add a bare asset and expect a master upload to be created
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	standardStream:expectDataWithinTime( 5, {assetId, "master"} )

	-- Add xmp data to the existing asset and expect an xmp upload to be created

	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
	end )

	local xmpDevelopFileSize = KSData.length( xmpDevelopSettings )

	local xmpDevelopSha256 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings ) )

	rxTest:waitFor( addCreateLinksToDoc( catalog, catalogId, assetId, revisionId ) )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

	-- Make it look like the latest revision is synced to Oz

	local newRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = assetId,
		_newRevId = newRevId,
		_serverProvidedMetadata = {
			revision_ids = {
				newRevId
			}
		}
	} ) )

	standardStream:expectDataWithinTime( 5, { assetId, "xmp_develop" } )

	local carouselDocuments = KSPathUtils.getStandardFilePath( 'carouselDocuments' )

	-- Add mask to asset

	local auxId = KSIdGenerator.uuidInLowercaseHex()

	local auxDigest = string.upper( WFCrypto.MD5.digest( KSIdGenerator.uuidInLowercaseHex() ) )

	local auxType = "mask"

	local auxBinary = KSData.make( auxDigest )

	local auxFilePath = WFCatAssetAux.calculatePathUsingDigest( carouselDocuments, auxDigest )

	local auxParentPath = KSPathUtils.parent( auxFilePath )

	WFFileUtils.mkdir( auxParentPath )

	KSData.saveToFile( auxBinary, auxFilePath )

	local auxFileSize = WFFileUtils.fileAttributes( auxFilePath ).fileSize

	local auxSha256 = WFCrypto.digestFile( auxFilePath, 'SHA256' )

	-- Adds file to localonly section and sets up the develop section

	if WFPlatformInfo.isAndroid then
		-- Android doesn't store full file path here, so remove the beginning if we have a full file path
		auxFilePath = string.replace( auxFilePath, carouselDocuments .. '/', '' )
	end

	local auxParamsList = {{ id = auxId, sha256 = auxSha256, filePath = auxFilePath, fileSize = auxFileSize, digest = auxDigest, type = auxType }}

	rxTest:waitFor(
		addAuxReferenceToDocument( catalog, assetId, auxParamsList )
	)

	rxTest:waitFor(
		addAuxCreateLinksToDoc( catalog, catalogId,  assetId )
	)

	-- Make it look like the latest revision is synced to Oz

	newRevId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = assetId,
		_newRevId = newRevId,
		_serverProvidedMetadata = {
			revision_ids = {
				newRevId
			}
		}
	} ) )

	-- Once an aux has been referenced, it should be uploaded

	standardStream:expectDataWithinTime( 5, { assetId, 'aux' } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadVersion_WF_1639( rxTest )

	-- [jandrew 2022-03-16]: The following test was put into place to repro
	-- https://jira.corp.adobe.com/browse/WF-1639. We were seeing repeated attempts
	-- to upload xmp_develop files for version documents that had been deleted
	-- on Oz. Now when we detect this scenario, we delete the local version.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	-- Create an asset

	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
	end )

	local xmpDevelopFileSize = KSData.length( xmpDevelopSettings )
	local xmpDevelopSha256 = WFCrypto.SHA256.digest( tostring( xmpDevelopSettings ) )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	rxTest:waitFor( addXmpDevelopSettingsToDocument(
			catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

	rxTest:waitFor( addXmpDevelopLinkToDoc( catalog, catalogId, assetId, revisionId, xmpDevelopSha256 ) )

	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	-- Create the version from the asset

	local versionId, versionRevisionId

	rxTest:waitFor( WFCatVersions.createVersionForAsset( catalog, assetId, false, 'Simple Version' )

		:flatMap( function( id )

			versionId = id

			return catalog:docStore():docValue( id )
				:map( function( docValue )
					versionRevisionId = docValue._revId
				end )

		end ) )

	rxTest:waitFor( addCreateLinksToVersionDoc( catalog, catalogId, versionId, versionRevisionId ) )

	-- Generate a mock 404 request from Oz

	local versionXmpDevelopFilePath = rxTest:getFirstValue( catalog:docStore():docValue( versionId )

		:map( function( docValue )

			local versionXmpDevelopFilePath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )

			if WFPlatformInfo.isiOS then

				local catalogBasePath = OzFilePaths.getCatalogBasePath( catalogId )
				versionXmpDevelopFilePath = KSPathUtils.child( catalogBasePath, versionXmpDevelopFilePath )

			elseif WFPlatformInfo.isAndroid then

				versionXmpDevelopFilePath = KSPathUtils.child(
						KSPathUtils.getStandardFilePath( 'carouselDocuments' ), versionXmpDevelopFilePath )

			end

			return versionXmpDevelopFilePath

		end ) )

	catalogSpec:expectHttpRequest(
		WFCatBinaryUploaderTestUtils.generateUploadXmpDevelop404ResponseForVersion( catalogId, versionId, versionRevisionId, versionXmpDevelopFilePath )
	)

	catalogSpec:expectHttpRequest(
		WFCatBinaryUploaderTestUtils.generate404ResponseForGetVersion( catalogId, versionId, versionRevisionId, versionXmpDevelopFilePath )
	)

	-- Start the upload

	local docObserver = rxTest:observe( catalog:docStore()
		:ongoingDocValue( versionId )
		:pluck( '_empty' ) )

	docObserver:expectDataWithinTime( 5, nil )

	uploader:startContinuousUploads()

	-- Wait for Version to be deleted.

	docObserver:expectDataWithinTime( 5, true )

	docObserver:dispose()

	uploader:stopContinuousUploads()

end

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

local function testUploadVersion_testNightly( rxTest, atomicDevelopSettings, inAppContributeRole, missingTemplateLink, metadataSyncedFirst )

	rxTest:setTimeLimit( 360 )

	local setupOptions = {
		atomicDevelopSettings = atomicDevelopSettings and not metadataSyncedFirst,
			-- need to disable ADS in the pusher to get metadata to sync first
		inAppContributeRole = inAppContributeRole,
	}
	local _, catalog, _, catalogId, pusher, puller, uploader, spaceId, spaceFeedsManager =
		setupPusherPullerAndUploaderForNightly( rxTest, setupOptions )

	local assetId, _, _, _, assetDocWithExternalDevelopSettings = setupAssetForNightly( rxTest, catalogId )
	
	local docsToPush = { assetDocWithExternalDevelopSettings }
	if spaceId then
		assetDocWithExternalDevelopSettings._spaceId = spaceId
		docsToPush[ #docsToPush + 1 ] = createAlbumAssetDocForSpaceAsset( rxTest, catalog, spaceId, assetId )
			-- asset will not show up in space feed unless both asset and album_asset documents are pushed
	end
	rxTest:waitFor( catalog:docStore():bulkSetDocValue( docsToPush ) )

	local testTempFolder = WFTestFileUtils.createTestFolder( rxTest )
	local xmpDevelopFilePath = KSPathUtils.child( testTempFolder, 'small.XMP' )
	local xmpDevelopFileSize = generateSidecarFile( rxTest, xmpDevelopFilePath )
	local xmpDevelopSha256 = WFCrypto.digestFile( xmpDevelopFilePath, 'SHA256' )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

	-- start up sync engine

	if spaceFeedsManager then
		spaceFeedsManager:startSendingUpdates()
	else
		rxTest:waitFor( pusher:sendUpdatesContinuously() )
		puller:getUpdatesContinuously()
	end

	local assetBinariesSyncedObservable = catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') and
			derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' ) then
				return true
			end
		end )

	if not metadataSyncedFirst then

		uploader:startContinuousUploads()

		rxTest:observe( assetBinariesSyncedObservable )
			:expectDataWithinTime(120, true)

	end

	if missingTemplateLink then

		-- remove the xmp_develop_create link for version from asset metadata
		-- to artificially simulate scenario where asset has last synced from Oz
		-- long ago, before atomic develop settings feature existed.

		local removeTemplateOverlay = {
			_id = assetId,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/versions/xmp_develop_create/parts' ] = { _delete = true },
				}
			}
		}

		rxTest:waitFor( catalog:docStore():applyOverlay( removeTemplateOverlay ) )

	end

	local versionId, versionRevisionId
	rxTest:waitFor( WFCatVersions.createVersionForAsset( catalog, assetId, false, 'Simple Version' )
		:flatMap( function( id )
			versionId = id
			return catalog:docStore():docValue( id )
				:map( function( docValue )
					versionRevisionId = docValue._revId
				end )
		end ) )

	local versionObserver

	if metadataSyncedFirst then

		-- wait to get confirmation that metadata synced to Oz and THEN start binary uploader
		-- it should figure out that version xmp_develop should be uploaded via old method
		-- not via direct upload even though ADS is enabled.

		versionObserver = rxTest:observe( catalog:docStore():ongoingDocValue( versionId )
			:map( function( docValue )
				if derefOpt( docValue, '_serverProvidedMetadata' ) then
					return true
				end
			end ) )
		versionObserver:expectDataWithinTime( 120, true )
		versionObserver:dispose()

		-- need to enable ADS in the binary uploader to test the ADS workflow
		-- where version metadata synced first when a different version of the
		-- client had ADS disabled

		uploader:TEST_getCapabilitiesCache():setCapabilitiesForTests {
			[ 'WFCatBinaryUploader.atomicDevelopSettings' ] = true,
		}

		uploader:startContinuousUploads()

		rxTest:observe( assetBinariesSyncedObservable )
			:expectDataWithinTime(120, true)

	end

	versionObserver = rxTest:observe( catalog:docStore():ongoingDocValue( versionId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' ) then
				return true
			end
		end ) )

	versionObserver:expectDataWithinTime( 120, true )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_testNightly( rxTest )

	testUploadVersion_testNightly( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_atomicDevelopSettings_testNightly( rxTest )

	local atomicDevelopSettings = true
	testUploadVersion_testNightly( rxTest, atomicDevelopSettings )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_missingTemplateLink_testNightly( rxTest )

	local atomicDevelopSettings = true
	local inAppContributeRole = nil
	local missingTemplateLink = true
	testUploadVersion_testNightly( rxTest, atomicDevelopSettings, inAppContributeRole, missingTemplateLink )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_atomicDevelopSettings_metadataSyncedFirst_testNightly( rxTest )

	-- story WF-2429 WRT

	local atomicDevelopSettings = true
	local inAppContributeRole = nil
	local missingTemplateLink = nil
	local metadataSyncedFirst = true
	testUploadVersion_testNightly( rxTest, atomicDevelopSettings, inAppContributeRole, missingTemplateLink, metadataSyncedFirst )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_atomicDevelopSettings_IAC_testNightly( rxTest )

	local atomicDevelopSettings = true
	local inAppContributeRole = 'editor'
	testUploadVersion_testNightly( rxTest, atomicDevelopSettings, inAppContributeRole )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_missingTemplateLink_IAC_testNightly( rxTest )

	local atomicDevelopSettings = true
	local inAppContributeRole = 'editor'
	local missingTemplateLink = true
	testUploadVersion_testNightly( rxTest, atomicDevelopSettings, inAppContributeRole, missingTemplateLink )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_atomicDevelopSettings_metadataSyncedFirst_IAC_testNightly( rxTest )

	-- story WF-2429 WRT

	local atomicDevelopSettings = true
	local inAppContributeRole = 'editor'
	local missingTemplateLink = nil
	local metadataSyncedFirst = true
	testUploadVersion_testNightly( rxTest, atomicDevelopSettings, inAppContributeRole, missingTemplateLink, metadataSyncedFirst )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVersion_WF2608_testNightly( rxTest )

	-- https://jira.corp.adobe.com/browse/WF-2608
	-- This test verifies that we do not try to reload the parents Asset's links
	-- when the asset has not been uploaded to Oz. I encountered this bug in LrD,
	-- and although it was self-healing and minor in nature, it was worth fixing.

	rxTest:setTimeLimit( 360 )

	local setupOptions = {
		atomicDevelopSettings = true
	}

	local _, catalog, _, catalogId, pusher, puller, uploader =
		setupPusherPullerAndUploaderForNightly( rxTest, setupOptions )

	-- Create an asset

	local assetId, _, _, _, assetDocWithExternalDevelopSettings = setupAssetForNightly( rxTest, catalogId )
	assetDocWithExternalDevelopSettings._labels = { _dontsendtooz = true }

	local docsToPush = { assetDocWithExternalDevelopSettings }
	rxTest:waitFor( catalog:docStore():bulkSetDocValue( docsToPush ) )

	local testTempFolder = WFTestFileUtils.createTestFolder( rxTest )
	local xmpDevelopFilePath = KSPathUtils.child( testTempFolder, 'small.XMP' )
	local xmpDevelopFileSize = generateSidecarFile( rxTest, xmpDevelopFilePath )
	local xmpDevelopSha256 = WFCrypto.digestFile( xmpDevelopFilePath, 'SHA256' )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )
	
	-- Now create the Version.
	
	local versionId, versionRevisionId
	rxTest:waitFor( WFCatVersions.createVersionForAsset( catalog, assetId, false, 'Simple Version' )
		:flatMap( function( id )
			versionId = id
			return catalog:docStore():docValue( id )
				:map( function( docValue )
					versionRevisionId = docValue._revId
				end )
		end ) )

	-- I'm not sure if this is how WF-2608 occurs in the wild, but it's the only
	-- way I could reproduce with a unit test:
	-- Create a new revision of the asset that has no develop settings and a
	-- local original error. This prevents any binaries from being emitted into
	-- the needsBinaryUpload table for the asset. However, the Version *does*
	-- have binaries that are are in needsBinaryUpload, but because the asset
	-- metadata hasn't been uploaded, this triggers the WF-2608 error.

	local optOverrides = {
		internalXmpCameraRaw = true
	}

	local assetDocWithInlineSettings = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, optOverrides )

	local assetOverlay = {
		_id = assetId,
		_localOnly = {
			localOriginalError = true
		},
		develop = assetDocWithInlineSettings.develop
	}

	rxTest:waitFor( catalog:docStore():applyOverlay( assetOverlay ) )

	-- Start binary upload, which starts processing the Version binary.
	-- Before the fix, the uploader would try to refresh the links in the parent Asset
	-- doc, which would fail because the Asset has no self link. As a result, we would
	-- see the following error in the console log:
	-- [string "WFCatBinaryUploader.lua"]:7368: bad argument #2 (url) to get (+string expected, got nil)
	--
	-- With the fix, the uploader will not try to refresh the links if the parent
	-- Asset has no self link.

	uploader:startContinuousUploads()

	local didTriggerReloadLinks = false

	local reloadLinksSub = uploader:_TEST_getReloadLinksForDocumentStream()
		:subscribe( function()
			didTriggerReloadLinks = true
		end )

	-- Sleep just long enough for the uploader to process the Version document and
	-- determine if it needs to refresh the parent Asset's links. There's no
	-- good hook for this, so sleep is fine (especially in the context of a nightly
	-- test that already takes many seconds to complete).

	rxTest:sleep( 1 )

	rxTest:expectEqual( false, didTriggerReloadLinks )

	reloadLinksSub:dispose()

	-- For completeness, remove blocking errors and watch sync resolve successfully.

	assetOverlay = {
		_id = assetId,
		_localOnly = {
			localOriginalError = { _delete = true } -- Remove blocking original upload error.
		}
	}

	rxTest:waitFor( catalog:docStore():applyOverlay( assetOverlay ) )

	-- Start up Sync

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()

	-- Wait for version to fully sync

	local versionObserver = rxTest:observe( catalog:docStore():ongoingDocValue( versionId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' ) then
				return true
			end
		end ) )

	versionObserver:expectDataWithinTime( 120, true )

end

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

function WFCatBinaryUploaderTest.rxTestMockedVersion_ErrorAuxMissing( rxTest )

	-- Test for trying to upload a Version whose Asset does not contain the auxiliaries
	-- referenced by the Version.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local wfCatalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	-- Create Asset & Version

	local optArguments = {
		hasMasks = false
	}

	local assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )

	optArguments = {
		hasMasks = true,
		noDevelopSettingsLink = true
	}

	local versionDocId = KSIdGenerator.uuidInLowercaseHex()
	local versionDocValue, expectedData = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId, optArguments )
	local versionRevId = versionDocValue._newRevId
	versionDocValue.asset.id = assetDocValue._id

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue, versionDocValue } ) )

	local xmpUploadUrl = string.format( "/v2/catalogs/{catalog_id}/assets/%s/revisions/%s/xmp/develop", versionDocId, versionRevId )
	local errKey = 'missing_aux_ids'
	local errStr = 'foo'
	local errDesc = 'Invalid content type'
	local errStatus = '403'
	local errCode = 'AuxMissingError'

	local response = WFCatBinaryUploaderTestUtils.generateErrorResponseMock( xmpUploadUrl, expectedData.xmpDevelopSettingsFilePath, errKey, errStr, errDesc, errStatus, errCode )
	catalogSpec:expectHttpRequest( response )

	uploader:startContinuousUploads()

	rxTest:observe( wfCatalog:docStore():ongoingDocValue( versionDocId )
			:pluck( '_localOnly', 'ozError', 'subtype' )
		):expectDataWithinTime( 5, 'ReferencedAuxMissingError' )

end

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

function WFCatBinaryUploaderTest.rxTestMockedVersion_maybeFixUpOrRemoveCandidates( rxTest )

	-- Verifies that we will not attempt to upload a Version's externalized develop
	-- settings until the parent Asset document's auxiliaries are fully synced and
	-- also not when the template for start direct xmp upload for versions is not
	-- available in the asset document.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local wfCatalog = setupMockedCatalogAndUploader( rxTest, catalogId )

	-- first test code with ADS feature disabled

	local atomicDevelopSettingsEnabled = false

	-- Create Asset & Version. Mark the asset as deleted as this should also remove version
	-- from the candiate list. Run this test before the ADS capability is set as in that case
	-- the delete asset check is redundant with the check for the version direct upload template
	-- link since a deleted asset by definition does not have all that server metadata any more.
	-- also include a version with a parent asset that does have xmp uploaded but is missing
	-- direct upload link for xmp camera raw for versions to test that that is ignored if ADS
	-- capability is not enabled

	local assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog )
	assetDocValue._deleted = true

	local optArguments = {
		noDevelopSettingsLink = true
	}

	local versionDocId = KSIdGenerator.uuidInLowercaseHex()
	local versionDocValue, versionLocalOnly = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId, optArguments )
	local sha256 = versionLocalOnly.developSettingsSha256
	versionDocValue.asset.id = assetDocValue._id

	local assetDocValue2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog )
	local versionDocId2 = KSIdGenerator.uuidInLowercaseHex()

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue, versionDocValue } ) )

	local expected = {
		false,
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocValue2._id },

	}

	local candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 19 ] = assetDocValue._id },
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocValue2._id },
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- some atomic develop settings specific code has been added to this method so testing it here

	atomicDevelopSettingsEnabled = true

	-- Create Asset & Version. The auxiliaries referred to by the Version are synced,
	-- so maybeFixUpOrRemoveCandidates should not remove the entry and
	-- should also add the partially populated direct upload template link

	optArguments = {
		hasMasks = true,
		sharedMasks = true
	}

	assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )
	local revisionId = assetDocValue._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDocValue, catalogId, assetDocValue._id, revisionId)

	optArguments = {
		hasMasks = true,
		noDevelopSettingsLink = true
	}

	versionDocId = KSIdGenerator.uuidInLowercaseHex()
	versionDocValue, versionLocalOnly = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId, optArguments )
	sha256 = versionLocalOnly.developSettingsSha256
	versionDocValue.asset.id = assetDocValue._id

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue, versionDocValue } ) )

	local template = derefOpt( assetDocValue, '_serverProvidedMetadata', 'links', '/rels/versions/xmp_develop_create/parts', 'href' )
	local _, partialTemplate = WFURI.replaceFromOzTemplate( template, {
		version_id = versionDocId,
		sha256 = sha256,
		upload_status = 'all',
		content_type  = WFURI.escape( 'application/rdf+xml', 'http_query_key' )
	} )

	expected = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = partialTemplate, [ 19 ] = assetDocValue._id }
	}

	candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 19 ] = assetDocValue._id }
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Running the same candidate list through the function should be a no-op,
	-- and not setup the template link again if it is already set

	expected = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = 'fake_the_template', [ 19 ] = assetDocValue._id }
	}

	candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = 'fake_the_template', [ 19 ] = assetDocValue._id }
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- with same setup as previous test (auxiliaries synced, asset has template link)
	-- special value mainPartsRequestUrlVersionAlreadySynced set for parts link means
	-- that version should NOT have that link populated as it will not be uploaded
	-- with direct upload (WF-2429)

	expected = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocValue._id }
	}

	candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocValue._id }
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Running the same candidate list through the function should be a no-op,

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Create Asset & Version. The auxiliaries referred to by the Version have not
	-- yet been uploaded within the parent Asset document.

	optArguments = {
		hasMasks = true,
		sharedMasks = true,
		noMaskLinks = true
	}

	assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )
	revisionId = assetDocValue._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDocValue, catalogId, assetDocValue._id, revisionId)

	optArguments = {
		hasMasks = true,
		noDevelopSettingsLink = true
	}

	versionDocId = KSIdGenerator.uuidInLowercaseHex()
	versionDocValue, versionLocalOnly = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId, optArguments )
	sha256 = versionLocalOnly.developSettingsSha256
	versionDocValue.asset.id = assetDocValue._id

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue, versionDocValue } ) )

	expected = { false }

	candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 19 ] = assetDocValue._id }
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Create Asset & Version. There are no auxiliaries pending upload, but the
	-- direct upload xmp develop for version template link is missing from asset

	optArguments = {
		hasMasks = true,
		sharedMasks = true,
	}

	assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )

	optArguments = {
		hasMasks = true,
		noDevelopSettingsLink = true
	}

	versionDocId = KSIdGenerator.uuidInLowercaseHex()
	versionDocValue, versionLocalOnly = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId, optArguments )
	sha256 = versionLocalOnly.developSettingsSha256
	versionDocValue.asset.id = assetDocValue._id

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue, versionDocValue } ) )

	expected = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 19 ] = assetDocValue._id, skipForNow = true }
	}

	candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 19 ] = assetDocValue._id }
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- with same setup as previous test (auxiliaries synced, asset does not have template link)
	-- special value mainPartsRequestUrlVersionAlreadySynced set for parts link means
	-- that version should NOT have that link populated as it will not be uploaded
	-- with direct upload (and therefore asset does not need to be reloaded and skipForNow
	-- annotation should not be added) (WF-2429)

	expected = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocValue._id }
	}

	candidates = {
		{ [ 1 ] = versionDocId, [ 8 ] = sha256, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocValue._id }
	}

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

	-- Running the same candidate list through the function should be a no-op,

	rxTest:expectSingleValue(
			WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates, atomicDevelopSettingsEnabled ),
			expected )

end

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

function WFCatBinaryUploaderTest.rxTestRefreshAssetLinks_maybeFixUpOrRemoveCandidates( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local wfCatalog = setupMockedCatalogAndUploader( rxTest, catalogId )

	local optArguments = {
		noDevelopSettingsLink = true
	}

	-- first run test without atomicDevelopSettings capability enabled, only original binary
	-- links are refreshed

	local atomicDevelopSettingsEnabled = false

	-- first asset will have direct upload links for aux, xmp_develop and master, but not for versions

	local assetDocId = KSIdGenerator.uuidInLowercaseHex()
	local assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetDocId, optArguments )

	local versionDocId = KSIdGenerator.uuidInLowercaseHex()

	-- second asset will have no direct upload links

	local assetDocId2 = KSIdGenerator.uuidInLowercaseHex()
	local versionDocId2 = KSIdGenerator.uuidInLowercaseHex()

	-- third asset will have no original upload link but be in a space where it is not expected
	-- so should not get reloaded

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local assetDocId3 = KSIdGenerator.uuidInLowercaseHex()

	-- fourth asset is marked with mainPartsRequestUrlNotAvailable so should not get reloaded

	local assetDocId4 = KSIdGenerator.uuidInLowercaseHex()

	-- should not attempt to load direct upoad link for watermark upload

	local watermarkId = KSIdGenerator.uuidInLowercaseHex()

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue } ) )

	local expected = {

		{ [ 1 ] = assetDocId, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },
		{ [ 1 ] = versionDocId, [ 19 ] = assetDocId },
			-- no check for version direct upload template link with ADS disabled

		{ [ 1 ] = assetDocId2, [ 4 ] = 'aux' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'xmp_develop' },
			-- not marked as skip without ADS enabled
		{ [ 1 ] = assetDocId2, [ 4 ] = 'master', skipForNow = true },
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocId2 },
			-- not replaced by false without ADS enabled

		{ [ 1 ] = assetDocId3, [ 4 ] = 'master', [ 15 ] = spaceId },

		{ [ 1 ] = assetDocId4, [ 4 ] = 'master', [ 16 ] = 'mainPartsRequestUrlNotAvailable' },

		{ [ 1 ] = watermarkId, [ 4 ] = 'watermark_original' },

	}

	local candidates = {

		{ [ 1 ] = assetDocId, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },
		{ [ 1 ] = versionDocId, [ 19 ] = assetDocId },

		{ [ 1 ] = assetDocId2, [ 4 ] = 'aux' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'xmp_develop' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'master' },
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocId2 },

		{ [ 1 ] = assetDocId3, [ 4 ] = 'master', [ 15 ] = spaceId },

		{ [ 1 ] = assetDocId4, [ 4 ] = 'master', [ 16 ] = 'mainPartsRequestUrlNotAvailable' },

		{ [ 1 ] = watermarkId, [ 4 ] = 'watermark_original' },

	}

	local reloadLinksForDocumentStream = Rx.ReplaySubject( 2 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	-- only second asset links refreshed with ADS disabled
	local expectedReloads = {
		{ assetDocId2, false },
	}

	local function mapParameters( docId, shouldReprocessVersions )
		return { docId, shouldReprocessVersions }
	end

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue(
		reloadLinksForDocumentStream
			:map( mapParameters )
			:toArray(),
		expectedReloads
	)
	reloadLinksForDocumentStream:dispose()

	-- rest of tests run with atomicDevelopSettings capability enabled

	atomicDevelopSettingsEnabled = true

	-- now different results using same candidates as previous test (even though using the
	-- same input, need to recreate candidates list for each test as call to
	-- maybeFixUpOrRemoveCandidates() changes the data)

	expected = {

		{ [ 1 ] = assetDocId, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },
		{ [ 1 ] = versionDocId, [ 19 ] = assetDocId, skipForNow = true },
			-- asset needs link refresh to get version direct upload xmp camera raw with ADS enabled

		{ [ 1 ] = assetDocId2, [ 4 ] = 'aux', skipForNow = true },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'xmp_develop', skipForNow = true },
			-- marked as skip with ADS enabled
		{ [ 1 ] = assetDocId2, [ 4 ] = 'master', skipForNow = true },
		false,
			-- if asset needs template links refresh then version is just removed from list

		{ [ 1 ] = assetDocId3, [ 4 ] = 'master', [ 15 ] = spaceId },

		{ [ 1 ] = assetDocId4, [ 4 ] = 'master', [ 16 ] = 'mainPartsRequestUrlNotAvailable' },

		{ [ 1 ] = watermarkId, [ 4 ] = 'watermark_original' },

	}

	candidates = {

		{ [ 1 ] = assetDocId, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },
		{ [ 1 ] = versionDocId, [ 19 ] = assetDocId },

		{ [ 1 ] = assetDocId2, [ 4 ] = 'aux' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'xmp_develop' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'master' },
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocId2 },

		{ [ 1 ] = assetDocId3, [ 4 ] = 'master', [ 15 ] = spaceId },

		{ [ 1 ] = assetDocId4, [ 4 ] = 'master', [ 16 ] = 'mainPartsRequestUrlNotAvailable' },

		{ [ 1 ] = watermarkId, [ 4 ] = 'watermark_original' },

	}

	reloadLinksForDocumentStream = Rx.ReplaySubject( 3 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	local function sortReloads( values )

		table.sort( values, function( a, b )
				return a[ 1 ] < b[ 1 ]
			end )

		return values

	end

	-- both assets links refreshed with ADS enabled
	expectedReloads = {
		{ assetDocId, true },
		{ assetDocId2, false },
	}
	sortReloads( expectedReloads )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue(
		reloadLinksForDocumentStream
			:map( mapParameters )
			:toArray()
			:map( sortReloads ),
		expectedReloads
	)
	reloadLinksForDocumentStream:dispose()

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	reloadLinksForDocumentStream = Rx.ReplaySubject( 1 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue( reloadLinksForDocumentStream:toArray(), { } )
	reloadLinksForDocumentStream:dispose()

	-- test with no versions in the candidates list

	expected = {

		{ [ 1 ] = assetDocId, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'xmp_develop', skipForNow = true  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },

		{ [ 1 ] = assetDocId2, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'master', skipForNow = true },

		{ [ 1 ] = assetDocId3, [ 4 ] = 'aux', skipForNow = true },
		{ [ 1 ] = assetDocId3, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink' },
		{ [ 1 ] = assetDocId3, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },

	}

	candidates = {

		{ [ 1 ] = assetDocId, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'xmp_develop'  },
		{ [ 1 ] = assetDocId, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },

		{ [ 1 ] = assetDocId2, [ 4 ] = 'aux', [ 16 ] = 'auxDirectLink'  },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink' },
		{ [ 1 ] = assetDocId2, [ 4 ] = 'master' },

		{ [ 1 ] = assetDocId3, [ 4 ] = 'aux' },
		{ [ 1 ] = assetDocId3, [ 4 ] = 'xmp_develop', [ 16 ] = 'xmpDevelopDirectLink' },
		{ [ 1 ] = assetDocId3, [ 4 ] = 'master', [ 16 ] = 'originalDirectLink' },

	}

	reloadLinksForDocumentStream = Rx.ReplaySubject( 4 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	expectedReloads = {
		{ assetDocId, false },
		{ assetDocId2, false },
		{ assetDocId3, false },
	}
	sortReloads( expectedReloads )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue(
		reloadLinksForDocumentStream
			:map( mapParameters )
			:toArray()
			:map( sortReloads ),
		expectedReloads
	)
	reloadLinksForDocumentStream:dispose()

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	reloadLinksForDocumentStream = Rx.ReplaySubject( 1 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue( reloadLinksForDocumentStream:toArray(), { } )
	reloadLinksForDocumentStream:dispose()

	-- when all versions are marked with mainPartsRequestUrlVersionAlreadySynced and asset
	-- does not have direct upload link template for versions, the asset should not be
	-- reloaded. WF-2429

	expected = {

		{ [ 1 ] = versionDocId, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocId },
		{ [ 1 ] = versionDocId2, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocId },

	}

	candidates = {

		{ [ 1 ] = versionDocId, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocId },
		{ [ 1 ] = versionDocId2, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocId },

	}

	reloadLinksForDocumentStream = Rx.ReplaySubject( 1 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue( reloadLinksForDocumentStream:toArray(), { } )
	reloadLinksForDocumentStream:dispose()

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	reloadLinksForDocumentStream = Rx.ReplaySubject( 1 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue( reloadLinksForDocumentStream:toArray(), { } )
	reloadLinksForDocumentStream:dispose()

	-- when some versions are marked with mainPartsRequestUrlVersionAlreadySynced but
	-- some are not marked and asset does not have direct upload link template for
	-- versions, the asset should be reloaded. WF-2429

	expected = {

		{ [ 1 ] = versionDocId, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocId },
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocId, skipForNow = true },

	}

	candidates = {

		{ [ 1 ] = versionDocId, [ 16 ] = 'mainPartsRequestUrlVersionAlreadySynced',
				[ 19 ] = assetDocId },
		{ [ 1 ] = versionDocId2, [ 19 ] = assetDocId },

	}

	reloadLinksForDocumentStream = Rx.ReplaySubject( 2 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	expectedReloads = {
		{ assetDocId, true },
	}

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue(
		reloadLinksForDocumentStream
			:map( mapParameters )
			:toArray(),
		expectedReloads
	)
	reloadLinksForDocumentStream:dispose()

	-- Running the same candidate list through the function should be a no-op,
	-- and not throw an error:

	reloadLinksForDocumentStream = Rx.ReplaySubject( 1 )

	rxTest:expectSingleValue(
		WFCatBinaryUploader._TEST_maybeFixUpOrRemoveCandidates( wfCatalog:docStore(), candidates,
			atomicDevelopSettingsEnabled, reloadLinksForDocumentStream ),
		expected )

	reloadLinksForDocumentStream:onCompleted()
	rxTest:expectSingleValue( reloadLinksForDocumentStream:toArray(), { } )
	reloadLinksForDocumentStream:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedVersion_maybeFixUpOrRemoveCandidates_noWorkReporting( rxTest )

	rxTest:setTimeLimit( 10 )

	-- Verifies that if the only candidates are ones with unsynced parental aux,
	-- the uploader correctly reports that it is not doing any work.

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local setupOptions = {
		atomicDevelopSettings = true,
			-- some atomic develop settings specific code has been added to this method so testing it here
	}
	local wfCatalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, setupOptions )

	-- Create Asset & Version. The auxiliaries referred to by the Version have not
	-- yet been uploaded within the parent Asset document.

	local optArguments = {
		hasMasks = true,
		sharedMasks = true,
		noMaskLinks = true
	}

	-- Using the _pendingCommmit label to prevent this asset from showing up in the needsBinaryUpload table.
	-- This is kind of tricky, but it allows us verify this edge-casey bug.

	local assetDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )
	local revisionId = derefOpt(assetDocValue, '_serverProvidedMetadata', 'revision_ids', 1)
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDocValue, wfCatalog:ozCatalogId(), assetDocValue._id, revisionId)
	assetDocValue._labels = {}
	assetDocValue._labels[ '_pendingCommit' ] = true

	-- Create the version, which will show up in the needsBinaryUpload table.

	optArguments = {
		hasMasks = true,
		noDevelopSettingsLink = true
	}

	local versionDocId = KSIdGenerator.uuidInLowercaseHex()
	local versionDocValue = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId, optArguments )
	versionDocValue.asset.id = assetDocValue._id

	-- Create a second version, which will also show up in the needsBinaryUpload table.

	optArguments = {
		hasMasks = true,
		noDevelopSettingsLink = true
	}

	local versionDocId2 = KSIdGenerator.uuidInLowercaseHex()
	local versionDocValue2 = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionDocId2, optArguments )
	versionDocValue2.asset.id = assetDocValue._id

	rxTest:waitFor( wfCatalog:docStore():bulkSetDocValue( { assetDocValue, versionDocValue, versionDocValue2 } ) )

	-- Verify the uploader thinks it's doing work

	local observer = rxTest:observe( uploader:isDoingWork() )

	uploader:DEBUG_taskDidWork( true )

	observer:expectDataWithinTime( 3, true )

	-- Now process the pending Version upload. There should be nothing to upload,
	-- and the task should report it has no work.

	uploader:startContinuousUploads()

	observer:expectDataWithinTime( 3, false )

	observer:dispose()

end

--==============================================================================
-- @mark Other
--==============================================================================

local function testMockedUserOwnedSharedAlbumUploadPriority( rxTest, atomicDevelopSettings )

	-- for now leave this test doing Oz binary uploads as order of api calls is indeterministic once both uploads are going

	rxTest:setTimeLimit( 10 )

	local docsToInsertIntoDocStore = {}

	-- Construct a user owned shared album.

	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local albumRevId = KSIdGenerator.uuidInLowercaseHex()

	local albumDoc = {

		_id = albumId,
		_newRevId = albumRevId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',
		_serverProvidedMetadata = {
			revision_ids = {
				albumRevId,
			},
		}

	}

	docsToInsertIntoDocStore[ #docsToInsertIntoDocStore + 1 ] = albumDoc

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local spaceRevId = KSIdGenerator.uuidInLowercaseHex()

	local spaceDoc = {
		_id = spaceId,
		_newRevId = spaceRevId,
		type = 'space',
		subtype = 'shared',
		download = true,
		private = false,
		location = true,
		metadata = true,
	}

	docsToInsertIntoDocStore[ #docsToInsertIntoDocStore + 1 ] = spaceDoc

	local spaceAlbumRevId = KSIdGenerator.uuidInLowercaseHex()

	local spaceAlbumDoc = {
		type = 'space_album',
		_id = KSIdGenerator.uuidInLowercaseHex(),
		_newRevId = spaceAlbumRevId,
		space = {
			id = spaceId,
		},
		album = {
			id = albumId,
		},
		_serverProvidedMetadata = {
			revision_ids = {
				spaceAlbumRevId,
			},
		}
	}

	docsToInsertIntoDocStore[ #docsToInsertIntoDocStore + 1 ] = spaceAlbumDoc

	-- Create a number of different assets, all of which should have a different upload priority.

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local urlResponses = {}

	local count = 4
	if atomicDevelopSettings then
		count = 8
	end

	for j = 1, count, 1 do

		local i = j % 4

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local revisionId = KSIdGenerator.uuidInLowercaseHex()
		local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

		local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

		if j <= 4 then

			local fileSize = WFFileUtils.fileAttributes( originalFilePath ).fileSize
			local contentType = 'image/jpeg'
			local _, mainPartsRequestUrl = WFURI.replaceFromOzTemplate(
				"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
				"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
				catalogId = catalogId,
				assetId = assetId,
				revisionId = revisionId,
				upload_status = 'pending',
				file_size = tostring( fileSize ),
				part_size = tostring( kOZChunkSize ),
				content_type = WFURI.escape( contentType, 'http_query_key' ),
			})

			local assetUploadUrl = string.format( "xyz.s3-us-west-2.amazonaws.com/%d", j )
			local assetCompleteUrl = string.format( "main/parts/%d?file_size=%d", j, fileSize )

			urlResponses[ #urlResponses + 1 ] =
				WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock( mainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )

			urlResponses[ #urlResponses + 1 ] =
				WFCatBinaryUploaderTestUtils.generateAssetUploadMock( assetUploadUrl, originalFilePath, fileSize, contentType )

			urlResponses[ #urlResponses + 1 ] =
				WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

		end

		local assetDoc = WFCatAssetTestUtils.createDocumentWithOzMetadataUpdate(
			catalogId, assetId, revisionId, sha256, originalFilePath )

		if i == 3 or i == 0 then

			-- Asset belongs to the user-owned shared album, so create the album asset.

			local albumAssetRevId = KSIdGenerator.uuidInLowercaseHex()

			docsToInsertIntoDocStore[ #docsToInsertIntoDocStore + 1 ] = {
				_id = KSIdGenerator.uuidInLowercaseHex(),
				_newRevId = albumAssetRevId,
				type = 'album_asset',
				asset = {
					id = assetId,
				},
				album = {
					id = albumId,
				},
				_serverProvidedMetadata = {
					revision_ids = {
						albumAssetRevId,
					},
				}

			}

		end

		if i == 1 or i == 3 then

			-- Asset is a pick and therefore should have a high upload priority.

			assetDoc.reviews = {
				[ 'foo' ] = {
					flag = 'pick'
				},
			}

			if not atomicDevelopSettings then
				-- make appear to be not completey synced with Oz, which should not impact
				-- decision when atomic develop settings is not enabled
				assetDoc = WFTableMerge.applyOverlay( assetDoc, {
					_newRevId = KSIdGenerator.uuidInLowercaseHex(),
					userUpdated = KSDate.timeInIso8601FormatNearestSecond(),
				} )
			end

		end

		-- extra assets created for atomicDevelopSettings tests should not be completely
		-- synced with Oz, which means the should not be considered ready to upload

		if j > 4 then
			assetDoc = WFTableMerge.applyOverlay( assetDoc, {
				_newRevId = KSIdGenerator.uuidInLowercaseHex(),
				userUpdated = KSDate.timeInIso8601FormatNearestSecond(),
			} )
		end

		docsToInsertIntoDocStore[ #docsToInsertIntoDocStore + 1 ] = assetDoc

	end

	local prioritizedUrlResponses = {

		urlResponses[ 7 ],
		urlResponses[ 8 ],
		urlResponses[ 9 ],
			-- asset 3 should be uploaded first since it's in a user-owned shared album, and is a pick

		urlResponses[ 10 ],
		urlResponses[ 11 ],
		urlResponses[ 12 ],
			-- asset 4 should be uploaded second since it's in a user-owned shared album.

		urlResponses[ 1 ],
		urlResponses[ 2 ],
		urlResponses[ 3 ],
			-- asset 1 should be uploaded third because it's a pick.

		urlResponses[ 4 ],
		urlResponses[ 5 ],
		urlResponses[ 6 ],
			-- asset 2 should be uploaded last.

	}

	local setupOptions = {
		atomicDevelopSettings = atomicDevelopSettings,
	}
	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, prioritizedUrlResponses, setupOptions )

	rxTest:waitFor( catalog:docStore():bulkSetDocValue( docsToInsertIntoDocStore ) )

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for all four uploads to complete

	httpRequestsProcessed:expectDataWithinTime( 5, 12 )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUserOwnedSharedAlbumUploadPriority( rxTest )

	testMockedUserOwnedSharedAlbumUploadPriority( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUserOwnedSharedAlbumUploadPriority_AtomicDeveopSettings( rxTest )

	-- rules for when assets are eligible for upload are a little different when atomic develop settings is enabled
	-- so make sure shared album priority uploader is running the correct sql query

	local atomicDevelopSettings = true
	testMockedUserOwnedSharedAlbumUploadPriority( rxTest, atomicDevelopSettings )

end

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

function WFCatBinaryUploaderTest.rxTestGetEstimatedTimeRemainingImp( rxTest )

	local kOzUploadConfirmationDelay = 5

	local sizeOfRemainingUploadsStream = Rx.Subject()
	local sizeOfRemainingCopiesStream = Rx.Subject()
	local ozUploadSpeedRingBuffer = WFRingBuffer.make( 1 )

	local observer = rxTest:observe( WFCatBinaryUploader._TEST_getEstimatedTimeRemainingImp(
			sizeOfRemainingUploadsStream,
			sizeOfRemainingCopiesStream,
			ozUploadSpeedRingBuffer ) )

	-- Test Only Uploads

	sizeOfRemainingCopiesStream:onNext( { 0, 0, 0, 0, 0 } ) -- Nothing to copy
	sizeOfRemainingUploadsStream:onNext( { 0, 0, 0 } ) -- Nothing to upload

	observer:expectDataWithinTime( 1, -1 ) -- -1 indicates nothing to upload or copy.

	sizeOfRemainingUploadsStream:onNext( { 0, 0, 1 } ) -- File finished uploading, but uploads are not yet confirmed by Oz.

	local expectedUploadSpeed = 0

	observer:expectDataWithinTime( 1, kOzUploadConfirmationDelay, expectedUploadSpeed ) -- Nothing to upload, just waiting on Oz.

	sizeOfRemainingUploadsStream:onNext( { 100, 0, 1 } ) -- One file to upload, 0 upload speed.

	observer:expectDataWithinTime( 1, math.huge, expectedUploadSpeed ) -- infinite upload time

	sizeOfRemainingUploadsStream:onNext( { 100, 5, 1 } ) -- One 100 byte file to upload, 5bps upload speed.

	expectedUploadSpeed = 5

	observer:expectDataWithinTime( 1, 20 + kOzUploadConfirmationDelay, expectedUploadSpeed ) -- 100b / 5bps = 20 seconds

	-- Test Only Copies

	sizeOfRemainingUploadsStream:onNext( { 0, 0, 0 } ) -- Nothing to upload
	sizeOfRemainingCopiesStream:onNext( { 0, 1, 5, 0, 1 } ) -- File finished copying, waiting for Oz.

	expectedUploadSpeed = 0

	observer:expectDataWithinTime( 1, kOzUploadConfirmationDelay, 0 ) -- Just waiting on Oz.

	sizeOfRemainingCopiesStream:onNext( { 100, 1, 5, 0, 1 } ) -- 100 bytes to copy, 5bps copy speed.

	expectedUploadSpeed = 0

	observer:expectDataWithinTime( 1, 20 + kOzUploadConfirmationDelay, 0 ) -- 100b / 5bps = 20 seconds.

	sizeOfRemainingCopiesStream:onNext( { 100, 10, 5, 0, 1 } ) -- 100 bytes to copy, 5bps copy speed, 10 in-flight copies.

	expectedUploadSpeed = 0

	observer:expectDataWithinTime( 1, 2 + kOzUploadConfirmationDelay, expectedUploadSpeed ) -- 100b / 5bps / 10 (in-flight) = 2 seconds.

	sizeOfRemainingCopiesStream:onNext( { 100, 10, 5, 5, 1 } ) -- 100 bytes to copy, 5bps copy speed, 10 in-flight copies, 5 second buffer.

	expectedUploadSpeed = 0

	observer:expectDataWithinTime( 1, 2.5 + kOzUploadConfirmationDelay, expectedUploadSpeed ) -- ( ( 100b / 5bps ) + 5 ) / 10 (in-flight) = 2.5 seconds.

	-- Test Both Uploads And Copies

	sizeOfRemainingCopiesStream:onNext( { 100, 10, 5, 0, 1 } ) -- 100 bytes to copy, 5bps copy speed, 10 in-flight copies.
	sizeOfRemainingUploadsStream:onNext( { 100, 5, 1 } ) -- One 100 byte file to upload, 5bps upload speed.

	expectedUploadSpeed = 5

	observer:expectDataWithinTime( 1, 20 + 2 + kOzUploadConfirmationDelay, expectedUploadSpeed )

end

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

function WFCatBinaryUploaderTest.rxTestCalculateUploadSpeed( rxTest )

	-- Nothing to upload

	local rows = {}
	local firstMeasurement = {}
	local previousRemainingBinaryUploads = {}
	local inFlightUploadProgress = {}

	local result = WFCatBinaryUploader._TEST_calculateUploadSpeed(
			rows, firstMeasurement, previousRemainingBinaryUploads,inFlightUploadProgress )

	local expected = { 0, 0, 0 }

	rxTest:expectEqual( expected, result )

	-- Stuff to upload, but nothing scheduled to upload

	rows = {
		{ '1', 'original', 10, 'sha256' }
	}

	firstMeasurement = {}
	previousRemainingBinaryUploads = {}
	inFlightUploadProgress = {}

	result = WFCatBinaryUploader._TEST_calculateUploadSpeed(
			rows, firstMeasurement, previousRemainingBinaryUploads,inFlightUploadProgress )

	expected = { 10, 0, #rows }

	rxTest:expectEqual( expected, result )

	-- Upload in flight

	rows = {
		{ '1', 'original', 10, 'sha256' }
	}

	firstMeasurement = {
		timestamp = 0,
		size = 10
	}

	previousRemainingBinaryUploads = {
		[ '1::original::sha256' ] = true
	}

	local processedCount = 5
	local progessTimestamp = 100

	inFlightUploadProgress = {
		[ '1' ] = {
			original = {
				sha256 = {
					processedCount = processedCount,
					extraInfo = {
						startTime = 100,
						timestamp = progessTimestamp
					}
				}
			}
		}
	}

	local expectedSpeed = ( firstMeasurement.size - processedCount ) / ( progessTimestamp - firstMeasurement.timestamp )

	result = WFCatBinaryUploader._TEST_calculateUploadSpeed(
			rows, firstMeasurement, previousRemainingBinaryUploads,inFlightUploadProgress )

	expected = { 5, expectedSpeed, #rows }

	rxTest:expectEqual( expected, result )

end

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

function WFCatBinaryUploaderTest.rxTestSanitizeDirectUploadUrlForAnalytics( rxTest )

	local exampleS3Url = [[https://oz-prod-masters.s3.us-west-2.amazonaws.com/5cda448fc27a4502ab90e5f79bbf7d13/6f0608f378a6456197383f691921e386/master?partNumber=1&uploadId=EdCnRgobfelrIPPZose27UgQlqKsfrldl57VJRMvnBpyXUm7pLhB9vfTB36F88Z2HKPyd.vWIoH6ZszUv9n7IsmzgwSsW_yeBhT.36mRgtOb1eiK1JvuxrR_tH1I_Z2agOXblb4QNVY2vAZwYzccOrwSz111vboEdQiUPl4BfE8-&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIA3PR2V2HL3KH5I4A7%2F20210921%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210921T211621Z&X-Amz-Expires=21600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJz%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIQCZHzXke4MwwNWDuy5Xuyb2zs%2BAduGahwXHQWTGvNEx0wIgE4SczUsf21nUpGla2De2vChAFAofxBw5VIBJLhG8JVkqogII9f%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw3ODkzMjMzMDU0MzEiDOA7%2F7EfO4aWptNm%2Fyr2AfoL6UOPfH11XbjH5GA8jAGx86LoQf%2FOBzldIXLsmdFHL2I6cuQfsDEJznAqzPNet0ct0wJTJsd%2FDtXXUxk8x9hIcBIt5wMiWaUJgdlOpyC81zAIQpVh4E28WZfXAwA1nZobnjvUi5nxee4XwxrV0%2BpFjjBhg5sQcHqJn5yMy7437jWQNU0XoehEg0n7fVD0A9tWC4rW7sAmwKl%2FNx0Z1bzdVvkzLm%2FelQbOFnGHPZA3%2BqbadOuhAbzfDEJ4mJBLVRa10Qv0OcfnOfOYqNbWaC%2F%2BmNW6iRfsVAPKDRszcYQTQ1HjicVxxbp93bP%2F6zbx%2BbfOWgef4TCY7qiKBjqdASlMpVTWyVupMtkLJ%2BHynbORUMpjyzjvdqroSxpWwlMGRKibhBczOSx%2FE95dh%2FzQFmt%2BNf6fDV0zkgzO%2BzQIDhxzZ6GN0FUo4QDrlbgGs3dpSzZtf1%2BLub%2BSzeiiG2IIp6QWMHqQbgtH4tWi4Y0adoa0wDHCp1CF3Yzouq2l5ppMWHYDyKQHvtnIh4vms8XV7VEgG2Wi%2FypU%2FFuFX1s%3D&X-Amz-SignedHeaders]]

	local sanitizedUrl = WFCatBinaryUploader._DEBUG_sanitizeDirectUploadUrlForAnalytics( exampleS3Url )

	local expectedUrl = [[https://oz-prod-masters.s3.us-west-2.amazonaws.com/5cda448fc27a4502ab90e5f79bbf7d13/6f0608f378a6456197383f691921e386/master?partNumber=1&uploadId=EdCnRgobfelrIPPZose27UgQlqKsfrldl57VJRMvnBpyXUm7pLhB9vfTB36F88Z2HKPyd.vWIoH6ZszUv9n7IsmzgwSsW_yeBhT.36mRgtOb1eiK1JvuxrR_tH1I_Z2agOXblb4QNVY2vAZwYzccOrwSz111vboEdQiUPl4BfE8-&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIA3PR2V2HL3KH5I4A7%2F20210921%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210921T211621Z&X-Amz-Expires=21600&X-Amz-Security-Token=***&X-Amz-SignedHeaders]]

	rxTest:expectEqual( expectedUrl, sanitizedUrl )

end

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

function WFCatBinaryUploaderTest.rxTestDontUploadPendingCommits( rxTest )

	-- This test verifies that BYOCR assets pending commit do not show up
	-- within the `needsBinaryUpload` table.

	rxTest:setTimeLimit( 10 )

	local optArgs = {}

	if WFPlatformInfo.isMobile() then
		optArgs.indexDefinitions = 'LrmCatalogIndexDefinitions.lua'
	end

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest , nil, nil, optArgs )

	-- Add an asset that needs to be uplaoded.

	local overrides = {
		noMasterLink = true
	}

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local docValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, overrides )

	rxTest:waitFor( wfCatalog:docStore():setDocValue( docValue ) )

	-- The asset should be emitted.

	local queryObs = wfCatalog:indexStore():queryOnce {
		sql = "SELECT docId FROM needsBinaryUpload WHERE docId == ?",
		singleColumnQuery = true,
		assetId
	}

	rxTest:expectSingleValue( queryObs, { assetId } )

	-- Modify the asset and add the pending commit label.

	local overlay = {
		_id = assetId,
		_newRevId = KSIdGenerator.uuidInLowercaseHex(),
		_newLabel = '_pendingCommit'
	}

	rxTest:waitFor( wfCatalog:docStore():applyOverlay( overlay ) )

	-- The asset should not be emitted.

	rxTest:expectSingleValue( queryObs, {} )

end

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

function WFCatBinaryUploaderTest.rxTestGetNeedsUploadDocIds( rxTest )

	-- This test verifies that BYOCR assets pending commit do not show up
	-- within the `needsBinaryUpload` table.

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local wfCatalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	-- Add an asset that needs to be uplaoded.

	local overrides = {
		noMasterLink = true
	}

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local docValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, overrides )

	rxTest:waitFor( wfCatalog:docStore():setDocValue( docValue ) )

	-- The asset should be emitted.

	local queryObs = wfCatalog:indexStore():queryOnce {
		sql = "SELECT docId FROM needsBinaryUpload WHERE docId == ?",
		singleColumnQuery = true,
		assetId
	}

	rxTest:expectSingleValue( queryObs, { assetId } )

	rxTest:expectSingleValue( uploader:getNeedsUploadDocIds(), { assetId } )

	-- Modify the asset and add the pending commit label.

	local overlay = {
		_id = assetId,
		_newRevId = KSIdGenerator.uuidInLowercaseHex(),
		_newLabel = '_pendingCommit'
	}

	rxTest:waitFor( wfCatalog:docStore():applyOverlay( overlay ) )

	-- The asset should not be emitted.

	rxTest:expectSingleValue( queryObs, {} )

	rxTest:expectSingleValue( uploader:getNeedsUploadDocIds(), {} )

end

--==============================================================================
-- @mark Watermarks
--==============================================================================

local function createTestWatermark( rxTest, catalog )

	local name = "My First Watermark"

	local settings = {
		opacity = 73,
		rotation = 90,
		position = "center",
		xOffset = 0.23,
		yOffset = 0.40,
		size = 22,
		text = "@ saravana",
		font = {
			name = "SourceSansPro",
			bold = true,
			italic = true,
		},
		shadow = {
			enabled = "off"
		},
		rgbColor = { 0.4, 0.33, 0.0 },
		activeWatermarkType = "image"
	}

	rxTest:waitFor(
		WFCatWatermark.createWatermark( catalog, name, settings ) )

end

function WFCatBinaryUploaderTest.rxTestMockedUploadWatermark( rxTest )
	-- For June/August 2020 releases we don't actually need to upload image watermark
	-- data. But we are trying to get the WFCatBinaryUploader map fn ready for October
	-- when we will want to upload these but also want to avoid a reindexing due to
	-- map fn changes. So we've made changes in WFCatBinaryUploader's map fn to be ready
	-- to index watermark documents but to fail to do so (all changes for October will be
	-- outside of the map fn).
	--
	-- So what we need to do here is make sure that a watermark document that gets
	-- fed into the uploader indexing map fn does not throw an error and break the indexing
	-- for documents we do care about.
	--
	-- This test cannot validate that it *did* index the watermark document (but came up with
	-- nothing) - that will have to be hand verified that we do enter the map fn for watermark
	-- documents.
	--
	-- But it can verify that if we have a watermark document and another uploadable document
	-- after that, that we do wind up indexing the second document. This should validate that
	-- a watermark document does not throw a nil dereference or similar error. Though, again,
	-- some of this has to be taken on trust as to *why* it's not throwing an error...

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local catalog = setupMockedCatalogAndUploader( rxTest, catalogId, {} )

	local xmpDevelopFilePath = KSPathUtils.child( originalsTestFolder, 'settings' )
	local xmpDevelopSettings = KSData.make( '<xmp />' )
	KSData.saveToFile( xmpDevelopSettings, xmpDevelopFilePath )

	rxTest:finally( function()
		WFFileUtils.delete( xmpDevelopFilePath )
	end )

	-- create a watermark
	createTestWatermark( rxTest, catalog )

	-- create an asset after the watermark
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath ) )

	-- we don't actually care about upload, just select the rows in the WFCatBinaryUploader table and make
	-- sure we have one for the asset. This should mean that the watermark was indexed (and ignored) safely
	rxTest:expectSingleValue( catalog:indexStore():queryOnce( {
		sql = "SELECT docId FROM needsBinaryUpload ORDER BY binaryType ASC",
		singleColumnQuery = true,
	} ), { assetId } )

end

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

function WFCatBinaryUploaderTest.rxTestUploadVerifyManualCheckWithOz_testNightly( rxTest )

	-- WF-921: This test case verifies after max wait time,
	-- whether WF triggers a manual check with Oz to see if there is a master before attempting to upload again.
	-- This is to avoid duplicate uploads.

	rxTest:setTimeLimit( 360 )

	local uploaderOpts = { uploadBlackoutWindowInSeconds = 5, uploadBlackoutWindowCount = 0 }
	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { uploaderOpts = uploaderOpts } )

	-- Larger file size is choosen ( file size is 22MB )
	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_9876.CR2" )
	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId, originalFilePath )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	-- Do not start Uploader here
	--uploader:startContinuousUploads()

	-- wait for meta data sync
	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata') then
				return true
			else
				return false
			end
		end ) )

	docObserver:expectDataWithinTime( 60, true )

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	-- make sure no master link yet
	docObserver:expectDataWithinTime( 60, false )

	-- Now Stop the puller and start the uploader
	puller:stopContinuousUpdates()
	uploader:startContinuousUploads()

	-- make sure master link got updated, when puller is off.
	docObserver:expectDataWithinTime( 350, true )

	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader, true )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

end

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

function WFCatBinaryUploaderTest.rxTestUploadWatermark_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, _, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local name = "BinaryUploadTestWithRealOz"

	local settings = {
		opacity = 73,
		rotation = 90,
		position = "center",
		xOffset = 0.23,
		yOffset = 0.40,
		size = 22,
		text = "@ saravana",
		font = {
			name = "SourceSansPro",
			bold = true,
			italic = true,
		},
		shadow = {
			enabled = "off"
		},
		rgbColor = { 0.4, 0.33, 0.0 },
		activeWatermarkType = "image"
	}

	local relativePath = KSPathUtils.child( 'assets', 'IMG_7706.PNG' )
	--local absolutePath = KSPathUtils.child( testDataPath, relativePath )
	--local fileSize = absolutePath and WFFileUtils.fileAttributes( absolutePath ).fileSize
	local fileName = 'IMG_7706.PNG'
	--local sha256 = WFCrypto.digestFile( absolutePath, 'SHA256' )

	local wmId

	rxTest:waitFor(
		WFCatWatermark.createWatermark( catalog, name, settings, relativePath, fileName )
			:tap( function( watermarkId )
				-- capture the watermark id returned, it will be used for validation later.
				wmId = watermarkId
			end ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( wmId )
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

	-- Also verify whether originalLinkAvailable set to true
	local WFCatWatermark = require 'WFCatWatermark'
	rxTest:expectSingleValue( WFCatWatermark.getWatermarkInfo( catalog, wmId )
		:map( function( watermarkInfo )
			return ( watermarkInfo.originalLinkAvailable )
		end ), true )

end

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

function WFCatBinaryUploaderTest.rxTestImageWatermarkNoCopy_testNightly( rxTest )

	-- This test case verifies incase of watermark binary already uploaded, and if
	-- the same binary being requested to be uploaded by clients
	-- avoid issuing copying as there is no support from Oz for copying and
	-- re upload again.

	rxTest:setTimeLimit( 360 )

	local sawCopyRequest = false

	local function watchForCopyRequest( self, url, data, options, progressReporter )
		if options.requestHeaders then
			for _, header in ipairs( options.requestHeaders ) do
				if header.header == 'X-Async-Copy' then
					sawCopyRequest = true
				end
			end
		end
		return self.originalCatalogSpec:putBinary( url, data, options, progressReporter )
	end

	local _, catalog, _, _, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest, { spyFn = watchForCopyRequest } )

	local name = "BinaryUploadTestWithRealOz"

	local settings = {
		opacity = 73,
		rotation = 90,
		position = "center",
		xOffset = 0.23,
		yOffset = 0.40,
		size = 22,
		text = "@ saravana",
		font = {
			name = "SourceSansPro",
			bold = true,
			italic = true,
		},
		shadow = {
			enabled = "off"
		},
		rgbColor = { 0.4, 0.33, 0.0 },
		activeWatermarkType = "image"
	}

	local relativePath = KSPathUtils.child( 'assets', '1.5MB.jpg' )
	local fileName = '1.5MB.jpg'

	local wmId

	rxTest:waitFor(
		WFCatWatermark.createWatermark( catalog, name, settings, relativePath, fileName )
			:tap( function( watermarkId )
				-- capture the watermark id returned, it will be used for validation later.
				wmId = watermarkId
			end ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( wmId )
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(360, true)

	-- Also verify whether originalLinkAvailable set to true
	local WFCatWatermark = require 'WFCatWatermark'
	rxTest:expectSingleValue( WFCatWatermark.getWatermarkInfo( catalog, wmId )
		:map( function( watermarkInfo )
			return ( watermarkInfo.originalLinkAvailable )
		end ), true )

	-----------------------------------------------------------------------
	--- <<<< Now again trigger create watermark with the same binary >>>>
	------------------------------------------------------------------------
	name = "BinaryUploadTestWithRealOzNoCopy"
	rxTest:waitFor(
		WFCatWatermark.createWatermark( catalog, name, settings, relativePath, fileName )
			:tap( function( watermarkId )
				-- capture the watermark id returned, it will be used for validation later.
				wmId = watermarkId
			end ) )


	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( wmId )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') then
				return true
			end
		end ) )

	docObserver:expectDataWithinTime(360, true)

	-- Copy master api will use "X-Async-Copy" in the request HEAD: So, make sure that is not being used.
	rxTest:expectEqual( false, sawCopyRequest )

	-- Also verify whether originalLinkAvailable set to true
	--WFCatWatermark = require 'WFCatWatermark'
	rxTest:expectSingleValue( WFCatWatermark.getWatermarkInfo( catalog, wmId )
		:map( function( watermarkInfo )
			return ( watermarkInfo.originalLinkAvailable )
		end ), true )


end

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

function WFCatBinaryUploaderTest.rxTestNotifyOriginalLinkAvailable_testNightly( rxTest )

	-- This test case verifies that when href available from Oz, WF notifies the client.

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, _, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()

	local WFCatWatermark = require 'WFCatWatermark'
	local emptyTbl = {}

	--<<--------------------------------------------------------------------------->>--
	--- This is THE beginning.---
	-- Even before creating watermark, kick off the getAllWatermarksInfo observer.
	--<<----------------------------------------------------------------------------->>--

	local observer = rxTest:observe(
		WFCatWatermark.getAllWatermarksInfo( catalog )
			:map( function( watermarksInfoList )
				if( next( watermarksInfoList ) == nil ) then
					return watermarksInfoList
				end
				return watermarksInfoList
			end )
			:map( function( watermarksInfoList )
				table.sort( watermarksInfoList, function( a, b )
					return a.displayName < b.displayName
				end )
				return watermarksInfoList
			end ), true )

	-- As there is no watermark, expect the empty Tbl
	observer:expectDataWithinTime( 3, emptyTbl )

	-- Now create first watermark
	local name1 = "NotifyOriginalLinkAvailable-1"

	local settings = {
		opacity = 73,
		rotation = 90,
		position = "center",
		xOffset = 0.23,
		yOffset = 0.40,
		size = 22,
		shadow = {
			enabled = "off"
		},
		rgbColor = { 0.4, 0.33, 0.0 },
		activeWatermarkType = "image"
	}
	local relativePath1 = KSPathUtils.child( 'assets', 'IMG_7706.PNG' )
	local fileName1 = 'IMG_7706.PNG'

	local wmId1
	rxTest:waitFor(
		WFCatWatermark.createWatermark( catalog, name1, settings, relativePath1, fileName1 )
			:tap( function( watermarkId )
				-- capture the watermark id returned, it will be used for validation later.
				wmId1 = watermarkId
			end ) )

	local watermarkListExpected = {}

	-- prepare expected content for validating received content.
	rxTest:expectSingleValue( WFCatWatermark.getWatermarkInfo( catalog, wmId1 )
		:map( function( watermarkInfo )
			watermarkListExpected[#watermarkListExpected + 1] = watermarkInfo
			return true
		end ), true )

	-- set the expectation of originalLinkAvailable = false exclusively
	watermarkListExpected[1].originalLinkAvailable = false

	-- expect the firing should happen for the created watermark
	-- expect that the received data should indicate originalLinkAvailable = false
	observer:expectDataWithinTime( 3, watermarkListExpected )

	-- Now start the uploader. Let the binary sync.
	uploader:startContinuousUploads()

	local isValidOriginalAvailableOnOz
	local docObserver1 = rxTest:observe( catalog:docStore():ongoingDocValue( wmId1 )
		:map( function( docValue )
			if derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href') then
				-- WF-1813 need to check presence of the master link & no "invalid=true" in master link & no sha256 error in importsource
				isValidOriginalAvailableOnOz = WFOzDocUtils.isValidOriginalAvailableOnOz( docValue )

				return isValidOriginalAvailableOnOz
			end
		end ) )
	docObserver1:expectDataWithinTime(360, true)

	-- This time OriginalLinkAvailable must be seen true
	-- set the expectation accordingly.
	watermarkListExpected[1].originalLinkAvailable = true

	-- check does it met the expectation
	observer:expectDataWithinTime( 3, watermarkListExpected )

	-- Now stop the uploader.
	uploader:stopContinuousUploads()

	--<<------------------------------------------------------------->>--

	-- Now create an another watermark text watermark,
	-- Lets name it watermark-2

	--<<------------------------------------------------------------->>--

	local name2 = "NotifyOriginalLinkAvailable-2"
	local wmId2
	local relativePath2 = KSPathUtils.child( 'assets', '1.5MB.jpg' )
	local fileName2 = '1.5MB.jpg'

	rxTest:waitFor(
		WFCatWatermark.createWatermark( catalog, name2, settings, relativePath2, fileName2 )
			:tap( function( watermarkId )
				-- capture the watermark id returned, it will be used for validation later.
				wmId2 = watermarkId
			end ) )

	-- prepare expected content for validating received content.
	rxTest:expectSingleValue( WFCatWatermark.getWatermarkInfo( catalog, wmId2 )
		:map( function( watermarkInfo )
			watermarkListExpected[#watermarkListExpected + 1] = watermarkInfo
			return true
		end ), true )

	-- set the expectation
	-- received data should indicate originalLinkAvailable = false for the second watermark.
	-- and for the first watermark originalLinkAvailable shall continue to show true
	watermarkListExpected[1].originalLinkAvailable = true
	watermarkListExpected[2].originalLinkAvailable = false

	observer:expectDataWithinTime( 3, watermarkListExpected )

	-- Now start the uploader. Let the binary sync.
	uploader:startContinuousUploads()

	local docObserver2 = rxTest:observe( catalog:docStore():ongoingDocValue( wmId2 )
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )
	docObserver2:expectDataWithinTime(360, true)

	-- This time both the watermarks originalLinkAvailable must be seen true
	-- set the expectation accordingly.
	watermarkListExpected[1].originalLinkAvailable = true
	watermarkListExpected[2].originalLinkAvailable = true

	-- check does it met the expectation
	observer:expectDataWithinTime( 3, watermarkListExpected )

	-- All done, happy ending!
	observer:dispose()

end


--==============================================================================
-- @mark Reprioritization
--==============================================================================

function WFCatBinaryUploaderTest.rxTestMockedUploadReprioritization_ReplaceWithHigherPriority( rxTest, fileName )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local testFile = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( testFile ).fileSize

	local contentType = 'image/jpeg'

	local _, mainPartsRequestTemplate = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	-- Define a lower priority asset:

	local lowerPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local lowerPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local lowerPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local lowerPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset.JPG" )
	WFFileUtils.copy( testFile, lowerPriorityOriginalFilePath )

	-- First define the request/response for the request that will be rescheduled.

	local _, lowerPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = lowerPriorityAssetId,
		revisionId = lowerPriorityRevisionId,
	})
	local mainPartsMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			lowerPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )

	mainPartsMockForLowPriorityAsset.notifyWhenHttpRequestIsInProgress = true
	mainPartsMockForLowPriorityAsset.sitInQueueForNSeconds = 3
		-- Delay the scheduling so we have time to reschedule it.

	-- Next define the request/response for the rescheduled request and the follow up requests

	local mainPartsMockForLowPriorityAsset2 = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			lowerPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, lowerPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForLowPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForLowPriorityAsset2.notifyWhenHttpRequestIsInProgress = true

	-- Define the higher priority asset:

	local higherPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local higherPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local higherPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local higherPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset2.JPG" )
	WFFileUtils.copy( testFile, higherPriorityOriginalFilePath )

	local _, higherPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = higherPriorityAssetId,
		revisionId = higherPriorityRevisionId,
	})
	local mainPartsMockForHigherPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			higherPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForHigherPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, higherPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForHigherPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForHigherPriorityAsset.notifyWhenHttpRequestIsInProgress = true

	-- Setup the mocked HTTP responses

	local urlResponses = {
		mainPartsMockForLowPriorityAsset,
		mainPartsMockForHigherPriorityAsset,
		mainUploadMockForHigherPriorityAsset,
		mainCompleteMockForHigherPriorityAsset,
		mainPartsMockForLowPriorityAsset2,
		mainUploadMockForLowPriorityAsset,
		mainCompleteMockForLowPriorityAsset,
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	-- Create the lower priority asset document first and let it start uploading.

	local uploading = rxTest:observe( uploader:isDoingWork() )

	local uploadSuccessStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			lowerPriorityAssetId, lowerPriorityRevisionId, lowerPrioritySha256, lowerPriorityOriginalFilePath ) )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 5, true )

	-- Now create the higher priority asset document.

	local overlay = {
		ratings = {
			[ KSIdGenerator.uuidInLowercaseHex() ] = {
				rating = 5,
			}
		}
	}

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			higherPriorityAssetId, higherPriorityRevisionId, higherPrioritySha256, higherPriorityOriginalFilePath, { overlay = overlay } ) )

	-- Once we create the higher-priority asset, the current upload should be rescheduled.
	-- The order of the requests is what verifies that this test was successful. The request
	-- order in `urlResponses` shows the expected order. First, we'll start to upload the low
	-- priority asset, then will cancel it and upload the high priority asset, then we'll
	-- resume ulpoading the low priority asset.

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	uploadSuccessStream:expectDataWithinTime( 5, higherPriorityAssetId )

	uploadSuccessStream:expectDataWithinTime( 5, lowerPriorityAssetId )

	uploadSuccessStream:dispose()

	httpRequestsProcessed:expectDataWithinTime( 5, 7 )

	httpRequestsProcessed:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadReprioritization_DontCancelInFlightRequest( rxTest, fileName )

	-- for now leave this test doing Oz binary uploads as order of api calls is indeterministic once both uploads are going

	rxTest:setTimeLimit( 30 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	-- Define a lower priority asset:

	local lowerPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local lowerPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local lowerPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local testFile = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local lowerPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset.JPG" )
	WFFileUtils.copy( testFile, lowerPriorityOriginalFilePath )

	local createMasterForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseWithXReadMeta(
			catalogId, lowerPriorityAssetId, lowerPriorityRevisionId, lowerPriorityOriginalFilePath )

	createMasterForLowPriorityAsset.notifyWhenHttpRequestIsInProgress = true
	createMasterForLowPriorityAsset.responseDelayBySeconds = 3
		-- Prevent it from completing right away.

	-- Define the higher priority asset:

	local higherPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local higherPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local higherPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local higherPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset2.JPG" )
	WFFileUtils.copy( testFile, higherPriorityOriginalFilePath )

	local createMasterForHighPriorityAsset = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseWithXReadMeta(
			catalogId, higherPriorityAssetId, higherPriorityRevisionId, higherPriorityOriginalFilePath )

	createMasterForHighPriorityAsset.notifyWhenHttpRequestIsInProgress = true
	createMasterForHighPriorityAsset.responseDelayBySeconds = 3

	-- Setup the mocked HTTP responses

	local urlResponses = {
		createMasterForLowPriorityAsset,
		createMasterForHighPriorityAsset,
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	uploader:DEBUG_setFallbackToOzUploadForOriginalsOnly( true )
		-- code has changed so that if direct upload template link for original binary is missing,
		-- we will reload the asset document to get it. So to keep this test working, which relies
		-- on upload of original to Oz, rather than removing those links from documents, add this hack
		-- where we force fallback mode and do Oz upload. If we remove fallback mode, probably be best
		-- just to remove this test.

	-- Create the lower priority asset document first and let it start uploading.

	local uploading = rxTest:observe( uploader:isDoingWork() )

	local uploadSuccessStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			lowerPriorityAssetId, lowerPriorityRevisionId, lowerPrioritySha256, lowerPriorityOriginalFilePath ) )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 10, true )

	-- Now create the higher priority asset document.

	local overlay = {
		ratings = {
			[ KSIdGenerator.uuidInLowercaseHex() ] = {
				rating = 5,
			}
		}
	}

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			higherPriorityAssetId, higherPriorityRevisionId, higherPrioritySha256, higherPriorityOriginalFilePath, { overlay = overlay } ) )

	-- Once we create the higher-priority asset, the current upload should NOT be
	-- rescheduled before it is already in-flight. It should finish uploading.

	uploadSuccessStream:expectDataWithinTime( 10, lowerPriorityAssetId )

	uploadSuccessStream:expectDataWithinTime( 10, higherPriorityAssetId )

	uploadSuccessStream:dispose()

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	httpRequestsProcessed:expectDataWithinTime( 10, 2 )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadReprioritization_ReplaceWithHigherPriorityWhenCurrentPriorityLowers( rxTest, fileName )

	rxTest:setTimeLimit( 30 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local testFile = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( testFile ).fileSize

	local contentType = 'image/jpeg'

	local _, mainPartsRequestTemplate = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	-- Define the first asset. Though it will eventually have a lower priority, it starts
	-- out with a normal priority.

	local lowerPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local lowerPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local lowerPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local lowerPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset.JPG" )
	WFFileUtils.copy( testFile, lowerPriorityOriginalFilePath )

	-- First define the request/response for the request that will be rescheduled.

	local _, lowerPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = lowerPriorityAssetId,
		revisionId = lowerPriorityRevisionId,
	})
	local mainPartsMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			lowerPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )

	mainPartsMockForLowPriorityAsset.notifyWhenHttpRequestIsInProgress = true
	mainPartsMockForLowPriorityAsset.sitInQueueForNSeconds = 3
		-- Delay the scheduling so we have time to reschedule it.

	-- Next define the request/response for the rescheduled request.
	local mainPartsMockForLowPriorityAsset2 = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			lowerPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, lowerPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForLowPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForLowPriorityAsset2.notifyWhenHttpRequestIsInProgress = true

	-- Define the normal priority asset:

	local normalPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local normalPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local normalPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local normalPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset2.JPG" )
	WFFileUtils.copy( testFile, normalPriorityOriginalFilePath )

	local _, normalPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = normalPriorityAssetId,
		revisionId = normalPriorityRevisionId,
	})
	local mainPartsMockForNormalPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			normalPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForNormalPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, normalPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForNormalPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForNormalPriorityAsset.notifyWhenHttpRequestIsInProgress = true

	-- Setup the mocked HTTP responses

	local urlResponses = {
		mainPartsMockForLowPriorityAsset,
		mainPartsMockForNormalPriorityAsset,
		mainUploadMockForNormalPriorityAsset,
		mainCompleteMockForNormalPriorityAsset,
		mainPartsMockForLowPriorityAsset2,
		mainUploadMockForLowPriorityAsset,
		mainCompleteMockForLowPriorityAsset,
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	-- Create the first normal priority asset document first and let it start uploading.

	local uploading = rxTest:observe( uploader:isDoingWork() )

	local uploadSuccessStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	local ratingId = KSIdGenerator.uuidInLowercaseHex()

	local normalPriorityOverlay = {
		ratings = {
			[ ratingId ] = {
				rating = 5,
			}
		}
	}

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			lowerPriorityAssetId, lowerPriorityRevisionId, lowerPrioritySha256, lowerPriorityOriginalFilePath, { overlay = normalPriorityOverlay } ) )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 10, true )

	-- Now create another normal priority asset document, and modify the existing asset document to lower it's priority.

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			normalPriorityAssetId, normalPriorityRevisionId, normalPrioritySha256, normalPriorityOriginalFilePath, { overlay = normalPriorityOverlay } ) )

	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = lowerPriorityAssetId,
		ratings = {
			[ ratingId ] = {
				rating = 1,
			}
		}
	} ) )

	-- Once we lower the priority of the asset, the current upload should be rescheduled.
	-- The order of the requests is what verifies that this test was successful. The request
	-- order in `urlResponses` shows the expected order. First, we'll start to upload the low
	-- priority asset, then will cancel it and upload the high priority asset, then we'll
	-- resume ulpoading the low priority asset.

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	uploadSuccessStream:expectDataWithinTime( 10, normalPriorityAssetId )

	uploadSuccessStream:expectDataWithinTime( 10, lowerPriorityAssetId )

	uploadSuccessStream:dispose()

	httpRequestsProcessed:expectDataWithinTime( 10, 7 )

	httpRequestsProcessed:dispose()

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadReprioritization_DontCancelForSamePriority( rxTest, fileName )

	rxTest:setTimeLimit( 30 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )

	local testFile = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( testFile ).fileSize

	local contentType = 'image/jpeg'

	local _, mainPartsRequestTemplate = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	-- Define a lower priority asset:

	local lowPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local lowPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local lowPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local lowPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset.JPG" )
	WFFileUtils.copy( testFile, lowPriorityOriginalFilePath )

	local _, lowPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = lowPriorityAssetId,
		revisionId = lowPriorityRevisionId,
	})
	local mainPartsMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			lowPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, lowPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForLowPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForLowPriorityAsset.notifyWhenHttpRequestIsInProgress = true
	mainPartsMockForLowPriorityAsset.sitInQueueForNSeconds = 2
		-- Prevent it from completing right away.

	-- Define another low priority asset:

	local secondLowPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local secondLowPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local secondLowPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local secondLowPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "asset2.JPG" )
	WFFileUtils.copy( testFile, secondLowPriorityOriginalFilePath )

	local _, secondLowPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = secondLowPriorityAssetId,
		revisionId = secondLowPriorityRevisionId,
	})
	local mainPartsMockForSecondLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			secondLowPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForSecondLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, secondLowPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForSecondLowPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForSecondLowPriorityAsset.notifyWhenHttpRequestIsInProgress = true

	-- Setup the mocked HTTP responses

	local urlResponses = {
		mainPartsMockForLowPriorityAsset,
		mainUploadMockForLowPriorityAsset,
		mainCompleteMockForLowPriorityAsset,
		mainPartsMockForSecondLowPriorityAsset,
		mainUploadMockForSecondLowPriorityAsset,
		mainCompleteMockForSecondLowPriorityAsset,
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	-- Create the lower priority asset document first and let it start uploading.

	local uploading = rxTest:observe( uploader:isDoingWork() )

	local uploadSuccessStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			lowPriorityAssetId, lowPriorityRevisionId, lowPrioritySha256, lowPriorityOriginalFilePath ) )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 10, true )

	-- Now create another asset with the same priority.

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			secondLowPriorityAssetId, secondLowPriorityRevisionId, secondLowPrioritySha256, secondLowPriorityOriginalFilePath ) )

	-- Once we create the second asset, the current upload should NOT be
	-- rescheduled before it has the same upload priority.

	uploadSuccessStream:expectDataWithinTime( 10, lowPriorityAssetId )

	uploadSuccessStream:expectDataWithinTime( 10, secondLowPriorityAssetId )

	uploadSuccessStream:dispose()

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	httpRequestsProcessed:expectDataWithinTime( 10, 6 )

end

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

function WFCatBinaryUploaderTest.rxTestMockUploadPrioritization_TimeCriticalUpload( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local originalsTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local testFile = KSPathUtils.child( testDataFolder1, "small.JPG" )
	local fileSize = WFFileUtils.fileAttributes( testFile ).fileSize

	local contentType = 'image/jpeg'

	local _, mainPartsRequestTemplate = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalogId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local assetUploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local assetCompleteUrl = 'main/parts?file_size=' .. fileSize

	-- Define the Time Critical upload:

	local higherPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local higherPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local higherPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local higherPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "assethp.JPG" )
	WFFileUtils.copy( testFile, higherPriorityOriginalFilePath )

	local _, higherPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = higherPriorityAssetId,
		revisionId = higherPriorityRevisionId,
	})
	local mainPartsMockForHigherPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			higherPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForHigherPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, higherPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForHigherPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForHigherPriorityAsset.notifyWhenHttpRequestIsInProgress = true
	mainPartsMockForHigherPriorityAsset.options.bypassRateLimiter = true
	mainUploadMockForHigherPriorityAsset.options.bypassRateLimiter = true
	mainCompleteMockForHigherPriorityAsset.options.bypassRateLimiter = true
		-- This request should bypass the rate limiter because it is critical
		-- This is the KEY.

	-- Define a lower priority asset:

	local lowPriorityAssetId = KSIdGenerator.uuidInLowercaseHex()
	local lowPriorityRevisionId = KSIdGenerator.uuidInLowercaseHex()
	local lowPrioritySha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local lowPriorityOriginalFilePath = KSPathUtils.child( originalsTestFolder, "assetlp.JPG" )
	WFFileUtils.copy( testFile, lowPriorityOriginalFilePath )

	local _, lowPriorityMainPartsRequestUrl = WFURI.replaceFromOzTemplate( mainPartsRequestTemplate, {
		assetId = lowPriorityAssetId,
		revisionId = lowPriorityRevisionId,
	})
	local mainPartsMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetMainPartsMock(
			lowPriorityMainPartsRequestUrl, assetUploadUrl, assetCompleteUrl, fileSize, contentType )
	local mainUploadMockForLowPriorityAsset = WFCatBinaryUploaderTestUtils.generateAssetUploadMock(
			assetUploadUrl, lowPriorityOriginalFilePath, fileSize, contentType )
	local mainCompleteMockForLowPriorityAsset =
			WFCatBinaryUploaderTestUtils.generateAssetCompleteMock( assetCompleteUrl, contentType )

	mainPartsMockForLowPriorityAsset.notifyWhenHttpRequestIsInProgress = true

	local urlResponses = {
		mainPartsMockForHigherPriorityAsset,
		mainUploadMockForHigherPriorityAsset,
		mainCompleteMockForHigherPriorityAsset,
		mainPartsMockForLowPriorityAsset,
		mainUploadMockForLowPriorityAsset,
		mainCompleteMockForLowPriorityAsset,
	}

	-- Setup the mocked HTTP responses

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local uploading = rxTest:observe( uploader:isDoingWork() )

	local uploadSuccessStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	-- Now create the time sensitive asset document.

	local overlay = {
		_localOnly = {
			universal = {
				uploadPriorityModifier = 'CRITICAL'
			}
		}
	}

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			higherPriorityAssetId, higherPriorityRevisionId, higherPrioritySha256, higherPriorityOriginalFilePath, { overlay = overlay } ) )

	-- And the low priority asset:

	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId,
			lowPriorityAssetId, lowPriorityRevisionId, lowPrioritySha256, lowPriorityOriginalFilePath ) )

	uploader:startContinuousUploads()

	uploading:expectDataWithinTime( 5, true )

	-- Make sure the critical binary is uploaded.

	uploadSuccessStream:expectDataWithinTime( 5, higherPriorityAssetId )

	-- And that we succesfully upload a non critical binary afterwards. This guarantees
	-- we're properly clearing the scheduled-and-waiting state in the binary uploader
	-- for critical uploads.

	uploadSuccessStream:expectDataWithinTime( 5, lowPriorityAssetId )

	uploadSuccessStream:dispose()

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	httpRequestsProcessed:expectDataWithinTime( 5, 6 )

end


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

function WFCatBinaryUploaderTest.rxTestUploadPrioritization_TimeCriticalUpload_testNightly( rxTest )

	rxTest:setTimeLimit( 360 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )
	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- Mark the asset as time critical.

	rxTest:waitFor( WFCatPhotoImportance.increaseUploadPriorityForAsset( catalog, assetId, true ) )

	-- Verify there is no master link and that the asset has a priority modifier.

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )

		:map( function( docValue )

			local hasMasterHref = not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href')

			local hasUploadPriorityModifier = not not derefOpt( docValue, '_localOnly', 'universal', 'uploadPriorityModifier' )

			return { hasMasterHref, hasUploadPriorityModifier }

		end ) )

	docObserver:expectDataWithinTime( 10, { false, true } )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	-- Verify there is a link and the priority modifier was removed.

	docObserver:expectDataWithinTime( 360, { true, false } )

	docObserver:dispose()

end

--==============================================================================
-- @mark Background upload
--==============================================================================

function WFCatBinaryUploaderTest.rxTestMockedBackgroundUploadScheduledTasks_testNightly( rxTest )

	rxTest:setTimeLimit( 90 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local assetIdAndRevIds = {}

	for i = 0, 9 do
		for j = 0, 9 do
			local assetId = "docId_" .. tostring( i ) .. tostring( j )
			local revisionId = KSIdGenerator.uuidInLowercaseHex()

			assetIdAndRevIds[ #assetIdAndRevIds + 1 ] = {
				assetId = assetId,
				revisionId = revisionId
			}
		end
	end

	-- urlResponses

	local urlResponses = {}

	-- The first foreground upload
	for i = 1, 6 do

		local assetInfo = assetIdAndRevIds[ i ]
		local assetId = assetInfo.assetId
		local revisionId = assetInfo.revisionId

		local createMasterResponse = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath )

		-- delay the response, so we will have time to cancel it
		createMasterResponse.responseDelayBySeconds = 60

		urlResponses[ #urlResponses + 1 ] = createMasterResponse

	end

	-- upload in background
	for i = 6, 100 do

		local assetInfo = assetIdAndRevIds[ i ]
		local assetId = assetInfo.assetId
		local revisionId = assetInfo.revisionId

		local createMasterResponse = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath, nil, nil, true )

		-- delay the response, so we will have time to cancel it
		createMasterResponse.responseDelayBySeconds = 60

		urlResponses[ #urlResponses + 1 ] = createMasterResponse

	end

	-- upload when we goes back to foreground again, we would dispose all uploads and reschedule
	for i = 1, 6 do

		local assetInfo = assetIdAndRevIds[ i ]
		local assetId = assetInfo.assetId
		local revisionId = assetInfo.revisionId

		local createMasterResponse = WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath )

		-- delay the response, so we will have time to cancel it
		createMasterResponse.responseDelayBySeconds = 60

		urlResponses[ #urlResponses + 1 ] = createMasterResponse

	end

	-- Create catalog, catalogSpec and uploader

	local setupOptions = { enableRateLimiter = true, forceDocIdUploadOrdering = true }
	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses, setupOptions )

	-- need to set this to enable having multiple uploads of a binary with the same sha256 in progress in parallel

	uploader:TEST_getCapabilitiesCache():setCapabilitiesForTests( {
		[ 'WFCatBinaryUploaderTest.debug_allowSameSha256Upload' ] = true,
	} )

	-- Add 100 assets into catalog

	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	for i = 1, 100 do

		local assetInfo = assetIdAndRevIds[ i ]
		local assetId = assetInfo.assetId
		local revisionId = assetInfo.revisionId

		rxTest:waitFor( addIACDocumentWithOzMetadataUpdate( catalog, spaceId, assetId, revisionId, sha256, originalFilePath ) )

	end

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	-- BinariesInFlight is 6 because rateLimiter pool has 5 requests, and one waiting in queue

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 6 )

	rxTest:expectEqual( uploader._numberOfScheduledBinaryUploads, 6 )

	-- Enable background upload would allow all 100 tasks be scheduled

	uploader:enableBackgroundUpload( true )

	observeBinariesInFlight:expectDataWithinTime( 25, 100 )

	-- Disable background upload would dispose all active uploads. Then reschedule the upload tasks up to rateLimiter limit

	uploader:enableBackgroundUpload( false )

	observeBinariesInFlight:expectDataWithinTime( 5, 6 )

	rxTest:expectEqual( uploader._numberOfScheduledBinaryUploads, 6 )

end

--==============================================================================
-- @mark Auxiliary uploads
--==============================================================================

function WFCatBinaryUploaderTest.rxTestMockedDirectUploadAdd_WithAux( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeOriginal = true, includeAux = true } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedUploadCopy_Aux( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalog, catalogSpec, uploader, assetId = testMockedDirectUploadWithoutADS( rxTest, { includeOriginal = true, includeAux = true } )

	local catalogId = catalog:ozCatalogId()

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.01 )
		:pluck( 'uploadsByType', 'catalog' ) )

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	-- Stop the uploader for further setup.

	uploader:stopContinuousUploads()

	rxTest:sleep( 0.1 ) -- Sleep to let the above call wrap up.

	-- Now Duplicate the Asset. All the binaries in this duplicated asset should
	-- use the *copy* APIs when uploading to Oz.

	local copiedAssetId = rxTest:getFirstValue(
			WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
				:map( function( theAssetIds )
					return theAssetIds[ 1 ]
				end ) )

	-- Make the copied document metadata and original appear synced.

	local revisionId = KSIdGenerator.uuidInLowercaseHex()

	local overlay = WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload(
			{}, catalogId, copiedAssetId, revisionId )

	overlay._id = copiedAssetId
	overlay._newRevId = revisionId

	-- do not want to trigger copy of master, xmp or xmp_develop

	KSTableUtils.setNested(
			overlay, "/href/bogus/original", '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
	KSTableUtils.setNested(
			overlay, "/href/bogus/xmp", '_serverProvidedMetadata', 'links', '/rels/xmp', 'href' )
	KSTableUtils.setNested(
			overlay, "/href/bogus/xmp_develop", '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )

	rxTest:waitFor( catalog:docStore():applyOverlay( overlay ) )

	-- Create some new request/response pairs:

	local copiedDocValue = rxTest:getFirstValue( catalog:docStore():docValue( copiedAssetId ) )
	local assetAuxCopyUrl = WFCatAssetAux.getCopyUrl( copiedDocValue )

	local auxes = { }
	local urlResponses = { }

	for id, info in pairs( derefOpt( copiedDocValue, 'aux' ) ) do
		local auxInfo = {
			id = id,
			sha256 = info.sha256,
		}
		table.insert( auxes, auxInfo )

		local sourceAssetLinkForAux = WFCatAssetAux.getSourceAssetLinkForAuxFromDocValue( copiedDocValue, auxInfo.digest )
		local auxCopyMock = WFCatBinaryUploaderTestUtils.generateAuxCopyMock( assetAuxCopyUrl, sourceAssetLinkForAux, auxInfo.id )
		table.insert( urlResponses, auxCopyMock )
	end

	for _, response in ipairs( urlResponses ) do
		catalogSpec:expectHttpRequest( response )
	end

	-- Start the uploader

	uploader:startContinuousUploads()

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 2, true )

	-- We should now see upload for the duplicated assets auxiliary
	-- It should be a *COPY*

	rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, copiedAssetId, revisionId, auxes[ 1 ].id, auxes[ 1 ].sha256 ) )
	rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, copiedAssetId, revisionId, auxes[ 2 ].id, auxes[ 2 ].sha256 ) )
	rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, copiedAssetId, revisionId, auxes[ 3 ].id, auxes[ 3 ].sha256 ) )
	rxTest:waitFor( addAuxLinksToDoc( catalog, catalogId, copiedAssetId, revisionId, auxes[ 4 ].id, auxes[ 4 ].sha256 ) )

	completionStream:expectDataWithinTime( 5, copiedAssetId )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
	{
		["Oz Copies"] = 4,
		["S3 Uploads"] = 5,
		aux = {
			["S3 Uploads"] = 4,
			["Oz Copies"] = 4,
			total = 8,
		},
		master = {
			["S3 Uploads"] = 1,
			total = 1,
		},
		total = 9,
	} )

end


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

function WFCatBinaryUploaderTest.rxTestMockedDirectUploadAuxRetryOnError( rxTest )

	rxTest:setTimeLimit( 10 )

	-- This test is for bug WF-2129 where the issue was that on first attempt at direct
	-- upload of aux section, a relative path is converted to an absolute path, but if
	-- the first attempt fails, on the second attempt the same concatenation is done AGAIN
	-- without checking if the path is relative or absolute, causing retries to cease
	-- until app relaunch. Was not able to repro this issue with mocks, so instead
	-- artificially putting upload document in that state initially. jkamerer

	testMockedDirectUploadWithoutADS( rxTest, { includeOriginal = true, includeAux = true, useAbsolutePathForAuxRelativePath = true })

end

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

local function createFakeWFCatalogForTestSuite( rxTest )

	local optArgs = {
		keepOpenAfterTestFinishes = true
	}

	if WFPlatformInfo.isMobile() then
		optArgs.indexDefinitions = 'LrmCatalogIndexDefinitions.lua'
	end

	return WFCatMockCatalog.createFakeWFCatalog( rxTest , nil, nil, optArgs )

end

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

function WFCatBinaryUploaderTest.rxTestSkipUploadsForAuxBinariesWithLocalError( rxTest )

	local needsUpload = false

	local emit = function( ... )

		needsUpload = true

	end

	local currentTime = KSDate.timeNow()

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local wfCatalog = createFakeWFCatalogForTestSuite( rxTest )

	-- An asset with local, unsynced masks should need uploading.

	local overrides = {
		hasDevelopAux = true,
		noDevelopAuxLinks = true
	}

	local docValue, expectedData = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, overrides )

	WFCatBinaryUploader._DEBUG_maybeEmitAuxUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( true, needsUpload )

	-- An asset with unsynced masks with errors should not need uploading.

	needsUpload = false -- reset

	-- Adds error to all masks and filters.

	KSTableUtils.setNested( docValue, true, '_localOnly', 'files', 'aux', expectedData.mask1Fingerprint, 'error' )
	KSTableUtils.setNested( docValue, true, '_localOnly', 'files', 'aux', expectedData.mask2Fingerprint, 'error' )
	KSTableUtils.setNested( docValue, true, '_localOnly', 'files', 'aux', expectedData.filter1Fingerprint, 'error' )
	KSTableUtils.setNested( docValue, true, '_localOnly', 'files', 'aux', expectedData.filter2Fingerprint, 'error' )

	WFCatBinaryUploader._DEBUG_maybeEmitAuxUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( false, needsUpload )

end

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

function WFCatBinaryUploaderTest.rxTestTempBinaryLinksForAuxUpload( rxTest )

	local isReadyToUpload = { }

	local emit = function( ... )
		isReadyToUpload[ #isReadyToUpload + 1 ] = arg[ 19 ]
			-- parameter 19 is isReadyToUpload which is what we want to check
	end

	local currentTime = KSDate.timeNow()
	local catalog = createFakeWFCatalogForTestSuite( rxTest )

	-- An asset with local, unsynced masks should need uploading.

	local overrides = {
		hasDevelopAux = true,
		noDevelopAuxLinks = true
	}

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local docValue, expectedData = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, overrides )

	local revisionId = docValue._serverProvidedMetadata.revision_ids[ 1 ]
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( docValue, catalog:ozCatalogId(), assetId, revisionId )

	rxTest:waitFor( catalog:docStore():setDocValue( docValue ) )
	docValue = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )

	-- without temp links these should be ready to upload for both non-ADS and ADS upload
	-- so readyToUpload value should be 1 + 2 = 3. emit should be called twice for the two
	-- aux binaries

	WFCatBinaryUploader._DEBUG_maybeEmitAuxUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( 4, #isReadyToUpload )
	rxTest:expectEqual( 3, isReadyToUpload[ 1 ] )
	rxTest:expectEqual( 3, isReadyToUpload[ 2 ] )
	rxTest:expectEqual( 3, isReadyToUpload[ 3 ] )
	rxTest:expectEqual( 3, isReadyToUpload[ 4 ] )


	-- now test with temp links, should not be ready to upload for atomic develop settings
	-- which means readyToUpload = 1

	isReadyToUpload = { }

	addAuxTempLinkToDoc( catalog, assetId, expectedData.mask1Sha256 )
	addAuxTempLinkToDoc( catalog, assetId, expectedData.mask2Sha256 )
	addAuxTempLinkToDoc( catalog, assetId, expectedData.filter1Sha256 )
	addAuxTempLinkToDoc( catalog, assetId, expectedData.filter2Sha256 )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )

	WFCatBinaryUploader._DEBUG_maybeEmitAuxUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( 4, #isReadyToUpload )
	rxTest:expectEqual( 1, isReadyToUpload[ 1 ] )
	rxTest:expectEqual( 1, isReadyToUpload[ 2 ] )
	rxTest:expectEqual( 1, isReadyToUpload[ 3 ] )
	rxTest:expectEqual( 1, isReadyToUpload[ 4 ] )

end

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

function WFCatBinaryUploaderTest.rxTestTempBinaryLinksForXmpDevelopUpload( rxTest )

	local isReadyToUpload

	local emit = function( ... )
		isReadyToUpload = arg[ 19 ]
			-- parameter 19 is isReadyToUpload which is what we want to check
	end

	local currentTime = KSDate.timeNow()
	local catalog = createFakeWFCatalogForTestSuite( rxTest )

	-- An asset with local, unsynced masks should need uploading.

	local overrides = {
		noDevelopSettingsLink = true
	}

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local docValue, expectedData = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, overrides )

	local revisionId = docValue._serverProvidedMetadata.revision_ids[ 1 ]
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( docValue, catalog:ozCatalogId(), assetId, revisionId )

	rxTest:waitFor( catalog:docStore():setDocValue( docValue ) )
	docValue = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )

	-- without temp links xmp develop should be ready to upload for both non-ADS and ADS upload
	-- so readyToUpload value should be 1 + 2 = 3

	WFCatBinaryUploader._DEBUG_maybeEmitXmpDevelopUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( 3, isReadyToUpload )

	-- now test with temp link, should not be ready to upload for atomic develop settings
	-- so readyToUpload = 1

	isReadyToUpload = nil

	addXmpDevelopTempLinkToDoc( catalog, assetId, expectedData.developSettingsSha256 )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( assetId ) )

	WFCatBinaryUploader._DEBUG_maybeEmitXmpDevelopUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( 1, isReadyToUpload )

end

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

local function addAuxToAsset( catalog, assetDoc, auxBinary, auxType )

	auxType = auxType or 'mask'

	local auxId = KSIdGenerator.uuidInLowercaseHex()
	local auxDigest = string.upper( WFCrypto.MD5.digest( KSIdGenerator.uuidInLowercaseHex() ) )
	local auxGroupDigest = string.upper( WFCrypto.MD5.digest( KSIdGenerator.uuidInLowercaseHex() ) )

	local baseAuxPath = WFCatAssetAux.getBasePath( catalog )
	local auxFilePath, auxRelativeFilePath = WFCatAssetAux.calculatePathUsingDigest( baseAuxPath, auxDigest )

	local auxParentPath = KSPathUtils.parent( auxFilePath )

	WFFileUtils.mkdir( auxParentPath )

	KSData.saveToFile( auxBinary, auxFilePath )

	local auxFileSize = WFFileUtils.fileAttributes( auxFilePath ).fileSize

	local auxSha256 = WFCrypto.digestFile( auxFilePath, 'SHA256' )

	-- Adds file to localonly section and sets up the develop section

	local isFilter = ( auxType == 'filter' )
	
	local auxParamsList = { {
		type = auxType,
		id = auxId, 
		sha256 = auxSha256, 
		filePath = auxRelativeFilePath, 
		fileSize = auxFileSize, 
		digest = auxDigest, 
		groupDigest = isFilter and auxGroupDigest or nil,
		sourceLongEdge = isFilter and 6000 or nil,
		crsSizeX = isFilter and 1024 or nil,
		crsSizeY = isFilter and 768 or nil
	} }
	
	assetDoc = WFCatAssetTestUtils.addAuxiliaryMetadataReference( assetDoc, assetDoc._id, auxParamsList )

	return assetDoc, auxSha256

end

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

--[[!
	\brief Runs end to end tests for upload of xmp develop settings. Tests also
		includes upload of original binary to Oz and optionally upload of mask
		binary.

	\param rxTest (rxTest)
	\param options (optional table) Optional document modifiers:

		atomicDevelopSettings (boolean): if true then enable ADS feature for which
			means xmp develop will use direct upload among other side effects.

		syncMetadataAsUpdate (boolean): if true then test is for workflow where asset
			document has been created on Oz and develop settings are sent in an update.
			When atomicDevelopSettings is set to true, this tests the workflow in which
			aux and xmp develop must sync to Oz as temp binaries before Oz will accept
			the metadata update with the new xmp develop sha256 and/or aux data. Also
			used when atomicDevelopSettings is not set to test workflow in which multiple
			aux binaries with the same sha256 are synced to Oz in a single update.

		maskCountWithSameSHA256 (integer): if not nil or 0, then include the upload of
			the given number of masks in the test. When set to 1, this just tests the
			upload of a mask aux binary. However, setting this to a number greater than
			one is to test edge case workflows in which each mask has a different digest
			but the same sha256. At one point Oz was not handling this well, cauing us
			to add several tests to ensure these workflows are well covered.
		
		includeOneFilter (boolean): if true then test would add 1 filter aux and upload.

		simulateBinaryMissingError (boolean): if true then test that successful upload
			of xmp develop changes BinaryMissingError annotation in asset document to
			DirectUploadComplete annotation.

		inAppContributeRole (string): if non-nil then set test up to push to a space from
			a contributor catalog. Role should be editor or contributor (viewers cannot
			upload binaries).

		missingAuxDCUTemplate (boolean): if true then remove the aux direct upload
			link  to mimic scenario where asset that has not synced from Oz since
			feature shipped needs to reload links before starting direct upload

		missingXmpDevelopDCUTemplate (boolean): if true then remove the xmp develop
			direct upload link  to mimic scenario where asset that has not synced from Oz
			since feature shipped needs to reload links before starting direct upload

	\return catalogTestSession, catalog, catalogSpec, spaceFeedsManager, puller,
			pusher, uploader, assetId, spaceId, auxBinary, xmpDevelopSha256,
			syncDiagnosticsObserver, expectedSyncDiagnostics

--]]
local function testUploadDevelopSettings( rxTest, options )

	rxTest:setTimeLimit( 120 )

	options = options or {}

	if options.maskCountWithSameSHA256 == 0 then
		options.maskCountWithSameSHA256 = nil
	end

	local catalogTestSession, catalog, catalogSpec, catalogId, pusher, puller, uploader, spaceId, spaceFeedsManager =
		setupPusherPullerAndUploaderForNightly( rxTest, options )

	local assetId, _, _, sha256, assetDoc = setupAssetForNightly( rxTest, catalogId )
	assetDoc._spaceId = spaceId

	local docsToPush = { assetDoc }
	if spaceId then
		docsToPush[ #docsToPush + 1 ] = createAlbumAssetDocForSpaceAsset( rxTest, catalog, spaceId, assetId )
			-- asset will not show up in space feed unless both asset and album_asset documents are pushed
	end

	if options.syncMetadataAsUpdate then

		-- binaries sync first test requires upload of first revision before adding develop settings
		rxTest:waitFor( catalog:docStore():bulkSetDocValue( docsToPush ) )

		-- start up sync engine (but not binary uploader yet)
		if spaceFeedsManager then
			spaceFeedsManager:startSendingUpdates()
		else
			rxTest:waitFor( pusher:sendUpdatesContinuously() )
			puller:getUpdatesContinuously()
		end

		-- wait for initial revision to sync to the cloud (and back)
		local assetObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:map( function( docValue )
				return not not docValue._serverProvidedMetadata
			end ) )
		assetObserver:expectDataWithinTime( 60, true )

		if options.missingAuxDCUTemplate or options.missingXmpDevelopDCUTemplate then

			local overlay = {
				_id = assetId,
				_serverProvidedMetadata = {
					links = {
					},
				},
			}

			if options.missingAuxDCUTemplate then
				overlay._serverProvidedMetadata.links[ '/rels/aux_create/parts' ] = { _delete = true }
			end

			if options.missingXmpDevelopDCUTemplate then
				overlay._serverProvidedMetadata.links[ '/rels/xmp_develop_create/parts' ] = { _delete = true }
			end

			rxTest:waitFor( catalog:docStore():applyOverlay( overlay ) )

			-- this removal should not cause the pusher to do work.
			-- Link should not refresh until binary uploader starts working.

			local isPushing
			if spaceFeedsManager then
				isPushing = rxTest:observe( spaceFeedsManager:isUploadingMetadata() )
			else
				isPushing = rxTest:observe( pusher:isDoingWork() )
			end
			isPushing:expectDataWithinTime( 5, false )

			-- ensure all removed links are still missing

			local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
				:map( function( doc )
					local links = derefOpt( doc, '_serverProvidedMetadata', 'links' )
					return ( not options.missingAuxDCUTemplate or not links[ ' /rels/aux_create/parts' ] ) and
						( not options.missingXmpDevelopDCUTemplate or not links[ '/rels/xmp_develop_create/parts' ] )
				end )
			)
			docObserver:expectDataWithinTime( 5, true )

		end

		-- turn assetDoc into an overlay that masks can be added to
		assetDoc = {
			_id = assetId,
		}

	end

	local maskSha256 = 'temp'
	local maskBinary = 'temp'

	if options.maskCountWithSameSHA256 then

		-- This should be close to random
		maskBinary = KSData.make( KSIdGenerator.uuidInLowercaseHex() )

		for i = 1, options.maskCountWithSameSHA256 do
			assetDoc, maskSha256 = addAuxToAsset( catalog, assetDoc, maskBinary )
		end

		if options.syncMetadataAsUpdate then

			-- asset already in doc store, need to apply overlay
			rxTest:waitFor( catalog:docStore():applyOverlay( assetDoc ) )

		end

	end
	
	local filterSha256 = 'temp'
	local filterBinary = 'temp'
	
	if options.includeOneFilter then

		-- This should be close to random
		filterBinary = KSData.make( KSIdGenerator.uuidInLowercaseHex() )

		assetDoc, filterSha256 = addAuxToAsset( catalog, assetDoc, filterBinary, 'filter' )

		if options.syncMetadataAsUpdate then

			-- asset already in doc store, need to apply overlay
			rxTest:waitFor( catalog:docStore():applyOverlay( assetDoc ) )

		end

	end

	if not options.syncMetadataAsUpdate then

		-- already done earlier for binaries sync before metadata tests
		rxTest:waitFor( catalog:docStore():bulkSetDocValue( docsToPush ) )

	end

	if options.simulateBinaryMissingError then

		-- test that completion of direct upload of aux or xmp_develop will clear
		-- BinaryMissingError annotation from the asset document

		applyErrorOverlay( rxTest, catalog, assetId, 'BinaryMissingError' )

	end

	local carouselDocuments = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	local xmpDevelopFilePath = KSPathUtils.child( carouselDocuments, 'small.XMP' )
	local _, xmpSha256 =  WFCatAssetTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 1 ) )

	local uploadSummaryStr =  "Uploaded 0 binaries to Oz."
	local uploadsByType = { }
	local recentlyUploaded = { }
	local expectedSyncDiagnostics = {
		documentsNeedingBinaryUpload = 2,
		inFlightUploads = {},
		isCurrentlySyncing = false,
		numberOfInFlightUploads = 0,
		numberOfScheduledBinaryUploads = 0,
		recentlyUploaded = recentlyUploaded,
		summaryStr = "Catalog: " .. uploadSummaryStr,
		uploadsByType = uploadsByType,
	}

	if options.maskCountWithSameSHA256 then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload =
			expectedSyncDiagnostics.documentsNeedingBinaryUpload + options.maskCountWithSameSHA256
	end
	
	if options.includeOneFilter then
		expectedSyncDiagnostics.documentsNeedingBinaryUpload =
			expectedSyncDiagnostics.documentsNeedingBinaryUpload + 1
	end

	syncDiagnosticsObserver:expectDataWithinTime( 30, expectedSyncDiagnostics )

	-- start binary uploader (and sync engine in case it wasn't aready started)

	if spaceFeedsManager then
		spaceFeedsManager:startSendingUpdates()
	else
		rxTest:waitFor( pusher:sendUpdatesContinuously() )
		puller:getUpdatesContinuously()
	end
	uploader:startContinuousUploads()

	if options.missingAuxDCUTemplate or options.missingAuxDCUTemplate then

		-- binary upload links should reload and be available before binary upload starts

		local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:map( function( doc )
				local links = derefOpt( doc, '_serverProvidedMetadata', 'links' )
				return ( not not links[ '/rels/aux_create/parts' ] and not not links[ '/rels/xmp_develop_create/parts' ] )
			end )
		)
		docObserver:expectDataWithinTime( 5, true )

	end

	-- make sure binaries all uploaded via direct upload

	expectedSyncDiagnostics.documentsNeedingBinaryUpload = 0
	expectedSyncDiagnostics.isCurrentlySyncing = true
	recentlyUploaded[ assetId ] = {
		aux = {
			[ maskSha256 ] = true,
			[ filterSha256 ] = true,
		},
		master = {
			[ sha256 ] = true,
		},
		xmp_develop = {
			[ xmpSha256 ] = true
		}
	}
	uploadSummaryStr = "Uploaded 4 binaries to Oz."
	uploadsByType = {
		["S3 Uploads"] = 3,
		aux = {
			["S3 Uploads"] = 2,
			total = 2,
		},
		master = {
			["S3 Uploads"] = 1,
			total = 1,
		},
		total = 4,
		xmp_develop = {
			total = 1
		}
	}

	if not ( options.maskCountWithSameSHA256 or options.includeOneFilter ) then
		recentlyUploaded[ assetId ].aux = nil
		uploadSummaryStr = "Uploaded 2 binaries to Oz."
		uploadsByType.aux = nil
		uploadsByType[ "S3 Uploads" ] = 1
		uploadsByType.total = 2
	elseif options.maskCountWithSameSHA256 and not options.includeOneFilter then
		recentlyUploaded[ assetId ].aux[ filterSha256 ] = nil
		uploadSummaryStr = "Uploaded 3 binaries to Oz."
		uploadsByType.aux = {
			["S3 Uploads"] = 1,
			total = 1,
		}
		uploadsByType[ "S3 Uploads" ] = 2
		uploadsByType.total = 3
	elseif not options.maskCountWithSameSHA256 and options.includeOneFilter then
		recentlyUploaded[ assetId ].aux[ maskSha256 ] = nil
		uploadSummaryStr = "Uploaded 3 binaries to Oz."
		uploadsByType.aux = {
			["S3 Uploads"] = 1,
			total = 1,
		}
		uploadsByType[ "S3 Uploads" ] = 2
		uploadsByType.total = 3
	end

	if options.atomicDevelopSettings then
		uploadsByType[ "S3 Uploads" ] = uploadsByType[ "S3 Uploads" ] + 1
		uploadsByType.xmp_develop[ "S3 Uploads" ] = 1
	end

	if spaceId then

		-- direct upload not supported for originals in spaces
		uploadsByType[ 'S3 Uploads' ] = uploadsByType[ 'S3 Uploads' ] - 1
		uploadsByType.master["S3 Uploads"] = nil

		expectedSyncDiagnostics.summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": " .. uploadSummaryStr
		expectedSyncDiagnostics.uploadsByType[ spaceId ] = uploadsByType

	else

		expectedSyncDiagnostics.summaryStr = "Catalog: " .. uploadSummaryStr
		expectedSyncDiagnostics.uploadsByType.catalog = uploadsByType

	end

	if uploadsByType[ "S3 Uploads" ] == 0 then
		uploadsByType[ "S3 Uploads" ] = nil
	end

	if options.atomicDevelopSettings and ( options.maskCountWithSameSHA256 or options.includeOneFilter ) 
			and not options.syncMetadataAsUpdate then

		-- Oz is keeping the requirement that WF upload aux first and xmp_develop second when
		-- aux and xmp appear in initial revision. For ADS, WF is basically ignoring this
		-- requirement and achieving it by attempting upload of xmp_develop, getting a 403
		-- on the GET parts API cal and rescheduling the attempt with a delay, which is a bit
		-- chatty BUT it works and this is an edge case as xmp_develop and aux will not
		-- typically be on first revision synced to Oz. This chattiness makes the state of
		-- the binary upload sync diagonstics difficult to predict, so skip this check. The
		-- isAssetDocFullySyncedWithOz check confirms that the upload completed successfully.

	else

		syncDiagnosticsObserver:expectDataWithinTime( 90, expectedSyncDiagnostics )

	end

	-- Make sure everything uploaded correctly

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )

		:map( function( docValue )

			return WFOzDocUtils.isAssetDocFullySyncedWithOz( docValue )

		end ) )

	docObserver:expectDataWithinTime( 90, true )

	-- check that BinaryMissingError annotation was cleared if that is expected

	if options.simulateBinaryMissingError then

		local expectedValue = {
			subtype = 'DirectUploadComplete',
			status = 202,
			retries = 1,
		}

		rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
			:map( function( docValue )
				return {
					subtype = KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'subtype' ),
					status = KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'status' ),
					retries = KSTableUtils.derefOpt( docValue, '_localOnly', 'ozError', 'retries' ),
				}
			end ),
		expectedValue )

	end

	return {

		catalogTestSession = catalogTestSession,
			-- needs to be returned to keep it from getting garbage collected for any tests that
			-- call this function and continue to do additional test steps

		catalog = catalog,

		catalogSpec = catalogSpec,
			-- also needs to be returned to avoid GC.

		spaceFeedsManager = spaceFeedsManager,
			-- also needs to be returned to avoid GC.

		puller = puller,
		pusher = pusher,
		uploader = uploader,
		assetId = assetId,
		spaceId = spaceId,
		maskBinary = maskBinary,
		xmpDevelopSha256 = xmpSha256,

		syncDiagnosticsObserver = syncDiagnosticsObserver,
		expectedSyncDiagnostics = expectedSyncDiagnostics,

	}

end

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

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

--[[!
	\brief Runs end to end tests for upload of second xmp develop settings binary
		after one was already uploaded on an earlier revision. (Almost) always tests
		with atomicDevelopSettings enabled (see options).

	\param rxTest (rxTest)
	\param options (optional table) Optional document modifiers, mostly handled by
		and documented in method testUploadDevelopSettings() aside from:

		metadataSyncedFirst (boolean): if true then metadata for second xmp develop
			syncs to Oz with ADS disabled, before the binary, but then ADS is enabled
			for binary uploader before binaries are uploaded. see WF-2429

	\return catalogTestSession, catalog, catalogSpec, spaceFeedsManager, puller,
			pusher, uploader, assetId, spaceId, auxBinary, xmpDevelopSha256,
			syncDiagnosticsObserver, expectedSyncDiagnostics

--]]
local function testXmpDevelopOtherExternalAlreadyUploaded( rxTest, options )

	options = options or {}
	options.atomicDevelopSettings = not options.metadataSyncedFirst

	-- first run test which will upload an external xmp develop to the asset

	local objects = testUploadDevelopSettings( rxTest, options )

	local catalog = objects.catalog
	local uploader = objects.uploader
	local assetId = objects.assetId
	local spaceId = objects.spaceId

	if options.metadataSyncedFirst then

		uploader:stopContinuousUploads()
		uploader:TEST_getCapabilitiesCache():setCapabilitiesForTests {
			[ 'WFCatBinaryUploader.atomicDevelopSettings' ] = true,
		}

	end

	-- easiest way to setup a second external xmp develop is to create a new asset with a randomized one
	-- configured and copy over the relevant pieces to the test asset

	local dummyAsset, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog )
	local xmpDevelopSha256 = localOnly.developSettingsSha256

	local overlay = {
		_id = assetId,
		_localOnly = {
			files = {
				xmp_develop = dummyAsset._localOnly.files.xmp_develop,
			}
		},
		develop = dummyAsset.develop,
	}
	rxTest:waitFor( catalog:docStore():applyOverlay( overlay ) )

	if options.metadataSyncedFirst then

		-- wait for metadata sync before starting binary uploader

		local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:map( function( docValue )
				return derefOpt( docValue, 'develop', 'xmpCameraRaw', 'sha256' ) == xmpDevelopSha256 and
					derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'invalid' )
			end ) )
		docObserver:expectDataWithinTime( 15, true )

		uploader:startContinuousUploads()

	end

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 30, true )

	local syncDiagnosticsObserver = objects.syncDiagnosticsObserver
	local expectedSyncDiagnostics = objects.expectedSyncDiagnostics

	expectedSyncDiagnostics.documentsNeedingBinaryUpload = 1

	if options.metadataSyncedFirst then

		-- stopping and starting the binary uploader clears the previous binaries out of recently uploaded
		-- so only expect the most recent xmp_develop
		expectedSyncDiagnostics.recentlyUploaded[ assetId ] = {
			xmp_develop = {
				[ xmpDevelopSha256 ] = true
			}
		}

	else

		expectedSyncDiagnostics.recentlyUploaded[ assetId ].xmp_develop[ xmpDevelopSha256 ] = true

	end

	if spaceId then

		expectedSyncDiagnostics.summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 3 binaries to Oz."

		if not options.metadataSyncedFirst then
			expectedSyncDiagnostics.uploadsByType[ spaceId ][ 'S3 Uploads' ] = 2
		end
		expectedSyncDiagnostics.uploadsByType[ spaceId ].total = 3

		if not options.metadataSyncedFirst then
			expectedSyncDiagnostics.uploadsByType[ spaceId ].xmp_develop[ 'S3 Uploads' ] = 2
		end
		expectedSyncDiagnostics.uploadsByType[ spaceId ].xmp_develop.total = 2

	else

		expectedSyncDiagnostics.summaryStr = "Catalog: Uploaded 3 binaries to Oz."

		if not options.metadataSyncedFirst then
			expectedSyncDiagnostics.uploadsByType.catalog[ 'S3 Uploads' ] = 3
		end
		expectedSyncDiagnostics.uploadsByType.catalog.total = 3

		if not options.metadataSyncedFirst then
			expectedSyncDiagnostics.uploadsByType.catalog.xmp_develop[ 'S3 Uploads' ] = 2
		end
		expectedSyncDiagnostics.uploadsByType.catalog.xmp_develop.total = 2

	end

	syncDiagnosticsObserver:expectDataWithinTime( 60, expectedSyncDiagnostics )

	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'sha256' )
	):expectDataWithinTime( 90, xmpDevelopSha256 )

	uploading:expectDataWithinTime( 30, false )

	return objects

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_WithAux_testNightly( rxTest )

	-- Basic test for asset with auxiliaries on the current document which need
	-- to be uploaded using the Oz-based direct upload APIs

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 1, includeOneFilter = true } )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_AuxAndXmpDirect_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 1, includeOneFilter = true, atomicDevelopSettings = true } )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_AuxAndXmpDirect_IAC_editor_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 1, atomicDevelopSettings = true, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_AuxAndXmpDirect_IAC_contributor_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 1, atomicDevelopSettings = true, inAppContributeRole = 'contributor' } )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_XmpDirect_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettingsWithMasks_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, maskCountWithSameSHA256 = 1 } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_missingAuxDCUTemplate_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, maskCountWithSameSHA256 = 1,
		missingAuxDCUTemplate = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_missingXmpDevelopDCUTemplate_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, maskCountWithSameSHA256 = 1,
		missingXmpDevelopDCUTemplate = true } )

end

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

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

function WFCatBinaryUploaderTest.DISABLED_rxTestAtomicDevelopSettings_xmpDevelop_otherExternalAlreadyUploaded_testNightly( rxTest )

	testXmpDevelopOtherExternalAlreadyUploaded( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_xmpDevelop_metadataSyncedFirst_testNightly( rxTest )

	testXmpDevelopOtherExternalAlreadyUploaded( rxTest, { metadataSyncedFirst = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_IAC_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_IAC_missingAuxDCUTemplate_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, maskCountWithSameSHA256 = 1,
		missingAuxDCUTemplate = true, inAppContributeRole = 'editor', } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_IAC_missingXmpDevelopDCUTemplate_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, maskCountWithSameSHA256 = 1,
		missingXmpDevelopDCUTemplate = true, inAppContributeRole = 'editor', } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_IAC_xmpDevelop_otherExternalAlreadyUploaded_testNightly( rxTest )

	testXmpDevelopOtherExternalAlreadyUploaded( rxTest, { inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_IAC_xmpDevelop_metadataSyncedFirst_testNightly( rxTest )

	testXmpDevelopOtherExternalAlreadyUploaded( rxTest, { metadataSyncedFirst = true, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_BinaryMissingError_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, simulateBinaryMissingError = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAtomicDevelopSettings_BinaryMissingError_IAC_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { atomicDevelopSettings = true, syncMetadataAsUpdate = true, simulateBinaryMissingError = true, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestDevelopSettingsForceMetadataPushRetry_testNightly( rxTest )

	-- WF-2410: This test case verifies after max wait time, if develop setting file upload
	-- (either aux or xmp_develop) has not had confirmation via metadata sync down that binary
	-- was successful when atomic develop settings is enabled, WF will apply a _localOnly
	-- annotation to try to trigger a metadata push since binaries need to upload before
	-- metadata with ADS so it is possible that pusher has already tried to push metadata
	-- and failed and may not retry without this.

	rxTest:setTimeLimit( 360 )

	local uploaderOpts = {
		uploadBlackoutWindowInSeconds = 5,
		uploadBlackoutWindowCount = 0,
	}
	local _, catalog, _, catalogId, pusher, puller, uploader =
		setupPusherPullerAndUploaderForNightly( rxTest, { uploaderOpts = uploaderOpts, atomicDevelopSettings = true, } )

	-- setup asset with (eventually) only an aux binary to upload

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )

	local auxBinary = KSData.make( KSIdGenerator.uuidInLowercaseHex() )
	assetDoc = addAuxToAsset( catalog, assetDoc, auxBinary )
	local auxId = next( assetDoc.aux )

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local testTempFolder = WFTestFileUtils.createTestFolder( rxTest )
	local xmpDevelopFilePath = KSPathUtils.child( testTempFolder, 'small.XMP' )
	local xmpDevelopFileSize = generateSidecarFile( rxTest, xmpDevelopFilePath )
	local xmpDevelopSha256 = WFCrypto.digestFile( xmpDevelopFilePath, 'SHA256' )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

	-- start up metadata sync

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()

	-- wait for metadata sync of asset

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return not not derefOpt( docValue, '_serverProvidedMetadata' )
		end ) )
	docObserver:expectDataWithinTime( 60, true )

	-- add version with xmp develop to doc store. Initial metadata will sync after binary.

	local versionId = rxTest:getFirstValue(
		WFCatVersions.createVersionForAsset( catalog, assetId, false, 'Simple Version' )
	)

	-- remove develop section from asset just to make test cleaner... asset only
	-- uploads aux binary and version only uploads xmp_develop binary and both
	-- are annotated when link does not sync back down.

	rxTest:waitFor(
		catalog:docStore():applyOverlay {
			_id = assetId,
			develop = { _delete = true },
		}
	)

	-- wait for metadata sync to Oz of develop removal. This is because original binary
	-- will not sync until all metadata is synced to Oz and version xmp_develop will
	-- not sync until all binaries have synced for the parent asset.

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			local revision_ids = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
			return revision_ids and #revision_ids == 2
		end ) )
	docObserver:expectDataWithinTime( 60, true )

	-- stop the pusher and puller

	pusher:stopContinuousUpdates()
	puller:stopContinuousUpdates()

	-- start uploading binaries to trigger the _localOnly

	uploader:startContinuousUploads()

	-- wait for _localOnly annotation for asset

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return derefOpt( docValue, '_localOnly', 'ozError', 'subtype' ) == 'DirectUploadComplete'
		end ) )
	docObserver:expectDataWithinTime( 60, true )

	-- sync asset metadata down to confirm aux upload and unblock upload of xmp_develop for version
	-- DO NOT start pusher so that version will not sync to Oz and error annotation will be written
	-- to it.

	puller:getUpdatesContinuously()

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/aux/' .. auxId )
		end ) )
	docObserver:expectDataWithinTime( 60, true )

	-- wait for _localOnly annotation for version

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( versionId )
		:map( function( docValue )
			return derefOpt( docValue, '_localOnly', 'ozError', 'subtype' ) == 'DirectUploadComplete'
		end ) )
	docObserver:expectDataWithinTime( 60, true )

end

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

function WFCatBinaryUploaderTest.rxTestApplyOverlayForBinaryUploadBloackWindowEdgeCases( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	-- setup uploader with atomicDevelopSettings enabled

	local uploaderOpts = {
		uploadBlackoutWindowInSeconds = 1,
		uploadBlackoutWindowCount = 0,
	}
	local catalog, catalogSpec, uploader =
		setupMockedCatalogAndUploader( rxTest, catalogId, {}, { uploaderOpts = uploaderOpts, atomicDevelopSettings = true, } )

	-- create asset with xmp develop to upload

	local assetDocOpts = {
		noDevelopSettingsLink = true
	}
	local assetDoc, localOnly = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, assetId, assetDocOpts )
	local revisionId = assetDoc._newRevId
	WFCatAssetTestUtils.addBasicOzMetadataUpdateForDirectUpload( assetDoc, catalogId, assetId, revisionId )

	-- add document to the doc store

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	-- register upload mocks

	local mocks = {}
	WFCatBinaryUploaderTestUtils.getXmpDevelopDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks,
			{ atomicDevelopSettings = true, } )
	for _, mock in ipairs( mocks ) do
		catalogSpec:expectHttpRequest( mock )
	end

	uploader:startContinuousUploads()

	-- wait for _localOnly annotation from xmp develop upload

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return derefOpt( docValue, '_localOnly', 'ozError', 'subtype' ) == 'DirectUploadComplete' and
					derefOpt( docValue, '_localOnly', 'ozError', 'status' ) == 202 and
					derefOpt( docValue, '_localOnly', 'ozError', 'retries' ) == 1
		end ) )
	docObserver:expectDataWithinTime( 60, true )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopy_Aux_testNightly( rxTest )

	rxTest:setTimeLimit( 240 )

	local objects = testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 1 } )

	local catalog = objects.catalog
	local uploader = objects.uploader
	local assetId = objects.assetId

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 1 )
		:pluck( 'uploadsByType', 'catalog' ) )

	-- wait for xmp sidecar link to be available on original assets to avoid race
	-- condition failures on mobile platforms which do not upload xmp sidecar

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp' )
		end ) )

	docObserver:expectDataWithinTime(60, true)

	-- Delete all local binaries from the asset. This forces duplicated
	-- assets to use the copy APIs.

	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = assetId,
		_localOnly = { _delete = true }
	} ) )

	-- Now Duplicate the Asset. All the binaries in this duplicated asset should
	-- use the *copy* APIs when uploading to Oz.

	local copiedAssetId = rxTest:getFirstValue(
			WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
				:map( function( theAssetIds )
					return theAssetIds[ 1 ]
				end ) )

	local uploadFinishedObserver = rxTest:observe( uploader:DEBUG_uploadFinishedStream()
		:map(function( d )
			return d and d.binaryDetails.type or nil
		end )
		:distinctUntilChanged()
	)

	uploadFinishedObserver:expectDataWithinTime( 30, 'aux' )
	uploadFinishedObserver:expectDataWithinTime( 30, 'xmp_develop' )
	uploadFinishedObserver:expectDataWithinTime( 60, 'master' )

	syncDiagnosticsObserver:expectDataWithinTime( 60,
		{
			["Oz Copies"] = 4,
			["S3 Uploads"] = 2,
			aux = {
				["Oz Copies"] = 1,
				["S3 Uploads"] = 1,
				total = 2,
			},
			master = {
				["Oz Copies"] = 1,
				["S3 Uploads"] = 1,
				total = 2,
			},
			total = 7,
			xmp_develop = {
				["Oz Copies"] = 1,
				total = 2,
			},
			xmp_sidecar = {
				["Oz Copies"] = 1,
				total = 1,
			},
		} )

	-- Make sure everything uploaded correctly

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId )

		:map( function( docValue )

			return WFOzDocUtils.isAssetDocFullySyncedWithOz( docValue )

		end ) )

	docObserver:expectDataWithinTime( 120, true )

end

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

function WFCatBinaryUploaderTest.rxTestUploadCopyACopy_Aux_testNightly( rxTest )

	rxTest:setTimeLimit( 240 )

	local objects = testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 1 } )

	local catalog = objects.catalog
	local uploader = objects.uploader
	local assetId = objects.assetId

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 1 )
		:pluck( 'uploadsByType', 'catalog' ) )

	-- wait for xmp sidecar link to be available on original assets to avoid race
	-- condition failures on mobile platforms which do not upload xmp sidecar

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:map( function( docValue )
			return not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp' )
		end ) )

	docObserver:expectDataWithinTime(60, true)

	-- Delete all local binaries from the asset. This forces duplicated
	-- assets to use the copy APIs.

	rxTest:waitFor( catalog:docStore():applyOverlay( {
		_id = assetId,
		_localOnly = { _delete = true }
	} ) )

	-- Now duplicate the Asset. All the binaries in this duplicated asset should
	-- use the *copy* APIs when uploading to Oz.

	local copiedAssetId = rxTest:getFirstValue(
			WFCatCopies.createCopiesOfAssets( catalog, { assetId } )
				:map( function( theAssetIds )
					return theAssetIds[ 1 ]
				end ) )

	-- wait for xmp sidecar link to be available on duplicated document

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId )
		:map( function( docValue )
			return not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp' )
		end ) )

	docObserver:expectDataWithinTime(60, true)

	-- Now duplicate the duplicated asset.

	local copiedAssetId2 = rxTest:getFirstValue(
			WFCatCopies.createCopiesOfAssets( catalog, { copiedAssetId } )
				:map( function( theAssetIds )
					return theAssetIds[ 1 ]
				end ) )

	-- Wait for the uploads to finish

	syncDiagnosticsObserver:expectDataWithinTime( 120,
		{
			["Oz Copies"] = 8,
			["S3 Uploads"] = 2,
			aux = {
				["Oz Copies"] = 2,
				["S3 Uploads"] = 1,
				total = 3,
			},
			master = {
				["Oz Copies"] = 2,
				["S3 Uploads"] = 1,
				total = 3,
			},
			total = 11,
			xmp_develop = {
				["Oz Copies"] = 2,
				total = 3,
			},
			xmp_sidecar = {
				["Oz Copies"] = 2,
				total = 2,
			},
		} )

	-- Make sure everything uploaded correctly

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId )

		:map( function( docValue )

			return WFOzDocUtils.isAssetDocFullySyncedWithOz( docValue )

		end ) )

	docObserver:expectDataWithinTime( 120, true )

	docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( copiedAssetId2 )

		:map( function( docValue )

			return WFOzDocUtils.isAssetDocFullySyncedWithOz( docValue )

		end ) )

	docObserver:expectDataWithinTime( 120, true )

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_WithAux_Multiple_testNightly( rxTest )

	-- Basic test for asset with auxiliaries on the current document which need to be uploaded

	rxTest:setTimeLimit( 560 )

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, originalFilePath, sha256, assetDoc = setupAssetForNightly( rxTest, catalogId )

	-- Add mask to assetDoc
	local auxId = KSIdGenerator.uuidInLowercaseHex()
	local auxDigest = string.upper( WFCrypto.MD5.digest( KSIdGenerator.uuidInLowercaseHex() ) )
	local auxType = "mask"

	-- This should be close to random
	local auxBinary = KSData.make( auxDigest )

	local baseAuxPath = WFCatAssetAux.getBasePath( catalog )
	local auxFilePath, auxRelativeFilePath = WFCatAssetAux.calculatePathUsingDigest( baseAuxPath, auxDigest )
	local auxParentPath = KSPathUtils.parent( auxFilePath )
	WFFileUtils.mkdir( auxParentPath )
	KSData.saveToFile( auxBinary, auxFilePath )
	local auxFileSize = WFFileUtils.fileAttributes( auxFilePath ).fileSize
	local auxSha256 = WFCrypto.digestFile( auxFilePath, 'SHA256' )

	-- Add mask2 to assetDoc
	local aux2Id = KSIdGenerator.uuidInLowercaseHex()
	local aux2Digest = string.upper( WFCrypto.MD5.digest( KSIdGenerator.uuidInLowercaseHex() ) )

	-- This should be close to random
	local aux2Binary = KSData.make( aux2Digest )
	local aux2FilePath, aux2RelativeFilePath = WFCatAssetAux.calculatePathUsingDigest( baseAuxPath, aux2Digest )
	local aux2ParentPath = KSPathUtils.parent( aux2FilePath )
	WFFileUtils.mkdir( aux2ParentPath )
	KSData.saveToFile( aux2Binary, aux2FilePath )
	local aux2FileSize = WFFileUtils.fileAttributes( aux2FilePath ).fileSize
	local aux2Sha256 = WFCrypto.digestFile( aux2FilePath, 'SHA256' )

	-- Adds file to localOnly subsection
	assetDoc = WFCatAssetTestUtils.addOriginalPathToDocument(assetDoc, sha256, originalFilePath)

	-- Adds file to localonly section and sets up the develop section

	local auxParamsList = {
		{ id = auxId, sha256 = auxSha256, filePath = auxRelativeFilePath, fileSize = auxFileSize, digest = auxDigest, type = auxType },
		{ id = aux2Id, sha256 = aux2Sha256, filePath = aux2RelativeFilePath, fileSize = aux2FileSize, digest = aux2Digest, type = auxType },
	}
	assetDoc = WFCatAssetTestUtils.addAuxiliaryMetadataReference( assetDoc, assetId, auxParamsList )

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local xmpDevelopFilePath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'carouselDocuments' ), 'small.XMP' )

	local _, xmpSha256 =  WFCatAssetTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )

	local uploadFinishedObserver = rxTest:observe( uploader:DEBUG_uploadFinishedStream()
		:map(function( d )
			return d and d.binaryDetails.type or nil
		end )
		:distinctUntilChanged()

	)

	-- This test is not interested in the intermediate states of
	-- the upload process. It only cares about the ordering of the
	-- individual binaries and the final state of the system. Here
	-- we modify the syncDiagnostics stream to maintain _all_
	-- changes to its reported document - so that deletions to
	-- "recentlyUploaded" do not effect the end testable state
	local appendOnlyDiagnostics = {}
	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 1 )
		:map( function( diagnostics )
			appendOnlyDiagnostics = WFTableMerge.applyOverlay( appendOnlyDiagnostics, diagnostics )
			return {
				documentsNeedingBinaryUpload = appendOnlyDiagnostics.documentsNeedingBinaryUpload,
				isCurrentlySyncing = appendOnlyDiagnostics.isCurrentlySyncing,
				recentlyUploaded = appendOnlyDiagnostics.recentlyUploaded,
				summaryStr       = appendOnlyDiagnostics.summaryStr,
				uploadsByType    = appendOnlyDiagnostics.uploadsByType
			}
		end ) )

	syncDiagnosticsObserver:expectDataWithinTime( 30,
		{
			documentsNeedingBinaryUpload = 4,
			isCurrentlySyncing = false,
			recentlyUploaded = {},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
			uploadsByType = {}
		} )

	-- start up sync engine
	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	-- Ensure that aux binaries are uploaded first, order independent

	uploadFinishedObserver:expectDataWithinTime(90, 'aux')
	uploadFinishedObserver:expectDataWithinTime(90, 'aux')

	-- upload remaining binaries

	uploadFinishedObserver:expectDataWithinTime(90, 'xmp_develop')
	uploadFinishedObserver:expectDataWithinTime(90, 'master')

	syncDiagnosticsObserver:expectDataWithinTime( 120,
		{
			documentsNeedingBinaryUpload = 0,
			isCurrentlySyncing = true,
			recentlyUploaded = {
				[ assetId ] = {
					aux = {
						[ auxSha256 ] = true,
						[ aux2Sha256 ] = true,
					},
					master = {
						[ sha256 ] = true,
					},
					xmp_develop = {
						[ xmpSha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 4 binaries to Oz.",
			uploadsByType = {
				catalog = {
					["S3 Uploads"] = 3,
					aux = {
						["S3 Uploads"] = 2,
						total = 2,
					},
					master = {
						["S3 Uploads"] = 1,
						total = 1,
					},
					total = 4,
					xmp_develop = {
						total = 1
					}
				},
			},
		} )

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		-- WF-1813 need to check presence of the master link & no "invalid=true" in master link & no sha256 error in importsource
		:map( WFOzDocUtils.isValidOriginalAvailableOnOz ) )

	docObserver:expectDataWithinTime(60, true)

end

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

function WFCatBinaryUploaderTest.rxTestUploadAdd_ManyAux_testPerformance( rxTest )

	rxTest:setTimeLimit( 180 )

	local numberOfMasks = 100 -- Adjust this value to configure the test.

	local _, catalog, _, catalogId, pusher, puller, uploader = setupPusherPullerAndUploaderForNightly( rxTest )

	local assetId, _, _, _, assetDoc = setupAssetForNightly( rxTest, catalogId )

	local baseAuxPath = WFCatAssetAux.getBasePath( catalog )

	for i = 1, numberOfMasks, 1 do

		-- Create the mask

		local auxId = KSIdGenerator.uuidInLowercaseHex()

		local auxDigest = string.upper( WFCrypto.MD5.digest( auxId ) ) -- Random-ish

		local auxData = string.rep( auxDigest, i * 10 ) -- Creates data of a random size, all relatively small.

		local auxBinary = KSData.make( auxData )

		local auxFilePath, auxRelativeFilePath = WFCatAssetAux.calculatePathUsingDigest( baseAuxPath, auxDigest )

		local auxParentPath = KSPathUtils.parent( auxFilePath )

		WFFileUtils.mkdir( auxParentPath )

		KSData.saveToFile( auxBinary, auxFilePath )

		local auxFileSize = WFFileUtils.fileAttributes( auxFilePath ).fileSize

		local auxSha256 = WFCrypto.digestFile( auxFilePath, 'SHA256' )

		-- Add Aux Files to Document

		local auxParamsList = {
			{ id = auxId, sha256 = auxSha256, filePath = auxRelativeFilePath, fileSize = auxFileSize, digest = auxDigest, type = "mask" }
		}

		assetDoc = WFCatAssetTestUtils.addAuxiliaryMetadataReference( assetDoc, assetId, auxParamsList )

	end

	rxTest:waitFor( catalog:docStore():setDocValue( assetDoc ) )

	local xmpDevelopFilePath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'carouselDocuments' ), 'small.XMP' )

	WFCatAssetTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )

	local uploadFinishedObserver = rxTest:observe( uploader:DEBUG_uploadFinishedStream()
		:map( function( d )
			return d and d.binaryDetails.type or nil
		end )
		:distinctUntilChanged() )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 1 )
		:pluck( 'uploadsByType', 'catalog' ) )

	-- start up sync engine

	rxTest:waitFor( pusher:sendUpdatesContinuously() )
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()

	-- Ensure that aux binaries are uploaded first, order independent

	for i = 1, numberOfMasks, 1 do
		uploadFinishedObserver:expectDataWithinTime( 90, 'aux' )
	end

	-- upload remaining binaries

	uploadFinishedObserver:expectDataWithinTime( 90, 'xmp_develop' )
	uploadFinishedObserver:expectDataWithinTime( 90, 'master' )

	syncDiagnosticsObserver:expectDataWithinTime( 120,
		{
			[ "S3 Uploads" ] = numberOfMasks + 1,
			aux = {
				[ "S3 Uploads" ] = numberOfMasks,
				total = numberOfMasks,
			},
			master = {
				[ "S3 Uploads" ] = 1,
				total = 1,
			},
			total = numberOfMasks + 2,
			xmp_develop = {
				total = 1
			}
		} )

	-- Make sure everything uploaded correctly

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )

		:map( function( docValue )

			return WFOzDocUtils.isAssetDocFullySyncedWithOz( docValue )

		end ) )

	docObserver:expectDataWithinTime( 120, true )

end

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

function WFCatBinaryUploaderTest.rxTestHandleAuxDoesNotMatch( rxTest )

	-- Set up all preconditions for proper aux documents

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	local assetBinaryDetail = WFCatBinaryUploader.TEST_newBinaryDetail('mask', 'sha256')

	local carouselDocuments = KSPathUtils.getStandardFilePath( 'carouselDocuments' )

	local auxDigest = string.upper( WFCrypto.MD5.digest( KSIdGenerator.uuidInLowercaseHex() ) )

	local auxType = "mask"

	local auxBinary = KSData.make( auxDigest )

	local auxFilePath = WFCatAssetAux.calculatePathUsingDigest( carouselDocuments, auxDigest )

	local auxParentPath = KSPathUtils.parent( auxFilePath )

	WFFileUtils.mkdir( auxParentPath )

	KSData.saveToFile( auxBinary, auxFilePath )

	local auxFileSize = WFFileUtils.fileAttributes( auxFilePath ).fileSize

	local auxSha256 = WFCrypto.digestFile( auxFilePath, 'SHA256' )

	-- Adds file to localonly section and sets up the develop section

	if WFPlatformInfo.isAndroid then
		-- Android doesn't store full file path here, so remove the beginning if we have a full file path
		auxFilePath = string.replace( auxFilePath, carouselDocuments .. '/', '' )
	end


	-- Setup 11 'masks'
	local auxParamsList = {}
	for i = 1, 11 do

		local auxId = KSIdGenerator.uuidInLowercaseHex()

		auxParamsList[ #auxParamsList + 1 ] = { id = auxId, sha256 = auxSha256, filePath = auxFilePath, fileSize = auxFileSize, digest = auxDigest, type = auxType }

	end

	-- Create an asset document with 11 aux references
	rxTest:waitFor(
		addAuxReferenceToDocument( catalog, assetId, auxParamsList )
	)

	local fakeURL = "i-am-a-computer.com"
	local fakeResponse = "{Hi, my name is JSON}"
	local fakeHeaders = {}

	local testOptions = {TEST_OVERRIDE = true}

	rxTest:waitFor(uploader:handleAuxDoesNotMatch(fakeURL, assetBinaryDetail, assetId, fakeResponse, fakeHeaders, testOptions))

	-- The value should be present
	rxTest:expectEqual(true, not not testOptions.TEST_OVERRIDE )

	local failureInfo = testOptions.TEST_OVERRIDE
	rxTest:expectEqual( assetId, derefOpt( failureInfo, 'docId' ))
	rxTest:expectEqual( fakeURL, derefOpt( failureInfo, 'url' ))
	rxTest:expectEqual( fakeHeaders, derefOpt( failureInfo, 'headers' ))
	rxTest:expectEqual( assetBinaryDetail, derefOpt( failureInfo, 'binaryDetails' ))
	rxTest:expectEqual( true, derefOpt( failureInfo, 'isDirectUpload' ))
	rxTest:expectEqual( true, derefOpt( failureInfo, 'warning' ))
	rxTest:expectEqual( 'binary_upload_aux_sha256_mismatch', derefOpt( failureInfo, 'reason' ))

	-- Verify extra section is properly supplied

	local numAux = 0
	for _, _ in pairs( derefOpt( failureInfo, 'extra', 'assetAux' ) ) do
		numAux = numAux + 1
	end
	rxTest:expectEqual( 10, numAux )
	rxTest:expectEqual( 'unknown', derefOpt( failureInfo, 'extra', 'revisionIds'))
	rxTest:expectEqual( 'unknown', derefOpt( failureInfo, 'extra', 'labels'))

end

--==============================================================================
-- @mark Aux and xmp develop upload tests with duplicate files with the same sha256
--==============================================================================

local function testAuxDupSha256AcrossRevisions( rxTest, options )

	options = options or {}
	options.maskCountWithSameSHA256 = options.maskCountWithSameSHA256 or 1

	-- first run test which will upload at least one mask to the asset

	local objects = testUploadDevelopSettings( rxTest, options )

	local catalog = objects.catalog
	local assetId = objects.assetId
	local maskBinary = objects.maskBinary
	local uploader = objects.uploader

	local syncDiagnosticsObserver = objects.syncDiagnosticsObserver

	-- grab latest sync diagnostics as a starting value. Because of race conditions
	-- when atomicDevelopSettings is enabled and binaries are uploaded after metadata
	-- has synced, values can be somewhat unpredictable. See comment in
	-- testUploadDevelopSettings() for more info

	local expectedSyncDiagnostics = rxTest:getFirstValue( uploader:syncDiagnostics( 0.01 ):take( 1 ) )

	-- add duplicate masks to document with same sha256

	local assetOverlay = {
		_id = assetId,
	}

	for i = 1, options.maskCountWithSameSHA256 do
		assetOverlay = addAuxToAsset( catalog, assetOverlay, maskBinary )
	end

	-- remove develop part of overlay to avoid error... when adding new aux
	-- to develop section also need to have a new xmpCameraRaw value that
	-- is externalized.

	assetOverlay.develop = nil

	rxTest:waitFor( catalog:docStore():applyOverlay( assetOverlay ) )

	-- the aux file with duplicate sha256 should not be uploaded again
	-- but also should not require upload as Oz will recognize that it
	-- already has a duplicate and sync the download links for the files.
	-- So the sync diagnostics should not change, other than
	-- isCurrentlySyncing should now be false and
	-- documentsNeedingBinaryUpload should be 0.

	expectedSyncDiagnostics.isCurrentlySyncing = false
	expectedSyncDiagnostics.documentsNeedingBinaryUpload = 0

	syncDiagnosticsObserver:expectDataWithinTime( 30, expectedSyncDiagnostics )

	-- Make sure the links are available

	local docObserver = rxTest:observe( catalog:docStore():ongoingDocValue( assetId )

		:map( function( docValue )

			return WFOzDocUtils.isAssetDocFullySyncedWithOz( docValue )

		end ) )

	docObserver:expectDataWithinTime( 90, true )

end

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

local function testXmpDevelopDupSha256( rxTest, options )

	options = options or {}

	-- first run test which will upload at least one mask to the asset

	local objects = testXmpDevelopOtherExternalAlreadyUploaded( rxTest, options )

	local catalog = objects.catalog
	local assetId = objects.assetId
	local spaceId = objects.spaceId
	local uploader = objects.uploader

	-- need to clear previous sha256 out of recently uploaded to unblock upload.
	-- This would normally clear after 30 seconds but do not want to delay test
	-- to wait for that.

	uploader:clearFromRecentlyUploaded( 'upload confirmed', assetId,
		{ type = 'xmp_develop', sha256 = objects.xmpDevelopSha256 }, true )

	-- switch back to the same xmp develop that was uploaded initially to ensure
	-- that Oz allows the upload of xmp develop with same sha256 a second time.
	-- (Was not working at one pont see OZ-12255). Currently this method of
	-- creating xmp uses static content and generates the same sha256 each time
	-- but including a check for that in case some refactoring changes this.

	local carouselDocuments = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	local xmpDevelopFilePath = KSPathUtils.child( carouselDocuments, 'small2.XMP' )
	local _, xmpDevelopSha256 =  WFCatAssetTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )
	rxTest:expectEqual( objects.xmpDevelopSha256, xmpDevelopSha256 )

	-- should start uploading third xmp develop which has same sha256 as the first

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 30, true )

	-- do not have to update expectedSyncDiagnostics.recentlyUploaded as it
	-- already has the sha256 entry which was cleared but should be repopulated
	-- when xmp develop upload happens again

	local syncDiagnosticsObserver = objects.syncDiagnosticsObserver
	local expectedSyncDiagnostics = objects.expectedSyncDiagnostics

	if spaceId then

		expectedSyncDiagnostics.summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 4 binaries to Oz."

		expectedSyncDiagnostics.uploadsByType[ spaceId ][ 'S3 Uploads' ] = 3
		expectedSyncDiagnostics.uploadsByType[ spaceId ].total = 4

		expectedSyncDiagnostics.uploadsByType[ spaceId ].xmp_develop[ 'S3 Uploads' ] = 3
		expectedSyncDiagnostics.uploadsByType[ spaceId ].xmp_develop.total = 3

	else

		expectedSyncDiagnostics.summaryStr = "Catalog: Uploaded 4 binaries to Oz."

		expectedSyncDiagnostics.uploadsByType.catalog[ 'S3 Uploads' ] = 4
		expectedSyncDiagnostics.uploadsByType.catalog.total = 4

		expectedSyncDiagnostics.uploadsByType.catalog.xmp_develop[ 'S3 Uploads' ] = 3
		expectedSyncDiagnostics.uploadsByType.catalog.xmp_develop.total = 3

	end

	syncDiagnosticsObserver:expectDataWithinTime( 60, expectedSyncDiagnostics )

	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
		:pluck( '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'sha256' )
	):expectDataWithinTime( 90, xmpDevelopSha256 )

	uploading:expectDataWithinTime( 30, false )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256FirstRevision_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 3 } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256FirstRevision_IAC_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 4, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256FirstRevision_ADS_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 2, atomicDevelopSettings = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256FirstRevision_ADS_IAC_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 5, atomicDevelopSettings = true,
		inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256AcrossRevisions_testNightly( rxTest )

	testAuxDupSha256AcrossRevisions( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256AcrossRevisions_IAC_testNightly( rxTest )

	testAuxDupSha256AcrossRevisions( rxTest, { maskCountWithSameSHA256 = 2, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256AcrossRevisions_ADS_testNightly( rxTest )

	testAuxDupSha256AcrossRevisions( rxTest, { maskCountWithSameSHA256 = 4, atomicDevelopSettings = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256AcrossRevisions_ADS_IAC_testNightly( rxTest )

	testAuxDupSha256AcrossRevisions( rxTest, { maskCountWithSameSHA256 = 3, atomicDevelopSettings = true,
		inAppContributeRole = 'editor'} )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256ChildRevision_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 2, syncMetadataAsUpdate = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256ChildRevision_IAC_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 3, syncMetadataAsUpdate = true,
		inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256ChildRevision_ADS_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 2, syncMetadataAsUpdate = true,
		atomicDevelopSettings = true } )

end

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

function WFCatBinaryUploaderTest.rxTestAuxDupSha256ChildRevision_ADS_IAC_testNightly( rxTest )

	testUploadDevelopSettings( rxTest, { maskCountWithSameSHA256 = 4, syncMetadataAsUpdate = true,
		atomicDevelopSettings = true, inAppContributeRole = 'editor' } )

end

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

function WFCatBinaryUploaderTest.rxTestXmpDevelopDupSha256_testNightly( rxTest )

	testXmpDevelopDupSha256( rxTest )

end

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

function WFCatBinaryUploaderTest.rxTestXmpDevelopDupSha256_IAC_testNightly( rxTest )

	testXmpDevelopDupSha256( rxTest, { inAppContributeRole = 'editor' } )

end


--==============================================================================
-- @mark c2pa Upload Tests
--==============================================================================

-- This test mocked no-classic clients upload origin binaries(with c2pa embedded ) to oz, and oz
-- would extract c2pa info and write the info into asset's importsource.c2paIngredients and aux subsection

function WFCatBinaryUploaderTest.rxTestMockedUploadC2pa( rxTest )

	rxTest:setTimeLimit( 10 )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
		-- code has changed so that if direct upload template link for original binary is missing,
		-- we will reload the asset document to get it, but not for spaces since direct upload
		-- of original was not implemented for spaces.

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )

	local urlResponses = {
		WFCatBinaryUploaderTestUtils.generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, originalFilePath ),
	}

	local catalog, catalogSpec, uploader = setupMockedCatalogAndUploader( rxTest, catalogId, urlResponses )

	local syncDiagnosticsObserver = rxTest:observe( uploader:syncDiagnostics( 0.001 )
		:map( function( diagnostics )
			return diagnostics
		end ) )

	local expectC2paIngredients = {
					aux_id = "a123b345",
                    state ="Signed_Valid",
                    version = 1,
				}

	local expectAux = {
			["a123b345"] = {
			  		type = "c2pa",
					fileSize = 256,
                    digest = "ddddddddddd",
					sha256 = sha256,
				},
	}

	local assetOptions = { spaceId = spaceId,
		overlay = {
			importSource = {
				c2paIngredients = expectC2paIngredients
			},
			aux = expectAux,
		},
	}
	rxTest:waitFor( addDocumentWithOzMetadataUpdate( catalog, catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) )

	local completionStream = rxTest:observe( uploader:binaryUploadCompletionStream() )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.",
			uploadsByType = {}
		} )

	uploader:startContinuousUploads()

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1,
			inFlightUploads = {
				[ assetId ] = {
					master = true,
				},
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 1,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			scheduledUpload = {
				binaryType = "master",
				docId = assetId,
				priority = -10,
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 1 binaries to Oz.",
			uploadsByType = {
				[ spaceId ] = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	local uploading = rxTest:observe( uploader:isDoingWork() )
	uploading:expectDataWithinTime( 5, true )

	local httpRequestsProcessed = rxTest:observe( catalogSpec._httpMock:requestsProcessed() )

	-- wait for the first PUT to occur
	httpRequestsProcessed:expectDataWithinTime( 2, 1 )

	completionStream:expectDataWithinTime( 5, assetId )
	completionStream:dispose()

	-- Upload Finished

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 1, -- haven't yet received Oz response with the master link
			inFlightUploads = {
			},
			isCurrentlySyncing = true,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 1 binaries to Oz.",
			uploadsByType = {
				[ spaceId ] = {
					master = {
						total = 1,
					},
					total = 1,
				},
			},
		} )

	-- Make it look like metadatapuller got an update with a master link in it to let the
	-- uploader know there is no more work to do.
	rxTest:waitFor( addMasterAndXmpLinkToDoc( catalog, catalogId, assetId, revisionId ) )

	local observeBinariesInFlight = rxTest:observe( pollBinariesInFlight( uploader ) )
	observeBinariesInFlight:expectDataWithinTime( 5, 0 )

	uploading:expectDataWithinTime( 5, false )

	-- Make sure we didn't run into any unexpected errors.
	local errorsRecordedInDiagnosticLog = getDiagnosticLogErrorsIgnoringAccountError( uploader )
	rxTest:expectEqual( "", removeEmptyErrorHeader( errorsRecordedInDiagnosticLog ) )

	syncDiagnosticsObserver:expectDataWithinTime( 5,
		{
			documentsNeedingBinaryUpload = 0,
			inFlightUploads = {},
			isCurrentlySyncing = false,
			numberOfInFlightUploads = 0,
			numberOfScheduledBinaryUploads = 0,
			recentlyUploaded = {
				[ assetId ] = {
					master = {
						[ sha256 ] = true
					}
				},
			},
			summaryStr = "Catalog: Uploaded 0 binaries to Oz.\nSpace " .. spaceId .. ": Uploaded 1 binaries to Oz.",
			uploadsByType = {
				[ spaceId ] = {
					master = {
						total = 1,
					},
					total = 1,
				}
			}
		} )

	syncDiagnosticsObserver:dispose()

	rxTest:expectSingleValue( catalog:docStore():docValue( assetId )
		:map( function( docValue )

		 	local actC2paIngredients = derefOpt( docValue, 'importSource', 'c2paIngredients' )
			local actAux = derefOpt( docValue, 'aux' )

			return { actC2paIngredients, actAux }
		end
		),
		{
			expectC2paIngredients,
			expectAux
		}
	)

end

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

function WFCatBinaryUploaderTest.rxTestMockedEmitC2paAux_classic( rxTest )

	rxTest:setTimeLimit( 10 )

	WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', 'true' )

	rxTest:finally( function()
		WFCapabilities.setCapability( 'WFPlatformInfo.isClassic', nil )
	end )
	
	local needsUpload = false

	local emit = function( ... )

		needsUpload = true

	end

	local currentTime = KSDate.timeNow()
	
	local catalogId = KSIdGenerator.uuidInLowercaseHex()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()

	local originalFilePath = KSPathUtils.child( testDataFolder1, "small.JPG" )
	
	local assetOptions = { 
		overlay = {
			importSource = {
				c2paIngredients = {
					aux_id = "a123b345",
                    state ="Signed_Valid",
                    version = 1,
				}
			},
			aux = {
			["a123b345"] = {
			  		type = "c2pa",
					fileSize = 256,
                    digest = "ddddddddddd",
					sha256 = sha256,
				},
	}
		},
	}
	
	local docValue = WFCatAssetTestUtils.createDocumentWithOzMetadataUpdate( catalogId, assetId, revisionId, sha256, originalFilePath, assetOptions ) 
	
	KSTableUtils.setNested( docValue, '/ddd/dddddddd', '_localOnly', 'files', 'aux', 'ddddddddddd', 'relativePath')
	WFCatBinaryUploader._DEBUG_maybeEmitAuxUploadRecord( docValue, emit, currentTime, currentTime, false, 1 )

	rxTest:expectEqual( true, needsUpload )

end

--==============================================================================
-- @mark Xmp Sidecar Upload Tests (disabled on mobile)
--==============================================================================

if not WFPlatformInfo.isMobile() then

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_xmpSideCarOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeXmpSidecar = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_xmpSideCarXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeXmpSidecar = true, includeXmpDevelop = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedDirectUpload_xmpSideCarAuxXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedDirectUploadWithoutADS( rxTest, { includeXmpSidecar = true, includeAux = true, includeXmpDevelop = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpSideCarOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpSidecar = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpSideCarOriginal_BinaryMissingError( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpSidecar = true, includeOriginal = true, simulateErrorAnnotation = 'BinaryMissingError', } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpSideCarXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpSidecar = true, includeXmpDevelop = true, includeOriginal = true, } )

end

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

function WFCatBinaryUploaderTest.rxTestMockedAtomicDevelopSettingsUpload_xmpSideCarAuxXmpDevelopOriginal( rxTest )

	rxTest:setTimeLimit( 10 )

	testMockedAtomicDevelopSettingsUpload( rxTest, { includeXmpSidecar = true, includeAux = true, includeXmpDevelop = true, includeOriginal = true, } )

end

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

end -- not WFPlatformInfo.isMobile()

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

function WFCatBinaryUploaderTest.rxTestHandleMalformedSourceLinkInCopyRequest( rxTest )

	local catalogId = KSIdGenerator.uuidInLowercaseHex()

	local catalog, _, uploader = setupMockedCatalogAndUploader( rxTest, catalogId )

	-- Create test doc

	local optOverrides = {
		hasMasks = true,
		copyLinks = true
	}

	local docId = KSIdGenerator.uuidInLowercaseHex()

	local docValue, expectedData = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, docId, optOverrides )

	rxTest:waitFor( catalog:docStore():setDocValue( docValue ) )

	-- Verify Copy Links

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( docId ) )

	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceOriginalLink' ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceXmpDevelopLink' ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceXmpDevelopSha256' ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceXmpLink' ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceProxyLink' ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceAuxLinks', expectedData.mask1Fingerprint ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceAuxLinks', expectedData.mask2Fingerprint ) )

	-- XMP Develop

	local binaryDetails = {
		type = 'xmp_develop'
	}

	rxTest:waitFor( WFCatBinaryUploader._TEST_handleMalformedSourceLinkInCopyRequest( uploader, docId, binaryDetails ) )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( docId ) )

	rxTest:expectEqual( false, not not derefOpt( docValue, '_localOnly', 'sourceXmpDevelopLink' ) )
	rxTest:expectEqual( false, not not derefOpt( docValue, '_localOnly', 'sourceXmpDevelopSha256' ) )

	-- XMP Sidecar

	binaryDetails = {
		type = 'xmp_sidecar'
	}

	rxTest:waitFor( WFCatBinaryUploader._TEST_handleMalformedSourceLinkInCopyRequest( uploader, docId, binaryDetails ) )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( docId ) )

	rxTest:expectEqual( false, not not derefOpt( docValue, '_localOnly', 'sourceXmpLink' ) )

	-- Aux

	binaryDetails = {
		type = 'aux'
	}

	-- Let's pretend one of the Mask links is malformed, but not the other.

	rxTest:waitFor( WFCatBinaryUploader._TEST_handleMalformedSourceLinkInCopyRequest( uploader, docId, binaryDetails, expectedData.mask1AuxId ) )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( docId ) )

	rxTest:expectEqual( false, not not derefOpt( docValue, '_localOnly', 'sourceAuxLinks', expectedData.mask1Fingerprint ) )
	rxTest:expectEqual( true, not not derefOpt( docValue, '_localOnly', 'sourceAuxLinks', expectedData.mask2Fingerprint ) )

	-- Proxy

		binaryDetails = {
		type = 'proxy'
	}

	rxTest:waitFor( WFCatBinaryUploader._TEST_handleMalformedSourceLinkInCopyRequest( uploader, docId, binaryDetails ) )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( docId ) )

	rxTest:expectEqual( false, not not derefOpt( docValue, '_localOnly', 'sourceProxyLink' ) )


	-- Original Binary

	binaryDetails = {
		type = 'master'
	}

	rxTest:waitFor( WFCatBinaryUploader._TEST_handleMalformedSourceLinkInCopyRequest( uploader, docId, binaryDetails ) )

	docValue = rxTest:getFirstValue( catalog:docStore():docValue( docId ) )

	rxTest:expectEqual( false, not not derefOpt( docValue, '_localOnly', 'sourceOriginalLink' ) )

end

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

function WFCatBinaryUploaderTest.rxTestIsMalformedSourceLinkResponse( rxTest )

	local malformedResponse = { code = 1005 }
	local malformedHeaders = { status_code = '400' }

	rxTest:expectEqual( true, WFCatBinaryUploader._TEST_isMalformedSourceLinkResponse( malformedResponse, malformedHeaders ) )

	-- Test with nil response (should return false)

	rxTest:expectEqual( false, WFCatBinaryUploader._TEST_isMalformedSourceLinkResponse( nil, malformedHeaders ) )

	-- Test with nil headers (should return false)

	rxTest:expectEqual( false, WFCatBinaryUploader._TEST_isMalformedSourceLinkResponse( malformedResponse, nil ) )

	-- Test with wrong error code (should return false)

	local wrongCodeResponse = { code = 1006 }
	rxTest:expectEqual( false, WFCatBinaryUploader._TEST_isMalformedSourceLinkResponse( wrongCodeResponse, malformedHeaders ) )

	-- Test with wrong status code (should return false)

	local wrongStatusHeaders = { status_code = '500' }
	rxTest:expectEqual( false, WFCatBinaryUploader._TEST_isMalformedSourceLinkResponse( malformedResponse, wrongStatusHeaders ) )

	-- Test with non-error response (should return false)

	local validResponse = { code = 200 }
	local validHeaders = { status_code = '200' }
	rxTest:expectEqual( false, WFCatBinaryUploader._TEST_isMalformedSourceLinkResponse( validResponse, validHeaders ) )

end

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

-- Note:
-- 1. Please refer WFCatLrmBinaryUploaderTest for mobile client specific test cases
-- 2. As this test suite has ACR specific and Desktop specific handling this test case can't be invoked from Mobile client.
--    Hence added in a separate file

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

return WFRxTestCase.wrapSuite( 'WFCatBinaryUploaderTest', WFCatBinaryUploaderTest )
