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

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 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 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
	
	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 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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = 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( versionId )
			:map( mapAwayCruft ), 
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = assetInfo.xmpDevelopSettingsString
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				createdManually = false,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAsset_ExternalSettings( 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"
		local versionId
		
		rxTest:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = 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

		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 = {
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = {
						sha256 = assetInfo.developSettingsSha256,
					},
				},
				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 guarenteed 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 versionId2 = rxTest:getFirstValue( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId2, 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 = {
					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.

		rxTest:expectSingleValue( wfCatalog:docStore():docValue( versionId2 )
			:map( mapAwayCruft ), 
			{
				_id = versionId2,
				_localOnly = _localOnly,
				asset = {
					id = assetId2,
					sha256 = assetInfo2.originalSha256,
				},
				develop = {
					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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = 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( versionId )
			:map( mapAwayCruft ), 
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = assetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = 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( versionId )
			:map( mapAwayCruft ), 
			{
				_id = versionId,
				_localOnly = _localOnly,
				asset = {
					id = newAssetId,
					sha256 = assetInfo.originalSha256,
				},
				develop = {
					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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, nil )
			:map( function( newVersionId )
			
				versionId = newVersionId
				
				return true
			
			end ) )

		local observer = rxTest:observe( 
				callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'getAllVersionsStreamForAsset', wfCatalog, assetId )
					:map( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							version.embeddedDevelopSettings = "settings"
							
						end
					
						return versions
						
					end ) )
		
		observer:expectDataWithinTime( 3, { {
			authorFormFactor = expectedFormFactor,
			croppedHeight = 1000,
			croppedWidth = 1000,
			embeddedDevelopSettings = "settings",
			hasExternalizedDevelopSettings = false,
			id = versionId,
			createdManually = false,
			type = "edit",
			userOrientation = 1,
		} } )
		
		observer:dispose()

	end

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

	function WFCatVersionsTest.rxTestGetVersionsStreamForAsset( 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 versions = {}
		local manualVersions = {}
		local autoVersions = {}
		
		local expectedVersionZero = {
			croppedHeight = 1000,
			croppedWidth = 1000,
			createdManually = false,
			type = "edit",
			userOrientation = 1,
			isFakeImportStateVersion = 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",
						hasExternalizedDevelopSettings = true,
						croppedWidth = 1000,
						croppedHeight = 1000,
						userOrientation = 1,
						createdManually = false,
						authorFormFactor = expectedFormFactor,
					}
					
					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",
						hasExternalizedDevelopSettings = true,
						croppedWidth = 1000,
						croppedHeight = 1000,
						userOrientation = 1,
						createdManually = true,
						authorFormFactor = expectedFormFactor,
					}
					
					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( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
			:expectDataWithinTime( 3, versions )
			:dispose()
			
		-- check manual versions for asset
		
		rxTest:observe( 
				callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'getManualVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
			:expectDataWithinTime( 3, manualVersions )
			:dispose()
			
		-- check non-manual versions for asset
		
		rxTest:observe( 
				callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'getAutoVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
			: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
		}
		
		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",
					hasExternalizedDevelopSettings = true,
					croppedWidth = 1000,
					croppedHeight = 1000,
					userOrientation = 1,
					createdManually = true,
					author = feedbackuserId,
					authorFullname = 'James Smith',
					authorFormFactor = expectedFormFactor,
				}
				
				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",
					hasExternalizedDevelopSettings = true,
					croppedWidth = 1000,
					croppedHeight = 1000,
					userOrientation = 1,
					createdManually = false,
					author = feedbackuserId,
					authorFullname = 'James Smith',
					authorFormFactor = expectedFormFactor,
				}
				
				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( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
			:expectDataWithinTime( 3, versions )
			:dispose()
			
		-- check manual versions for asset
		
		rxTest:observe( 
				callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'getManualVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
			:expectDataWithinTime( 3, manualVersions )
			:dispose()
			
		-- check non-manual versions for asset
		
		rxTest:observe( 
				callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'getAutoVersionsStreamForAsset', wfCatalog, assetId, true )
					:map( function( versions )
					
						for _, version in ipairs( versions ) do
						
							version.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
			: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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = newVersionId
				
				return true
			
			end ) )

		-- 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.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
		
		observer:expectDataWithinTime( 3, { {
		
			createdManually = false,
			croppedHeight = 1000,
			croppedWidth = 1000,
			externalizedDevelopSettingsPath = "path",
			hasExternalizedDevelopSettings = true,
			id = versionId,
			name = "Foo",
			type = "edit",
			userOrientation = 1,
			authorFormFactor = expectedFormFactor,

		} } )
		
		-- 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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = newVersionId
				
				return true
			
			end ) )

		-- 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.date = nil
							version.externalizedDevelopSettingsPath = version.externalizedDevelopSettingsPath and "path"
							
						end
					
						return versions
						
					end ) )
		
		observer:expectDataWithinTime( 3, { {
		
			createdManually = false,
			croppedHeight = 1000,
			croppedWidth = 1000,
			externalizedDevelopSettingsPath = "path",
			hasExternalizedDevelopSettings = true,
			id = versionId,
			name = "Foo",
			type = "edit",
			userOrientation = 1,
			authorFormFactor = expectedFormFactor,

		} } )
		
		-- 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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = newVersionId
				return true
			
			end ) )

		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:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = newVersionId
				return true
			
			end ) )

		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 
		 
			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',
					'deleteAutoVersionsForAsset', wfCatalog, assetId ) )
		
		-- Verify auto versions have been deleted and manual 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' ), nil )
		
		end
		
		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"
		local versionId
		
		rxTest:waitFor( callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'createVersionForAsset', wfCatalog, assetId, false, versionName )
			:map( function( newVersionId )
			
				versionId = newVersionId
				return true
			
			end ) )
		
		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

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

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 xmpDevelopFilePath = KSPathUtils.child( WFTestFileUtils.createTestFolder( rxTest ), '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

		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 = {
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = developSettings.cameraRawXmpStr,
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				subtype = "edit",
				type = "version",
			} )

	end

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

	function WFCatVersionsTest.rxTestCreateVersionForAssetWithSettings_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"

		 -- 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( 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 = {
			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 = {
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = {
						sha256 = developSettings.cameraRawXmpSha256
					}
				},
				importedOnDevice = {
					formFactor = expectedFormFactor
				},
				name = versionName,
				subtype = "edit",
				type = "version",
			} )

	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 = {
					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 assetDoc, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId )
		
		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

		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 = {
					croppedHeight = 1000,
					croppedWidth = 1000,
					userOrientation = 1,
					xmpCameraRaw = {
						sha256 = assetInfo.developSettingsSha256,
					},
				},
				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 = false
			
			if i % 2 == 0 then
				createdManually = true
			end
		 
			rxTest:waitFor( require 'WFCatVersions'.createVersionForAsset( wfCatalog, assetId, createdManually ) )
		 
		 end

		rxTest:expectSingleValue( 
			callFunctionOrSubmodel( 
					rxTest, wfCatalog, 
					'WFCatVersions',
					'getVersionsAnalytics', wfCatalog ),
			{
				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 = {
					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

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

	return maybeExistingTestClass and WFCatVersionsTest 
			or WFRxTestCase.wrapSuite( WFCatVersionsTest )
	
--============================================================================--

end
