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

WFCatVersionsTestFactory.lua

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

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

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

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

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

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

	-- WF.core.utils
local KSData = require 'KSData'
local KSIdGenerator = require 'KSIdGenerator'
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFAsyncFileUtils = require 'WFAsyncFileUtils'
local WFFileUtils = require 'WFFileUtils'
local WFModelsUtils = require 'WFModelsUtils'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFTestFileUtils = require 'WFTestFileUtils'

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

	-- WF.managed_catalog
local WFCatAssetAux = require 'WFCatAssetAux'
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatAssetUtils = require 'WFCatAssetUtils'
local WFCatBinaryUploader = require 'WFCatBinaryUploader'
local WFCatMetadataPuller = require 'WFCatMetadataPuller'
local WFCatMetadataPusher = require 'WFCatMetadataPusher'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatOzErrors = require 'WFCatOzErrors'
local WFCatTestSession = require 'WFCatTestSession'
local WFCatVersionTestUtils = require 'WFCatVersionTestUtils'
local WFCatVersions = require 'WFCatVersions'

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

local check = assert( WFArguments.check )

local derefOpt = assert( KSTableUtils.derefOpt )

local WFCatAssetUtils_getLocalExternalDevelopSettingsPath = assert( WFCatAssetUtils.getLocalExternalDevelopSettingsPath )

local WFCatMockCatalog_createFakeWFCatalog = assert( WFCatMockCatalog.createFakeWFCatalog )

local getLocalExternalDevelopSettingsContentId = assert( WFCatAssetUtils.getLocalExternalDevelopSettingsContentId )

local expectedFormFactor

if WFPlatformInfo.isMobile() then
	expectedFormFactor = 'phone'
else
	expectedFormFactor = 'desktop'
end

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

local function createFakeWFCatalog( rxTest, expectedRequestResponse )

	local optArgs = {}

	if WFPlatformInfo.isMobile() then
		optArgs.indexDefinitions = 'LrmCatalogIndexDefinitions.lua'
	end

	return WFCatMockCatalog_createFakeWFCatalog( rxTest, nil, expectedRequestResponse, optArgs )

end

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

local function mapAwayCruft( docValue )

	docValue._changeCounter = nil
	docValue._localId = nil
	docValue._revId = nil
	docValue.userCreated = nil
	docValue.userUpdated = nil
	docValue.develop.userUpdated = nil
	docValue.develop.device = nil

	return docValue

end

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

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 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 mapAwayFragileVersionInfoFields( versionInfo )

	versionInfo.date = nil
	versionInfo.externalizedDevelopSettingsPath = versionInfo.externalizedDevelopSettingsPath and "path"
	versionInfo.embeddedDevelopSettings = versionInfo.embeddedDevelopSettings and "settings"

	local versionMasks = KSTableUtils.derefOpt( versionInfo, 'developMasks' )
	local versionFilters = KSTableUtils.derefOpt( versionInfo, 'developFilters' )

	if versionMasks then
		KSTableUtils.setNested( versionInfo, '<maskData>', 'developMasks' )
	end

	if versionFilters then
		KSTableUtils.setNested( versionInfo, '<filterData>', 'developFilters' )
	end

	return versionInfo

end

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

 local function mapAwayFragileVersionInfoFieldsForVersions( versions )

	for _, version in ipairs( versions ) do

		version = mapAwayFragileVersionInfoFields( version )

	end

	return versions

end

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

local function addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize )

	local newXmpDevelopFilePath = xmpDevelopFilePath
	if WFPlatformInfo.isAndroid then
		-- Android doesn't store full file path here, so remove the beginning if we have a full file path
		local basePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
		newXmpDevelopFilePath = string.replace( xmpDevelopFilePath, basePath .. '/', '' )
	end

	local contentIdOrSha256 = WFPlatformInfo.isiOS and 'contentId' or 'sha256'
	return catalog:docStore():applyOverlay {
			_id = assetId,
			develop = {
				processingModel = 'lightroom',
				xmpCameraRaw = {
					sha256 = xmpDevelopSha256,
				},
			},
			_localOnly = {
				files = {
						xmp_develop = {
							path = newXmpDevelopFilePath,
							fileSize = xmpDevelopFileSize,
							[contentIdOrSha256] = xmpDevelopSha256,
						},
				},
			},
		}

end

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

return function( optSubmodelsByImplementation, maybeExistingTestClass )

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

	local function callFunctionOrSubmodel( rxTest, wfCatalog, fnNamespace, fnName, ... )

		return WFModelsUtils.callFunctionOrSubmodel( rxTest, optSubmodelsByImplementation, wfCatalog, fnNamespace, fnName, ... )

	end

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

	local function shouldIncludeNightlyTest()

		-- Don't run nightly tests for submodel tests.

		return not optSubmodelsByImplementation

	end

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

	local WFCatVersionsTest = maybeExistingTestClass or {}

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_InternalSettings( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true,
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local _localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft ),
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_BYOCR_Import( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local assetLinkedLocationId = 'linked-location-1'
		local linkedLocationSource = 'apple.icloud'

		if WFPlatformInfo.isAndroid then

			linkedLocationSource = 'android.content'

		end

		local optOverrides = {
			internalXmpCameraRaw = true,
			linkedLocation = {
				id = assetLinkedLocationId,
				source = linkedLocationSource
			}
		}
              
		local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		-- LinkedLocation should not be carried forward
		local expectedAndroidLinkedLocation = nil

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
				:map( mapAwayCruft )
					:map(function ( docValue )

						return docValue.linkedLocation

					end),
					expectedAndroidLinkedLocation )
	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_BYOCR_Export( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local assetLinkedLocationId = 'linked-location-1'
		local linkedLocationSource = 'apple.icloud'

		if WFPlatformInfo.isAndroid then

			linkedLocationSource = 'android.content'

		end

		local optOverrides = {
			internalXmpCameraRaw = true,
			linkedLocation = {
				id = assetLinkedLocationId,
				source = linkedLocationSource
			}
		}

		-- Create a base asset, because a linkedLocation is present we can assume this was done via implicit import
		local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		-- Explicitly create a version with a different linked
		-- location than what is present on the asset. This
		-- happens in the case of an export back to camera
		-- roll

		local versionName = "Foo"

		local versionLinkedLocation = {
			id = 'linked-location-2',
			source = linkedLocationSource
		}

		if WFPlatformInfo.isAndroid then

			versionLinkedLocation.sha256 = 'i-am-a-hash'

		end

		-- This does
		local versionId = rxTest:getFirstValue( WFCatVersions.createVersionForAsset(wfCatalog, assetId, false, versionName, versionLinkedLocation ))

		if WFPlatformInfo.isAndroid then

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
				:map( mapAwayCruft )
				:map(function ( docValue )

					return docValue.linkedLocation

				end),
					{
						id = 'linked-location-2',
						source = 'android.content',
						sha256 = 'i-am-a-hash'
					} )

		else

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
				:map( mapAwayCruft )
				:map(function ( docValue )

					return docValue.linkedLocation

				end),
					{
						id = 'linked-location-2',
						source = 'apple.icloud'
					} )

		end
	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_ExternalSettings( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optArgs = {
			hasMasks = true
		}

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

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

		local versionName = "Foo"
		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local _localOnly = {
			files = {
				xmp_develop = {
					fileSize = assetInfo.xmpDevelopSettingsFileSize,
					path = assetInfo.xmpDevelopSettingsFilePath,
					sha256 = assetInfo.developSettingsSha256,
				},
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = {
				files = {
					xmp_develop = {
						fileSize = assetInfo.xmpDevelopSettingsFileSize,
						path = '<path>',
						contentId = getLocalExternalDevelopSettingsContentId( assetDoc ),
					}
				},
			}

		elseif WFPlatformInfo.isAndroid then

			_localOnly = {
				files = {
					xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
				},
			}

		end

		_localOnly.files.aux = {
			[ assetInfo.mask1Fingerprint ] = {
				relativePath = assetInfo.mask1RelativeFilePath
			},
			[ assetInfo.mask2Fingerprint ] = {
				relativePath = assetInfo.mask2RelativeFilePath
			}
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft )
			:map( function( docValue )

				if WFPlatformInfo.isiOS then

					local path = KSTableUtils.derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'path' )

					assert( path )
					assert( path ~= assetInfo.xmpDevelopSettingsFilePath )

					KSTableUtils.setNested( docValue, '<path>', '_localOnly', 'files', 'xmp_develop', 'path' )

				end

				return docValue

			end ),
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = {
						sha256 = assetInfo.developSettingsSha256,
					},
					masks = {
						[ assetInfo.mask1AuxId ] = {
							digest = assetInfo.mask1Fingerprint,
						},
						[ assetInfo.mask2AuxId ] = {
							digest = assetInfo.mask2Fingerprint,
						},
					},
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_OzError( rxTest )

		-- WF-1644: This test verifies that Versions are created with the same
		-- Oz errors as the Asset documents that they reference; this prevents
		-- the code from performing an unncessary upload for the Version document,
		-- which is guaranteed to fail if Oz doesn't have the referenced Asset.

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

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

		assetDoc = WFCatOzErrors.addOverlayForOzErrorSubtype(
			assetDoc, 'StorageExceededError' )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, "foo" ) )

		local ozError = {
			subtype = 'StorageExceededError'
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:pluck( '_localOnly', 'ozError' ), ozError )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_BadCroppedWidthAndHeight( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		-- Floats are invalid.

		local assetId = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )
		KSTableUtils.setNested( assetDoc, 5.2, 'develop', 'croppedWidth' )
		KSTableUtils.setNested( assetDoc, 6.1, 'develop', 'croppedHeight' )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		-- Strings are invalid

		local assetId2 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc2, assetInfo2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId2, optOverrides )
		KSTableUtils.setNested( assetDoc2, "Hello", 'develop', 'croppedWidth' )
		KSTableUtils.setNested( assetDoc2, "Goodbye", 'develop', 'croppedHeight' )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )

		-- Create the Versions

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local _localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		-- Verify the floats are now integers

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft ),
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 6,
					croppedWidth = 5,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

		-- And the version with string fields have been removed.

		local versionId2 = rxTest:getFirstValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId2, false, versionName ) )

		_localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc2, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId2 )
			:map( mapAwayCruft ),
			{
				_id = versionId2,
				_localOnly = _localOnly,
				asset = {
					id = assetId2,
					sha256 = assetInfo2.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					userOrientation = 1,
					xmpCameraRaw = assetInfo2.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_EmptyDevelopProfiles( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )
		KSTableUtils.setNested( assetDoc, {}, 'develop', 'profiles' )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"
		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local _localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft ),
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForDeletedAsset_LRMA_4199669( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		rxTest:expectOnlyCompleted (
			require 'WFCatTrash'.moveAssetsToTrash( wfCatalog, { assetId } )
		)

		local newAssetId

		rxTest:waitFor( require 'WFCatTrashUtils'.getTrashOrRestoredDocFromNewId( wfCatalog:docStore(), assetId )
			:map( function( docValue )
				newAssetId = docValue._id
				return true
			end ) )

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local _localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft ),
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = newAssetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_NoName( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, nil ) )

		local observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )

		observer:expectDataWithinTime( 3, { {
			authorFormFactor = expectedFormFactor,
			croppedHeight = 1000,
			croppedWidth = 1000,
			embeddedDevelopSettings = "settings",
			hasDevelopFilters = false,
			hasExternalizedDevelopSettings = false,
			id = versionId,
			createdManually = false,
			type = "edit",
			userOrientation = 1,
			versionIsLocal = true
		} } )

		observer:dispose()

	end

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

	function WFCatVersionsTest.rxTestGetVersionInfo( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true,
			hasFilters = true
		}

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"
		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local expected = {
			id = versionId,
			name = versionName,
			type = "edit",
			embeddedDevelopSettings = "settings",
			hasDevelopFilters = true,
			developFilters = "<filterData>",
			hasExternalizedDevelopSettings = false,
			croppedWidth = 1000,
			croppedHeight = 1000,
			userOrientation = 1,
			createdManually = false,
			authorFormFactor = expectedFormFactor,
			versionIsLocal = true
		}

		rxTest:expectSingleValue( WFCatVersions.getVersionInfo( wfCatalog, versionId )
				:map( mapAwayFragileVersionInfoFields ), expected )

	end

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

	function WFCatVersionsTest.rxTestGetVersionsStreamForAsset( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optArgs = {
			hasDevelopAux = true
		}

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

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

		local versions = {}
		local manualVersions = {}
		local autoVersions = {}

		local expectedVersionZero = {
			croppedHeight = 1000,
			croppedWidth = 1000,
			createdManually = false,
			type = "edit",
			userOrientation = 1,
			isFakeImportStateVersion = true,
			versionIsLocal = true
		}

		versions[ #versions + 1 ] = expectedVersionZero
		manualVersions[ #manualVersions + 1 ] = expectedVersionZero
		autoVersions[ #autoVersions + 1 ] = expectedVersionZero

		for i = 1, 5, 1 do

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )

				:map( function( versionId )

					local versionData = {
						id = versionId,
						name = versionName,
						type = "edit",
						externalizedDevelopSettingsPath = "path", -- verifying presence, not contents
						hasDevelopFilters = true,
						hasExternalizedDevelopSettings = true,
						croppedWidth = 1000,
						croppedHeight = 1000,
						userOrientation = 1,
						createdManually = false,
						authorFormFactor = expectedFormFactor,
						versionIsLocal = true,
						developMasks = '<maskData>',
						developFilters = '<filterData>'
					}

					versions[ #versions + 1 ] = versionData
					autoVersions[ #autoVersions + 1 ] = versionData

					return true

				end ) )

		 end

		 for i = 1, 5, 1 do

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true, versionName )

				:map( function( versionId )

					local versionData = {
						id = versionId,
						name = versionName,
						type = "edit",
						externalizedDevelopSettingsPath = "path", -- verifying presence, not contents
						hasDevelopFilters = true,
						hasExternalizedDevelopSettings = true,
						croppedWidth = 1000,
						croppedHeight = 1000,
						userOrientation = 1,
						createdManually = true,
						authorFormFactor = expectedFormFactor,
						versionIsLocal = true,
						developMasks = '<maskData>',
						developFilters = '<filterData>'
					}

					versions[ #versions + 1 ] = versionData
					manualVersions[ #manualVersions + 1 ] = versionData

					return true

				end ) )

		 end

		-- check all versions for asset

		rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )
			:expectDataWithinTime( 3, versions )
			:dispose()

		-- check manual versions for asset

		rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getManualVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )
			:expectDataWithinTime( 3, manualVersions )
			:dispose()

		-- check non-manual versions for asset

		rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAutoVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )
			:expectDataWithinTime( 3, autoVersions )
			:dispose()

	end

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

	function WFCatVersionsTest.rxTestGetVersionsStreamForAsset_withAuthorFullname( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

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

		-- Add a feedbackuser

		local feedbackuserId = KSIdGenerator.uuidInLowercaseHex()

		local feedbackuserDoc = {
			_id = feedbackuserId,
			type = 'feedbackuser',
			_serverProvidedMetadata = {
				full_name = 'James Smith',
				first_name = 'James',
				last_name = 'Smith',
				email = 'jsmith@adobetest.com'
			}
		}

		rxTest:waitFor( wfCatalog:docStore():setDocValue( feedbackuserDoc ) )

		-- Prepare to add versions

		local versions = {}
		local manualVersions = {}
		local autoVersions = {}

		local expectedVersionZero = {
			croppedHeight = 1000,
			croppedWidth = 1000,
			createdManually = false,
			type = "edit",
			userOrientation = 1,
			isFakeImportStateVersion = true,
			versionIsLocal = true,
		}

		versions[ #versions + 1 ] = expectedVersionZero
		manualVersions[ #manualVersions + 1 ] = expectedVersionZero
		autoVersions[ #autoVersions + 1 ] = expectedVersionZero

		-- Add a named version which is created from other user

		local versionName_manual = KSIdGenerator.uuidInLowercaseHex()

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true, versionName_manual )

			:flatMap( function( versionId )

				local versionData = {
					id = versionId,
					name = versionName_manual,
					type = "edit",
					externalizedDevelopSettingsPath = "path",
					hasDevelopFilters = false,
					hasExternalizedDevelopSettings = true,
					croppedWidth = 1000,
					croppedHeight = 1000,
					userOrientation = 1,
					createdManually = true,
					author = feedbackuserId,
					authorFullname = 'James Smith',
					authorFormFactor = expectedFormFactor,
					versionIsLocal = true
				}

				versions[ #versions + 1 ] = versionData
				manualVersions[ #manualVersions + 1 ] = versionData

				local ozOverlay = {
					_id = versionId,
					_serverProvidedMetadata = {
						importedByUser = {
							id = feedbackuserId
						}
					}
				}

				return wfCatalog:docStore():applyOverlay( ozOverlay )

			end ) )

		-- Add an auto-version which is created from other user

		local versionName_auto = KSIdGenerator.uuidInLowercaseHex()

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName_auto )

			:flatMap( function( versionId )

				local versionData = {
					id = versionId,
					name = versionName_auto,
					type = "edit",
					externalizedDevelopSettingsPath = "path",
					hasDevelopFilters = false,
					hasExternalizedDevelopSettings = true,
					croppedWidth = 1000,
					croppedHeight = 1000,
					userOrientation = 1,
					createdManually = false,
					author = feedbackuserId,
					authorFullname = 'James Smith',
					authorFormFactor = expectedFormFactor,
					versionIsLocal = true,
				}

				versions[ #versions + 1 ] = versionData
				autoVersions[ #autoVersions + 1 ] = versionData

				local ozOverlay = {
					_id = versionId,
					_serverProvidedMetadata = {
						importedByUser = {
							id = feedbackuserId
						}
					}
				}

				return wfCatalog:docStore():applyOverlay( ozOverlay )

			end ) )

			-- check all versions for asset

		rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )
			:expectDataWithinTime( 3, versions )
			:dispose()

		-- check manual versions for asset

		rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getManualVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )
			:expectDataWithinTime( 3, manualVersions )
			:dispose()

		-- check non-manual versions for asset

		rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAutoVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( mapAwayFragileVersionInfoFieldsForVersions ) )
			:expectDataWithinTime( 3, autoVersions )
			:dispose()

	end

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

	function WFCatVersionsTest.rxTestHideIncompleteVersions( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

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

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		-- Verify the Version shows up since it is complete.

		local observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId, false )
					:map( function( versions )

						for _, version in ipairs( versions ) do

							version = mapAwayFragileVersionInfoFields( version )

						end

						return versions

					end ) )

		observer:expectDataWithinTime( 5, { {

			createdManually = false,
			croppedHeight = 1000,
			croppedWidth = 1000,
			externalizedDevelopSettingsPath = "path",
			hasDevelopFilters = false,
			hasExternalizedDevelopSettings = true,
			id = versionId,
			name = "Foo",
			type = "edit",
			userOrientation = 1,
			authorFormFactor = expectedFormFactor,
			versionIsLocal = true

		} } )

		-- Remove the local develop XMP path. The Version is now incomplete, and should
		-- be hidden from the client.

		local versionOverlay = {
			_id = versionId,
			_localOnly = { _delete = true }
		}

		rxTest:waitFor( wfCatalog:docStore():applyOverlay( versionOverlay ) )

		observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId, false ) )

		observer:expectDataWithinTime( 3, {} )

		observer:dispose()

	end

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

	function WFCatVersionsTest.rxTestHideIncompleteVersionsAfterRoleDowngrade( rxTest )

		-- Test for LRMA-4203003

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

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

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		-- Verify the Version shows up since it is complete.

		local observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId, false )
					:map( function( versions )

						for _, version in ipairs( versions ) do

							version = mapAwayFragileVersionInfoFields( version )

						end

						return versions

					end ) )

		observer:expectDataWithinTime( 3, { {

			createdManually = false,
			croppedHeight = 1000,
			croppedWidth = 1000,
			externalizedDevelopSettingsPath = "path",
			hasDevelopFilters = false,
			hasExternalizedDevelopSettings = true,
			id = versionId,
			name = "Foo",
			type = "edit",
			userOrientation = 1,
			authorFormFactor = expectedFormFactor,
			versionIsLocal = true

		} } )

		-- Remove the user created date.

		local versionOverlay = {
			_id = versionId,
			userCreated = { _delete = true }
		}

		rxTest:waitFor( wfCatalog:docStore():applyOverlay( versionOverlay ) )

		observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId, false ) )

		observer:expectDataWithinTime( 3, {} )

		observer:dispose()

	end

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

	function WFCatVersionsTest.rxTestRenameVersion( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( function( docValue )
				return docValue.name, docValue.createdManually
			end ), versionName, false )

		local newName = "Bar"

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'renameVersion', wfCatalog, versionId, newName ) )

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( function( docValue )
				return docValue.name, docValue.createdManually
			end ), newName, true )

	end

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

	function WFCatVersionsTest.rxTestDeleteVersion( rxTest )

		rxTest:setTimeLimit( 60)

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"

		local versionId = rxTest:getFirstValue( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local versionSettingsPath

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( function( docValue )

				versionSettingsPath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )

				return docValue

			end )
			:pluck( 'name' ), versionName )

		if WFPlatformInfo.isMobile() then

			local catalogId = wfCatalog:ozCatalogId()

			versionSettingsPath = WFCatAssetUtils.absolutePathToDevelopSettings( catalogId, versionSettingsPath )

		end

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

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteVersion', wfCatalog, versionId ) )

		-- Verify doc is deleted.

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:pluck( '_deleted' ), true )

		-- The purging of these files is handled outside of WFCatVersion by
		-- the purge agents.

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

		WFFileUtils.delete( versionSettingsPath )

	end

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

	function WFCatVersionsTest.rxTestDeleteAllVersionsForAsset( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local manualVersions = {}
		local autoVersions = {}

		for i = 1, 5, 1 do

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true, versionName )

				:map( function( versionId )

					manualVersions[ #manualVersions + 1 ] = {
						id = versionId,
						name = versionName,
						type = "version"
					}

					return true

				end ) )

		 end

		for i = 1, 5, 1 do

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )

				:map( function( versionId )

					autoVersions[ #autoVersions + 1 ] = {
						id = versionId,
						name = versionName,
						type = "version"
					}

					return true

				end ) )

		 end

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteAllVersionsForAsset', wfCatalog, assetId ) )

		-- Verify all versions have been deleted.

		local observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId ) )

		observer:expectDataWithinTime( 5, {} )

		observer:dispose()

		for _, version in ipairs( manualVersions ) do

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
				:pluck( '_deleted' ), true )

		end

		for _, version in ipairs( autoVersions ) do

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
				:pluck( '_deleted' ), true )

		end

	end

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

	function WFCatVersionsTest.rxTestDeleteManualVersionsForAsset( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local manualVersions = {}
		local autoVersions = {}

		for i = 1, 5, 1 do

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true, versionName )

				:map( function( versionId )

					manualVersions[ #manualVersions + 1 ] = {
						id = versionId,
						name = versionName,
						type = "version"
					}

					return true

				end ) )

		 end

		for i = 1, 5, 1 do

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )

				:map( function( versionId )

					autoVersions[ #autoVersions + 1 ] = {
						id = versionId,
						name = versionName,
						type = "version"
					}

					return true

				end ) )

		 end

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteManualVersionsForAsset', wfCatalog, assetId ) )

		-- Verify manual versions have been deleted and auto versions are not deleted

		local observer = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId )
					:map( function( versions )
						return #versions
					end ) )

		observer:expectDataWithinTime( 5, 5 )

		observer:dispose()

		for _, version in ipairs( manualVersions ) do

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
				:pluck( '_deleted' ), true )

		end

		for _, version in ipairs( autoVersions ) do

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
				:pluck( '_deleted' ), nil )

		end

	end

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

	function WFCatVersionsTest.rxTestDeleteAutoVersionsForAsset( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local manualVersions = {}
		local autoVersions = {}

		for i = 1, 5, 1 do
			-- create manual versions

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true, versionName )

				:map( function( versionId )

					manualVersions[ #manualVersions + 1 ] = {
						id = versionId,
						name = versionName,
						type = "version"
					}

					return true

				end ) )

		 end

		for i = 1, 5, 1 do
			-- create auto versions

			local versionName = KSIdGenerator.uuidInLowercaseHex()

			rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )

				:map( function( versionId )

					autoVersions[ #autoVersions + 1 ] = {
						id = versionId,
						name = versionName,
						type = "version",
						linkedLocation = false
					}

					return true

				end ) )

		 end

		-- add an auto version with a linked location

		local versionName = KSIdGenerator.uuidInLowercaseHex()

		WFCatVersions.createVersionForAsset(wfCatalog, assetId, false, versionName, {
					id = 'new-linked-location',
					source = 'apple.icloud'
				}, true)
			:map( function( versionId )

				autoVersions[ #autoVersions + 1 ] = {
					id = versionId,
					name = versionName,
					type = "version",
					linkedLocation = true
				}

				return true

			end )


		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteAutoVersionsForAsset', wfCatalog, assetId, true ) )

		-- Verify auto versions WITHOUT linkedLocation have been deleted and manual versions are not deleted

		for _, version in ipairs( manualVersions ) do

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
				:pluck( '_deleted' ), nil )

		end

		for _, version in ipairs( autoVersions ) do

			if version.linkedLocation then

				rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
					:pluck( '_deleted' ), nil )

			else

				rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
					:pluck( '_deleted' ), true )

			end
		end

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteAutoVersionsForAsset', wfCatalog, assetId, false ) )

		-- Verify auto versions WITH linkedLocation have been deleted

		for _, version in ipairs( autoVersions ) do

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( version.id )
				:pluck( '_deleted' ), true )

		end

	end

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

	function WFCatVersionsTest.rxTestCompareCurrentSettings( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"
		
		rxTest:waitFor( callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAsset', wfCatalog, assetId, false, versionName ) )

		local developSettings = {
			xmpCameraRaw = {
				sha256 = assetInfo.developSettingsSha256
			},
			croppedHeight = 1000,
			croppedWidth = 1000,
			userOrientation = 1
		}

		rxTest:expectSingleValue(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'compareCurrentSettings', wfCatalog, assetId, developSettings ), true )

		-- Mismatched Develop SHA256

		local mismatchedDevelopSettings = KSTableUtils.deepCopy( developSettings )

		mismatchedDevelopSettings.xmpCameraRaw.sha256 = KSIdGenerator.uuidInLowercaseHex()

		rxTest:expectSingleValue(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'compareCurrentSettings', wfCatalog, assetId, mismatchedDevelopSettings ), false )

		-- Mismatched crop

		mismatchedDevelopSettings = KSTableUtils.deepCopy( developSettings )

		mismatchedDevelopSettings.croppedHeight = 300

		rxTest:expectSingleValue(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'compareCurrentSettings', wfCatalog, assetId, mismatchedDevelopSettings ), false )

		-- Mismatched orientation

		mismatchedDevelopSettings = KSTableUtils.deepCopy( developSettings )

		mismatchedDevelopSettings.croppedHeight = 4

		rxTest:expectSingleValue(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'compareCurrentSettings', wfCatalog, assetId, mismatchedDevelopSettings ), false )

		-- Mismatched profile

		mismatchedDevelopSettings = KSTableUtils.deepCopy( developSettings )

		mismatchedDevelopSettings.cameraHref = '/foo/bar'

		rxTest:expectSingleValue(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'compareCurrentSettings', wfCatalog, assetId, mismatchedDevelopSettings ), false )

	end

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

	function WFCatVersionsTest.rxTestDownloadExternalizedXmpForVersion( rxTest )

		rxTest:setTimeLimit( 100 )

		local requestOptions = {
			requestHeaders = {
				{
					header = "Content-Type",
					value = "application/octet-stream",
				},
			},
		}

		local responses

		if WFPlatformInfo.isMobile() and optSubmodelsByImplementation then

			responses = {
				{
					url = "/href/bogus/aux1",
					response = KSData.make( 'foo' ),
					headers = {
						status_code = '200',
					},
					options = requestOptions
				},
				{
					url = "/href/bogus/aux1",
					response = KSData.make( 'bar' ),
					headers = {
						status_code = '200',
					},
					options = requestOptions
				},
				{
					url = "/href/bogus/xmp_develop",
					response = KSData.make( 'blah' ),
					headers = {
						status_code = '200',
					},
				}
			}

		else

			responses = {
				{
					url = "/href/bogus/xmp_develop",
					response = KSData.make( 'blah' ),
					headers = {
						status_code = '200',
					},
				},
				{
					url = "/href/bogus/aux1",
					response = KSData.make( 'foo' ),
					headers = {
						status_code = '200',
					},
					options = requestOptions
				},
				{
					url = "/href/bogus/aux1",
					response = KSData.make( 'bar' ),
					headers = {
						status_code = '200',
					},
					options = requestOptions
				}
			}

		end

		local catalogSpec = require 'WFCatSpecMock'(
				KSIdGenerator.uuidInLowercaseHex(),
				KSIdGenerator.uuidInLowercaseHex(),
				rxTest, responses )

		local wfCatalog = WFCatMockCatalog.createWFCatalogForTestWithSpec( rxTest, catalogSpec )

		local optArguments = {
			hasMasks = true,
			noLocalMasks = true,
			sharedMasks = true
		}

		local function deleteFile( path )

			pcall( WFFileUtils.deleteFile, path )

		end

		local assetDocValue, assetExpecteData = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )
		deleteFile( assetExpecteData.xmpDevelopSettingsFilePath )
		deleteFile( assetExpecteData.mask1FilePath )
		deleteFile( assetExpecteData.mask2FilePath )

		rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDocValue ) )

		optArguments = {
			hasMasks = true,
			noLocalMasks = true,
			noLocalDevelopSettings = true,
			sharedMasks = true
		}

		local docValue, expectedData = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, nil, optArguments )
		docValue.asset.id = assetDocValue._id
		deleteFile( expectedData.xmpDevelopSettingsFilePath )
		deleteFile( expectedData.mask1FilePath )
		deleteFile( expectedData.mask2FilePath )

		local versionId = docValue._id

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

		local xmpDevelopSettingsFilePath

		local saveFunc = function( wfCatalog, xmpKSData, versionDocVal )

			local xmpSha256 = KSTableUtils.derefOpt( versionDocVal, "develop", "xmpCameraRaw", "sha256" )

			local tempFolder = WFTestFileUtils.createTestFolder( rxTest )

			xmpDevelopSettingsFilePath = KSPathUtils.child( tempFolder, xmpSha256 )

			return WFAsyncFileUtils.saveToFile( xmpKSData, xmpDevelopSettingsFilePath )

				:map( function( success )
					return xmpDevelopSettingsFilePath, KSData.length( xmpKSData ), xmpSha256
				end )

		end

		-- Do the download

		if WFPlatformInfo.isMobile() and optSubmodelsByImplementation then
		
			rxTest:waitFor( callFunctionOrSubmodel(
						rxTest, wfCatalog,
						'WFCatVersions',
						'downloadExternalizedXmpForVersion', wfCatalog, versionId ) )
		
		else
			
			rxTest:waitFor( callFunctionOrSubmodel(
						rxTest, wfCatalog,
						'WFCatVersions',
						'downloadExternalizedXmpForVersion', wfCatalog, versionId, saveFunc ) )
						
		end

		-- Verify the Document Has Local Only Aux Annotations

		local function sortAux( auxiliaries )

			local sorted = {}

			for _, aux in pairs( auxiliaries or {} ) do
				sorted[ #sorted + 1 ] = aux
			end

			table.sort( sorted, function( a, b )
				return a.relativePath < b.relativePath
			end )

			return sorted

		end

		local _, mask1RelativePath = WFCatAssetAux.calculatePathUsingDigest( 'foo', expectedData.mask1Fingerprint )
		local _, mask2RelativePath = WFCatAssetAux.calculatePathUsingDigest( 'foo', expectedData.mask2Fingerprint )

		local expected = {
			[ expectedData.mask1Fingerprint ] = {
				relativePath = mask1RelativePath
			},
			[ expectedData.mask2Fingerprint ] = {
				relativePath = mask2RelativePath
			}
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:pluck( '_localOnly', 'files', 'aux' )
			:map( sortAux ), sortAux( expected ) )

	end

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

	function WFCatVersionsTest.rxTestDownloadAuxFilesForVersion( rxTest )

		rxTest:setTimeLimit( 100 )

		local requestOptions = {
			requestHeaders = {
				{
					header = "Content-Type",
					value = "application/octet-stream",
				},
			},
		}

		local responses = {
			{
				url = "/href/bogus/aux1",
				response = KSData.make( 'foo' ),
				headers = {
					status_code = '200',
				},
				options = requestOptions
			},
			{
				url = "/href/bogus/aux1",
				response = KSData.make( 'bar' ),
				headers = {
					status_code = '200',
				},
				options = requestOptions
			}
		}

		local catalogSpec = require 'WFCatSpecMock'(
				KSIdGenerator.uuidInLowercaseHex(),
				KSIdGenerator.uuidInLowercaseHex(),
				rxTest, responses )

		local wfCatalog = WFCatMockCatalog.createWFCatalogForTestWithSpec( rxTest, catalogSpec )

		local optArguments = {
			hasMasks = true,
			noLocalMasks = true,
			sharedMasks = true
		}

		local function deleteFile( path )
			pcall( WFFileUtils.deleteFile, path )
		end

		local assetDocValue, assetExpecteData = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, nil, optArguments )
		deleteFile( assetExpecteData.mask1FilePath )
		deleteFile( assetExpecteData.mask2FilePath )

		rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDocValue ) )

		optArguments = {
			hasMasks = true,
			noLocalMasks = true,
			noLocalDevelopSettings = true,
			sharedMasks = true
		}

		local docValue, expectedData = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, nil, optArguments )
		docValue.asset.id = assetDocValue._id
		deleteFile( expectedData.mask1FilePath )
		deleteFile( expectedData.mask2FilePath )

		local versionId = docValue._id

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

		-- Do the download

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'downloadAuxFilesForVersion', wfCatalog, versionId ) )

		-- Verify the Document Has Local Only Aux Annotations

		local function sortAux( auxiliaries )

			local sorted = {}

			for _, aux in pairs( auxiliaries or {} ) do
				sorted[ #sorted + 1 ] = aux
			end

			table.sort( sorted, function( a, b )
				return a.relativePath < b.relativePath
			end )

			return sorted

		end

		local _, mask1RelativePath = WFCatAssetAux.calculatePathUsingDigest( 'foo', expectedData.mask1Fingerprint )
		local _, mask2RelativePath = WFCatAssetAux.calculatePathUsingDigest( 'foo', expectedData.mask2Fingerprint )

		local expected = {
			[ expectedData.mask1Fingerprint ] = {
				relativePath = mask1RelativePath
			},
			[ expectedData.mask2Fingerprint ] = {
				relativePath = mask2RelativePath
			}
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:pluck( '_localOnly', 'files', 'aux' )
			:map( sortAux ), sortAux( expected ) )

	end

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

if shouldIncludeNightlyTest() then

	function WFCatVersionsTest.rxTestDownloadExternalizedXmpForVersion_testNightly( rxTest )

		rxTest:setRetryLimit( 3 )

		rxTest:setTimeLimit( 360 )

		-- Step 1: Create a version with externalized XMP and upload it to Oz.

		local catalogTestSession = WFCatTestSession( rxTest )

		local catalog, catalogSpec, catalogId, docStore

		local options = {

			continuousTaskDelays = {
				progressiveBackoff = false,
				noWorkDelay = 1,
				maxNoWorkDelay = 1,
				errorDelay = 1,
				maxDelay = 1,
			},

			isCatalogPuller = true

		}

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

		local pusher = WFCatMetadataPusher( catalog, options )
		rxTest:addCloseTarget( pusher )

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

		local uploader = WFCatBinaryUploader( catalog, nil, options, catalogSpec, {
					clearFromRecentlyUploadedSeconds = 1,
					waitForOtherUploadSeconds = .5,
					waitForCopyFailedSeconds = .5,
					timer = function( seconds )
						return Observable.timer( seconds, WFRxScheduler.sharedScheduler() )
					end,
				} )

		rxTest:addCloseTarget( uploader )

		uploader:DEBUG_resetDiagnosticLogErrors()

		rxTest:finally( function()
			puller:stopContinuousUpdates()
			pusher:stopContinuousUpdates()
			uploader:stopContinuousUploads()
		end )

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

		local testDataPath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), "import-samples" )
		local testDataFolder1 = KSPathUtils.child( testDataPath, "folder-1" )
		local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_7763.JPG" )
		local originalSha256 = require 'WFCrypto'.digestFile( originalFilePath, 'SHA256' )
		
		local testTempFolder = WFTestFileUtils.createTestFolder( rxTest )
		local xmpDevelopFilePath = KSPathUtils.child( testTempFolder, 'IMG_7763.XMP' )
		local xmpDevelopFileSize = generateSidecarFile( rxTest, xmpDevelopFilePath )
		local xmpDevelopSha256 = require 'WFCrypto'.digestFile( xmpDevelopFilePath, 'SHA256' )

		local assetDocWithExternalDevelopSettings = WFCatAssetTestUtils.createBaseAssetDocument( catalogId, assetId, revisionId, originalSha256, originalFilePath, nil )
		WFCatAssetTestUtils.addOriginalPathToDocument( assetDocWithExternalDevelopSettings, originalSha256, originalFilePath )

		rxTest:waitFor( catalog:docStore():setDocValue( assetDocWithExternalDevelopSettings ) )
		rxTest:waitFor( addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize ) )

		-- 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/master', 'href') and
				derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' ) then
					return true
				end
			end ) )

		docObserver:expectDataWithinTime(120, true)

		-- Create the version from the asset. Because the asset has externalized xmp, so will the version.

		local versionId, versionRevisionId

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, catalog,
					'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 = 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 )

		versionObserver:dispose()

		-- Step 2: Make sure the version does not have have local external develop settings.

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

		rxTest:waitFor( catalog:docStore():applyOverlay( {
				_id = versionId,
				_localOnly = {
					files = {
						xmp_develop = { _delete = true }
					}
				}
			} ) )

		WFFileUtils.delete( xmpDevelopFilePath )

		rxTest:expectNotEqual( WFFileUtils.exists( xmpDevelopFilePath ), 'file' )

		rxTest:expectSingleValue( catalog:docStore():docValue( versionId )

			:map( function( docValue )

				return WFCatAssetUtils_getLocalExternalDevelopSettingsPath( docValue )

			end ), nil )

		-- Step 3: Attempt to download the externalized develop settings from Oz.

		local xmpDevelopSettingsFilePath

		local saveFunc = function( wfCatalog, xmpKSData, versionDocVal )

			local xmpSha256 = KSTableUtils.derefOpt( versionDocVal, "develop", "xmpCameraRaw", "sha256" )

			local tempFolder = WFTestFileUtils.createTestFolder( rxTest )

			xmpDevelopSettingsFilePath = KSPathUtils.child( tempFolder, xmpSha256 )

			return WFAsyncFileUtils.saveToFile( xmpKSData, xmpDevelopSettingsFilePath )

				:map( function( success )
					return xmpDevelopSettingsFilePath, KSData.length( xmpKSData ), xmpSha256
				end )

		end

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatVersions',
					'downloadExternalizedXmpForVersion', catalog, versionId, saveFunc ) )

		-- Step 4: Verify the local only section and presence of the externalized XMP.

		rxTest:expectSingleValue( catalog:docStore():docValue( versionId )

			:map( function( docValue )

				return WFCatAssetUtils_getLocalExternalDevelopSettingsPath( docValue )

			end ), xmpDevelopSettingsFilePath )

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

	end

end

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

	function WFCatVersionsTest.rxTestCreateVersionForAssetWithSettings( rxTest )

		rxTest:setTimeLimit( 100 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"
		local versionId

		local developSettings = {
			cameraRawXmpStr = '<xmp>HelloJeff</xmp>'
		}

		rxTest:expectSingleValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithSettings', wfCatalog, assetId, true, versionName, developSettings )
			:map( function( newVersionId )

				versionId = newVersionId

				return true

			end ), true )

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft ),
			{
				_id = versionId,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				createdManually = true,
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = developSettings.cameraRawXmpStr,
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				subtype = "edit",
				type = "version",
			} )

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

	function WFCatVersionsTest.rxTestCreateValidVersionForAssetWithSettings( rxTest )

		rxTest:setTimeLimit( 5 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optArguments = {
			hasMasks = true,
			hasFilters = true,
			hasSourceOnlyFilters = nil
		}

		local assetDoc, _ = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optArguments )
		
		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"
		
		local versionId = KSIdGenerator.uuidInLowercaseHex()

		local developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100,
			cameraRawXmpStr = '<xmp>HelloJeff</xmp>',
			_id = versionId
		}
		
		
		rxTest:expectSingleValue(
			callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAssetWithSettings', wfCatalog, assetId, true, versionName, developSettings ),
				versionId )

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

	function WFCatVersionsTest.rxTestCreateValidVersionForAssetWithExternalSettings( rxTest )

		rxTest:setTimeLimit( 5 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		-- Create asset document
		
		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			hasMasks = true
		}

		local assetDoc, extraDocInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )
		
		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		-- Create a new version with external settings
		
		local versionId = KSIdGenerator.uuidInLowercaseHex()
		
		local versionName = "Foo"

		local developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100,
			cameraRawXmpStr = '<xmp>HelloJeff</xmp>',
			auxInfo = {
				{ digest = extraDocInfo.mask1Fingerprint },
				{ digest = extraDocInfo.mask2Fingerprint }
			},
			_id = versionId
		}
		
		rxTest:expectSingleValue(
			callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAssetWithSettings', wfCatalog, assetId, true, versionName, developSettings ),
				versionId )

	end

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

	function WFCatVersionsTest.rxTestCreateInvalidVersionForAssetWithExternalSettings( rxTest )

		rxTest:setTimeLimit( 5 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		-- Create asset
		local assetId = KSIdGenerator.uuidInLowercaseHex()
		
		local assetDoc, _ = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId )
		
		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )
		
		-- Create a dummy document that we will steal auxiliaries from
		local assetId2 = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			hasMasks = true,
			hasFilters = true
		}
		
		local _, extraDocInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId2, optOverrides )

		-- Attempt to create a new version, should fail since the auxilaries are not referenced in the main asset
		local versionName = "Foo"

		local developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100,
			auxInfo = {
				{ digest = extraDocInfo.mask1Fingerprint },
				{ digest = extraDocInfo.mask2Fingerprint }
			}
		}
		
		rxTest:expectOnlyError(
			callFunctionOrSubmodel(
				rxTest, wfCatalog,
				'WFCatVersions',
				'createVersionForAssetWithExternalSettings', wfCatalog, assetId, true, versionName, developSettings ),
			'Version auxiliaries are not referenced in the parent asset document.' )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAssetWithSettings_BadParams( rxTest )

		rxTest:setTimeLimit( 5 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"

		 -- No inline XMP test

		local developSettings = {
			--cameraRawXmpStr = '<xmp>HelloJeff</xmp>'
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithSettings', wfCatalog, assetId, true, versionName, developSettings ),
				':ignore:' )

		-- No asset sha256

		local assetId2 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId2, optOverrides )
		assetDoc2.importSource = nil

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )

		developSettings = {
			cameraRawXmpStr = '<xmp>HelloJeff</xmp>'
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithSettings', wfCatalog, assetId2, true, versionName, developSettings ),
				':ignore:' )

		-- No develop payload

		local assetId3 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc3 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId3, optOverrides )
		assetDoc3.develop = nil

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )

		developSettings = {
			cameraRawXmpStr = '<xmp>HelloJeff</xmp>'
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithSettings', wfCatalog, assetId3, true, versionName, developSettings ),
				':ignore:' )

		-- Existing Doc Id

		local assetId4 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc4 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId4, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc4 ), ':ignore:' )

		developSettings = {
			cameraRawXmpStr = '<xmp>HelloJeff</xmp>',
			_id = assetId3 -- This ID alredy exists in the doc store.
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithSettings', wfCatalog, assetId4, true, versionName, developSettings ),
				':ignore:' )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAssetWithExternalSettings( rxTest )

		rxTest:setTimeLimit( 5 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"
		local versionId

		local developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100
		}

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithExternalSettings', wfCatalog, assetId, true, versionName, developSettings )
			:map( function( newVersionId )

				versionId = newVersionId

				return true

			end ) )

		local sha256FieldName = WFPlatformInfo.isiOS and 'contentId' or 'sha256'

		local localOnly = {
			files = {
				xmp_develop = {
					path = developSettings.cameraRawXmpFilePath,
					fileSize = developSettings.cameraRawXmpSize,
					[ sha256FieldName ] = developSettings.cameraRawXmpSha256,
				}
			}
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft ),
			{
				_id = versionId,
				_localOnly = localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				createdManually = true,
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = {
						sha256 = developSettings.cameraRawXmpSha256
					}
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				subtype = "edit",
				type = "version",
			} )

	end

	----------------------------------------------------------------------------
	
	local function testCreateVersionForAssetWithExternalSettings_WithAux( rxTest, optArgs )

		rxTest:setTimeLimit( 5 )

		optArgs = optArgs or {}

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			hasMasks = true,
			hasFilters = true
		}

		if optArgs.localOnlyResampledAux then

			optOverrides = {
				hasMasks = true,
				hasSourceOnlyFilters = true,
				hasLocalResampledFilter  = true
			}

		end

		-- Create our asset document

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc ) )
	
		local filter1Fingerprint = assetInfo.filter1Fingerprint
		local filter2Fingerprint = assetInfo.filter2Fingerprint
		local groupDigest = assetInfo.filterGroupFingerprint

		if optArgs.localOnlyResampledAux then 

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetId )
				:map( function( docValue )
					local resampledAuxId = WFCatAssetAux.getAuxIdForDigest( docValue, filter2Fingerprint )
					return resampledAuxId == nil
				end ),
				true )
				
		end

		local versionName = "Foo"
		local auxDataList = { 
			{ digest = assetInfo.mask1Fingerprint },
			{ digest = assetInfo.mask2Fingerprint },
			{
				digest = filter2Fingerprint,
				groupDigest = groupDigest,
				targetLongEdge = 2560,
				crsSizeX = 437,
				crsSizeY = 328
			}
		}
		
		local developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100,
			auxInfo = auxDataList
		}
		
		local versionId = rxTest:getFirstValue(
			callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithExternalSettings', wfCatalog, assetId, true, versionName, developSettings )
		)
		-- Verify the version document was created correctly
		local sha256FieldName = WFPlatformInfo.isiOS and 'contentId' or 'sha256'

		local localOnly = {
			files = {
				xmp_develop = {
					path = developSettings.cameraRawXmpFilePath,
					fileSize = developSettings.cameraRawXmpSize,
					[ sha256FieldName ] = developSettings.cameraRawXmpSha256,
				},
				aux = {
					[ assetInfo.mask1Fingerprint ] = {
						relativePath = assetInfo.mask1RelativeFilePath
					},
					[ assetInfo.mask2Fingerprint ] = {
						relativePath = assetInfo.mask2RelativeFilePath
					},
					[ assetInfo.filter2Fingerprint ] = {
						relativePath = assetInfo.filter2RelativeFilePath
					}
				}
			}
		}

		local sortMasksOrFilters = function( tbl )

			table.sort( tbl, function( a, b )
				return a.digest > b.digest
			end )

			return tbl

		end

		local function mapAwayMaskAuxIds( docValue )

			-- Map out the auxId key in the develop.masks section because
			-- they are unknown.

			local masks = docValue.develop.masks

			local newMasks = {}

			for _, auxInfo in pairs( masks ) do
				newMasks[ #newMasks + 1 ] = auxInfo
			end

			docValue.develop.masks = sortMasksOrFilters( newMasks )

			return docValue

		end

		local expectedMasks = sortMasksOrFilters( {
			{
				digest = assetInfo.mask1Fingerprint
			},
			{
				digest = assetInfo.mask2Fingerprint
			}
		} )

		local function mapAwayFilterAuxIds( docValue )

			-- Map out the auxId key in the develop.filters[ groupDigest ] section because
			-- they are unknown.

			local filters = docValue.develop.filters

			local newFilters = {}

			for filterGroupDigest, groupAuxInfoList in pairs( filters ) do

				local newGroupAuxDataList = {}

				for _, auxInfo in pairs( groupAuxInfoList ) do
					newGroupAuxDataList[ #newGroupAuxDataList + 1 ] = auxInfo
				end

				newFilters[ filterGroupDigest ] = sortMasksOrFilters( newGroupAuxDataList )

			end 

			docValue.develop.filters = newFilters

			return docValue

		end
		
		local expectedFilters = {
			[ groupDigest ] = sortMasksOrFilters ( {
				{
					sourceLongEdge = 6000,
					crsSizeX = 1024,
					crsSizeY = 768,
					digest = filter1Fingerprint
				},
				{
					targetLongEdge = 2560,
					crsSizeX = 437,
					crsSizeY = 328,
					digest = filter2Fingerprint
				}
			} )
		}

		local expected = {
			_id = versionId,
			_localOnly = localOnly,
			asset = {
				id = assetId,
				sha256 = assetInfo.originalSha256,
			},
			createdManually = true,
			develop = {
				processingModel = 'lightroom',
				croppedHeight = 1000,
				croppedWidth = 1000,
				userOrientation = 1,
				xmpCameraRaw = {
					sha256 = developSettings.cameraRawXmpSha256
				},
				masks = expectedMasks,
				filters = expectedFilters
			},
			importedOnDevice = {
				formFactor = expectedFormFactor
			},
			name = versionName,
			subtype = "edit",
			type = "version",
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( mapAwayCruft )
			:map( mapAwayMaskAuxIds )
			:map( mapAwayFilterAuxIds ),
			expected )

		if optArgs.localOnlyResampledAux then 

			rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetId )
				:map( function( docValue )
					local resampledAuxId = WFCatAssetAux.getAuxIdForDigest( docValue, filter2Fingerprint )
					if resampledAuxId then
						return not not KSTableUtils.derefOpt( docValue, 'develop', 'filters', groupDigest, resampledAuxId )
					end
					return false
				end ),
				true )

		end

	end

	----------------------------------------------------------------------------
	
	function WFCatVersionsTest.rxTestCreateVersionForAssetWithExternalSettings_WithAux( rxTest )
	
		testCreateVersionForAssetWithExternalSettings_WithAux( rxTest )

	end

	----------------------------------------------------------------------------
	
	function WFCatVersionsTest.rxTestCreateVersionForAssetWithExternalSettings_WithAux_localOnlyResampledAux( rxTest )

		testCreateVersionForAssetWithExternalSettings_WithAux( rxTest, { localOnlyResampledAux = true } )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAssetWithExternalSettings_BadParams( rxTest )

		rxTest:setTimeLimit( 100 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionName = "Foo"

		-- Missing required fields

		local developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithExternalSettings', wfCatalog, assetId, true, versionName, developSettings ),
				':ignore:' )

		-- No asset sha256

		local assetId2 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId2, optOverrides )
		assetDoc2.importSource = nil

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )

		developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithExternalSettings', wfCatalog, assetId2, true, versionName, developSettings ),
				':ignore:' )

		-- No develop payload

		local assetId3 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc3 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId3, optOverrides )
		assetDoc3.develop = nil

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )

		developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithExternalSettings', wfCatalog, assetId3, true, versionName, developSettings ),
				':ignore:' )

		-- Existing Doc Id

		local assetId4 = KSIdGenerator.uuidInLowercaseHex()
		local assetDoc4 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId4, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc4 ), ':ignore:' )

		developSettings = {
			cameraRawXmpFilePath = "Foo",
			cameraRawXmpSha256 = KSIdGenerator.uuidInLowercaseHex(),
			cameraRawXmpSize = 100,
			_id = assetId3 -- This ID alredy exists in the doc store.
		}

		rxTest:expectOnlyError(
				callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAssetWithExternalSettings', wfCatalog, assetId4, true, versionName, developSettings ),
				':ignore:' )

	end

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

	function WFCatVersionsTest.rxTestDuplicateVersion_InternalSettings( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local sourceVersionName = "Foo"
		local sourceVersionId

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, sourceVersionName )
			:map( function( newVersionId )

				sourceVersionId = newVersionId

				return true

			end ) )

		local duplicateVersionName = "Dupe"
		local duplicateVersionId

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'duplicateVersion', wfCatalog, sourceVersionId, true, duplicateVersionName )
			:map( function( newVersionId )

				duplicateVersionId = newVersionId

				return true

			end ) )

		local _localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( duplicateVersionId )
			:map( mapAwayCruft ),
			{
				_id = duplicateVersionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				createdManually = true,
				name = duplicateVersionName,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestDuplicateVersion_ExternalSettings( rxTest )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optArgs = {
			hasMasks = true
		}

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local sourceVersionName = "Foo"
		local sourceVersionId

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, sourceVersionName )
			:map( function( newVersionId )

				sourceVersionId = newVersionId

				return true

			end ) )

		local duplicateVersionName = "Dupe"
		local duplicateVersionId

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'duplicateVersion', wfCatalog, sourceVersionId, true, duplicateVersionName )
			:map( function( newVersionId )

				duplicateVersionId = newVersionId

				return true

			end ) )

		local _localOnly = {
			files = {
				xmp_develop = {
					fileSize = assetInfo.xmpDevelopSettingsFileSize,
					path = assetInfo.xmpDevelopSettingsFilePath,
					sha256 = assetInfo.developSettingsSha256,
				}
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = {
				files = {
					xmp_develop = {
						fileSize = assetInfo.xmpDevelopSettingsFileSize,
						path = '<path>',
						contentId = getLocalExternalDevelopSettingsContentId( assetDoc ),
					}
				},
			}

		elseif WFPlatformInfo.isAndroid then

			_localOnly = {
				files = {
					xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
				},
			}

		end

		_localOnly.files.aux = {
			[ assetInfo.mask1Fingerprint ] = {
				relativePath = assetInfo.mask1RelativeFilePath
			},
			[ assetInfo.mask2Fingerprint ] = {
				relativePath = assetInfo.mask2RelativeFilePath
			}
		}

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( duplicateVersionId )
			:map( mapAwayCruft )
			:map( function( docValue )

				if WFPlatformInfo.isiOS then

					local path = KSTableUtils.derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'path' )

					assert( path )
					assert( path ~= assetInfo.xmpDevelopSettingsFilePath )

					KSTableUtils.setNested( docValue, '<path>', '_localOnly', 'files', 'xmp_develop', 'path' )

				end

				return docValue

			end ),
			{
				_id = duplicateVersionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = {
						sha256 = assetInfo.developSettingsSha256,
					},
					masks = {
						[ assetInfo.mask1AuxId ] = {
							digest = assetInfo.mask1Fingerprint,
						},
						[ assetInfo.mask2AuxId ] = {
							digest = assetInfo.mask2Fingerprint,
						},
					},
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				createdManually = true,
				name = duplicateVersionName,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestGetVersionsAnalytics( rxTest )

		rxTest:setTimeLimit( 30 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		 for i = 1, 99, 1 do

			local createdManually
			local linkedLocation

			if i % 2 == 0 then
				createdManually = true

				linkedLocation = {
					id = KSIdGenerator.uuidInLowercaseHex(),
					source = string.format('%s/%s', KSIdGenerator.uuidInLowercaseHex(), KSIdGenerator.uuidInLowercaseHex()),
					sha256 = 'i-am-a-sha-256'
				}

			else

				createdManually = false
				linkedLocation = nil

			end

			rxTest:waitFor( require 'WFCatVersions'.createVersionForAsset( wfCatalog, assetId, createdManually, nil, linkedLocation ) )

		 end

		rxTest:expectSingleValue(
			callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'getVersionsAnalytics', wfCatalog ),
			{
				implicitImportVersionCount = 49,
				implicitVersionsCount = 50,
				manualVersionsCount = 49
			} )


	end

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

	function WFCatVersionsTest.rxTestMaybeDeleteTempAutoVersion( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		local assetId = KSIdGenerator.uuidInLowercaseHex()

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

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		local versionId

		-- Deletes the old version with x seconds
		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false )
			:map( function( newVersionId )

				versionId = newVersionId

				return true

			end ) )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'maybeDeleteTempAutoVersion', wfCatalog, assetId, 60 ), versionId )


		-- Only manual version is present. Nothing should be deleted.
		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true )
			:map( function( newVersionId )

				versionId = newVersionId

				return true

			end ) )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'maybeDeleteTempAutoVersion', wfCatalog, assetId, 60 ), nil )


		-- Latest auto-version was created before x seconds. Shouldnt get deleted.
		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, true )
			:map( function( newVersionId )

				versionId = newVersionId

				return true

			end ) )

		rxTest:sleep( 1 )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'maybeDeleteTempAutoVersion', wfCatalog, assetId, 0.5 ), nil )

	end

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

	function WFCatVersionsTest.rxTestCheckUserAuthorization( rxTest )

		rxTest:setTimeLimit( 10 )

		local wfCatalog = createFakeWFCatalog( rxTest )

		-- Insert a space doc into the doc store.

		local spaceId = KSIdGenerator.uuidInLowercaseHex()
		local ownerId = KSIdGenerator.uuidInLowercaseHex()
		local spaceUserId = KSIdGenerator.uuidInLowercaseHex()

		local spaceDoc = {
			_id = spaceId,
			type = 'space',
			subtype = 'shared',
			download = false,
			private = false,
			location = false,
			metadata = false,
			_serverProvidedMetadata = {
				owner = {
					id = ownerId
				},
				this_space_user = {
					id = spaceUserId
				}
			}
		}

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceDoc ), ':ignore:' )

		-- Add an space_user to the space

		local spaceUserDoc = {
			_id = spaceUserId,
			type = 'space_user',
			space = { id = spaceId },
			user = { id = KSIdGenerator.uuidInLowercaseHex() },
			role = 'viewer'
		}

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceUserDoc ), ':ignore:' )

		-- Create asset

		local assetId = KSIdGenerator.uuidInLowercaseHex()

		local optOverrides = {
			internalXmpCameraRaw = true
		}

		local assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )
		assetDoc._spaceId = spaceId

		rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )

		-- Create version should throw an error

		local errorMessage = string.format( "User doesn't have authorized role for space %s, with role %s",
											spaceId, 'viewer' )

		local versionName = "Foo"
		local versionId

		rxTest:expectOnlyError( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName ),
			errorMessage )

		-- Update space role to editor and create version

		local spaceUserOverlay = {
			_id = spaceUserDoc._id,
			type = 'space_user',
			role = 'editor'
		}

		rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )

				versionId = newVersionId

				return true

			end ) )

		-- Set space role back to viewer

		spaceUserOverlay.role = 'viewer'

		rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )

		-- Duplicate version should throw an error

		local duplicateVersionName = 'Dupe'
		local duplicateVersionId

		rxTest:expectOnlyError( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'duplicateVersion', wfCatalog, versionId, false, duplicateVersionName ),
			errorMessage )

		-- Rename version should throw an error

		local newVersionName = 'Bar'

		rxTest:expectOnlyError( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'renameVersion', wfCatalog, versionId, newVersionName ),
			errorMessage )

		-- Delete version should throw an error

		rxTest:expectOnlyError( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteVersion', wfCatalog, versionId ),
			errorMessage )

		-- Delete versions with assetId should throw an error

		rxTest:expectOnlyError( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteAllVersionsForAsset', wfCatalog, assetId ),
			errorMessage )

		-- Set space role to editor and retry version update

		spaceUserOverlay.role = 'editor'

		rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )

		-- Duplicate version

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'duplicateVersion', wfCatalog, versionId, true, duplicateVersionName )
			:map( function( newVersionId )

				duplicateVersionId = newVersionId

				return true

			end ) )

		local _localOnly = {
			files = {
				xmp_develop = KSTableUtils.derefOpt( assetDoc, '_localOnly', 'files', 'xmp_develop' )
			},
			xmpSidecarExists = false,
		}

		if WFPlatformInfo.isiOS then

			_localOnly = nil

		elseif WFPlatformInfo.isAndroid then

			_localOnly.xmpSidecarExists = nil

		end

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( duplicateVersionId )
			:map( mapAwayCruft ),
			{
				_id = duplicateVersionId,
				_spaceId = spaceId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					processingModel = 'lightroom',
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				createdManually = true,
				name = duplicateVersionName,
				subtype = "edit",
				type = "version",
			} )

		-- Rename version

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'renameVersion', wfCatalog, versionId, newVersionName ) )

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:map( function( docValue )
				return docValue.name, docValue.createdManually
			end ), newVersionName, true )

		-- Delete Version

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteVersion', wfCatalog, versionId ) )

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId )
			:pluck( '_deleted' ), true )

		-- Delete All Version

		rxTest:waitFor( callFunctionOrSubmodel(
					rxTest, wfCatalog,
					'WFCatVersions',
					'deleteAllVersionsForAsset', wfCatalog, assetId ) )

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( duplicateVersionId )
			:pluck( '_deleted' ), true )

	end

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

	function WFCatVersionsTest.rxTestAssetsHaveManualVersions( rxTest )
		local wfCatalog = createFakeWFCatalog( rxTest )
		
		-- Create three assets
		local asset1Id = KSIdGenerator.uuidInLowercaseHex()
		local asset2Id = KSIdGenerator.uuidInLowercaseHex()
		local asset3Id = KSIdGenerator.uuidInLowercaseHex()
				
		local assetDoc1 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, asset1Id )
		local assetDoc2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, asset2Id )
		local assetDoc3 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, asset3Id )
		
		rxTest:expectSingleValue(wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:')
		rxTest:expectSingleValue(wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:')
		rxTest:expectSingleValue(wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:')
		
		-- Test 1: With no versions - should return false
		rxTest:expectSingleValue(
			WFCatVersions.assetsHaveManualVersions( wfCatalog, { asset1Id, asset2Id, asset3Id } ),
			false
		)
		
		-- Create a manual version for asset1
		local versionName1 = "Manual Version"
		rxTest:waitFor( callFunctionOrSubmodel(
			rxTest, wfCatalog,
			'WFCatVersions',
			'createVersionForAsset', wfCatalog, asset1Id, true, versionName1 -- true for manual creation
		) )
		
		-- Test 2: With 1 manually created version - should return true
		
		rxTest:expectSingleValue(
			WFCatVersions.assetsHaveManualVersions( wfCatalog, { asset1Id } ),
			true
		)
		
		
		-- Create an auto version for asset2
		local versionName2 = "Auto Version"
		rxTest:waitFor( callFunctionOrSubmodel(
			rxTest, wfCatalog,
			'WFCatVersions',
			'createVersionForAsset', wfCatalog, asset2Id, false, versionName2 -- false for auto version
		) )
		
		-- Test 2: With 1 auto version - should return false
		rxTest:expectSingleValue(
			WFCatVersions.assetsHaveManualVersions( wfCatalog, { asset2Id } ),
			false
		)
		
		-- Create a manual version for asset3
		local versionName3 = "Manual Version"
		rxTest:waitFor( callFunctionOrSubmodel(
			rxTest, wfCatalog,
			'WFCatVersions',
			'createVersionForAsset', wfCatalog, asset3Id, true, versionName3 -- true for manual creation
		) )
		
		--Create an auto version for asset3
		local versionName4 = "Manual Version"
		rxTest:waitFor( callFunctionOrSubmodel(
			rxTest, wfCatalog,
			'WFCatVersions',
			'createVersionForAsset', wfCatalog, asset3Id, false, versionName4 -- true for manual creation
		) )
		
		-- Test 3: With both manual and auto version - should return true
		
		rxTest:expectSingleValue(
			WFCatVersions.assetsHaveManualVersions( wfCatalog, { asset3Id } ),
			true
		)
		
		-- Test 4: Test with asset1 having manual version and asset2 having auto version - should return true
		
		rxTest:expectSingleValue(
			WFCatVersions.assetsHaveManualVersions( wfCatalog, { asset1Id, asset2Id } ),
			true
		)
		
		-- Test 5: Test with asset2 having auto version and asset3 having both version - should return true 
		
		
		rxTest:expectSingleValue(
			WFCatVersions.assetsHaveManualVersions( wfCatalog, { asset2Id, asset3Id } ),
			true
		)
	end

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

	return maybeExistingTestClass and WFCatVersionsTest
			or WFRxTestCase.wrapSuite( 'WFCatVersionsTest', WFCatVersionsTest )

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

end
