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

WFCatAssetTestFactory.lua

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

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

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

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

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

	-- WF.oz_client.managed_catalog
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatAsset = require 'WFCatAsset'

	-- WF.core.utils
local KSDate = require 'KSDate'
local KSIdGenerator = require 'KSIdGenerator'
local WFDeviceInfo = require 'WFDeviceInfo'
local WFModelsUtils = require 'WFModelsUtils'
local WFImageOrientation = require 'WFImageOrientation'
local WFCatCollaborationActivityTestUtils = require "WFCatCollaborationActivityTestUtils"
local WFPlatformInfo = require 'WFPlatformInfo'
local KSTableUtils = require 'KSTableUtils'

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

local createFakeWFCatalog = WFCatMockCatalog.createFakeWFCatalog
local derefOpt = KSTableUtils.derefOpt
local setNested = KSTableUtils.setNested

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

local randomDocId = '7206c3ae9a094d69861ee8983e95085d'

local kDefaultDescription = "WFCatTask"

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

return function( optSubmodelsByImplementation, maybeExistingTestClass )

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

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

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

	end

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

	local WFCatAssetTest = maybeExistingTestClass or {}

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

	function WFCatAssetTest.rxTestRatings( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		rxTest:expectSingleValue(

			docStore:setDocValue {
					_id = randomDocId,
					type = 'asset',
					subtype = 'image',
					_localOnly = {
						files = {
							proxy = {
								pano = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								}
							}
						}
					},
					importSource = {
						filePath = '/path/',
						originalHeight = 150,
						originalWidth = 100,
					}
				},

			':ignore:' )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setRatings', fakeWFCatalog, { 1 }, 4 ),
			':ignore:' )

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromIndexStore', fakeWFCatalog, {
					assetIds = { 1 },
					ratings = true,
				}
				) )
		:expectDataWithinTime( 3, {
			{
				rating = 4,
			},
		} )

	end

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

	function WFCatAssetTest.rxTestFlags( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		rxTest:expectSingleValue(

			docStore:setDocValue {
					_id = randomDocId,
					type = 'asset',
					subtype = 'image',
					_localOnly = {
						files = {
							proxy = {
								pano = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								}
							}
						}
					},
					importSource = {
						filePath = '/path/',
						originalHeight = 150,
						originalWidth = 100,
					}
				},

			':ignore:' )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setFlags', fakeWFCatalog, { 1 }, 'pick' ),
			':ignore:' )

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromIndexStore', fakeWFCatalog, {
				assetIds = { 1 },
				flags = true,
				coalesce = true,
			} ) )
			:expectDataWithinTime( 3, {
				flag = 'pick',
				numberOfAssets = 1,
			} )

	end

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

-- FIXME [jandrew 2022-03-04]:  This test causes a crasher on Android. WFCatAsset is only
-- used by LrD, so disabling this on Android is not that bad.

if not WFPlatformInfo.isAndroid then

	function WFCatAssetTest.rxTestRotateOrFlip_testNightly( rxTest )

		rxTest:setTimeLimit( 90 )

		local assetTypes = { "image", "video" }
		local rotateOptions = { "cw", "ccw", "NULL"}
		local flipOptions = { "flipH", "flipV", "NULL"}
		local imageOrientationLocation = { 'userOrientation' }
		local videoOrientationLocation = { 'adjustments', 'geometric', 'orientation' }

		local expectedVFVersion = 2
		-- TODO [btran 2022-05-09]: Update this with API to get current VF version

		local expectedVFProcessVersion = 1 -- Current Default is 1

		for _, assetType in ipairs( assetTypes ) do
			for _, rotation in ipairs( rotateOptions ) do
				for _, flip in ipairs( flipOptions ) do
					for tiffOrientation = 1, 8 do
						-- rotateOrFlipImages will only rotate or flip the asset, not both.
						-- Excatly of the operations must be no-op (NULL) for this test to work.
						if ( rotation == "NULL" or flip == "NULL" ) and flip ~= rotation then

							local fakeWFCatalog = createFakeWFCatalog( rxTest )
							local docStore = fakeWFCatalog:docStore()

							local isVideo = assetType == "video"
							local developPayload = nil
							local orientationLocation = nil

							local thisRotation = rotation
							local thisFlip = flip
							local startAgOrientation = WFImageOrientation.tiffOrientationToAgOrientation( tiffOrientation )
							local endAgOrientation = startAgOrientation
							local startOrientation = nil
							local endOrientation = nil


							if thisRotation == "NULL" then
								thisRotation = nil
							else
								endAgOrientation = WFImageOrientation.rotate( endAgOrientation, thisRotation )
							end

							if thisFlip == "NULL" then
								thisFlip = nil
							else
								endAgOrientation = WFImageOrientation.flip( endAgOrientation, thisFlip )
							end

							if isVideo then
								orientationLocation = videoOrientationLocation
								startOrientation = WFImageOrientation.AgOrientationToVFOrientation( startAgOrientation )
								developPayload = {
									-- Flip and rotate should keep the same
									-- version and process version for videos,
									-- or default to the current version.
									version = expectedVFVersion,
									processVersion = nil,

									adjustments = {
										geometric = {
											orientation = startOrientation
										}
									}
								}
								endOrientation = WFImageOrientation.AgOrientationToVFOrientation( endAgOrientation )
							else
								orientationLocation = imageOrientationLocation
								startOrientation = tiffOrientation
								developPayload = {
									userOrientation = startOrientation
								}
								endOrientation = WFImageOrientation.AgOrientationTOTiffOrientation( endAgOrientation )
							end

							rxTest:logf( "Checking %s asset. Starting Orientation: %s, Rotate: %s, Flip: %s. Expected End Orientation: %s",
										assetType,
										startOrientation,
										thisRotation,
										thisFlip,
										endOrientation )

							rxTest:expectSingleValue(
								docStore:setDocValue {
									_id = randomDocId,
									type = 'asset',
									subtype = assetType,
									_localOnly = {
										files = {
											proxy = {
												pano = {
													relativePath = 'foo',
													volumeGuid = 'bar'
												}
											}
										}
									},
									importSource = {
										filePath = '/path/',
										originalHeight = 150,
										originalWidth = 100,
									},

									develop = developPayload

								},

							':ignore:' )

							rxTest:expectSingleValue( callFunctionOrSubmodel(
								rxTest, fakeWFCatalog,
								'WFCatAsset',
								'rotateOrFlipImages', fakeWFCatalog, { 1 }, thisRotation, thisFlip ),
							':ignore:' )

							if isVideo then
								rxTest:observe( fakeWFCatalog:docStore():ongoingDocValue( randomDocId )
									:pluck( 'develop', 'version' )
								 )
								:expectDataWithinTime( 3, expectedVFVersion )

								rxTest:observe( fakeWFCatalog:docStore():ongoingDocValue( randomDocId )
									:pluck( 'develop', 'processVersion' )
								 )
								:expectDataWithinTime( 3, expectedVFProcessVersion )
							end

							rxTest:observe( fakeWFCatalog:docStore():ongoingDocValue( randomDocId )
								:pluck( 'develop', unpack( orientationLocation ) )
							 )
							:expectDataWithinTime( 3, endOrientation )
							:dispose()
						end
					end
				end
			end
		end
	end

end

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

	function WFCatAssetTest.rxTestKeywordsSingleAsset( rxTest )

		local timeOut = 5

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		docStore:setDocValue {
			_id = "doc_1",
			type = 'asset',
			subtype = 'image',
			_localOnly = {
				files = {
					proxy = {
						pano = {
							relativePath = 'foo',
							volumeGuid = 'bar'
						}
					}
				}
			},
		}

		local obs = rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setKeywords', fakeWFCatalog, { 1 }, { "KeywordA" } )
				:map( function( data )
					if data and derefOpt( data, 'extraInfo', 'undoToken' ) then
						setNested( data, 1, 'extraInfo', 'undoToken' )
					end
					return data
				end )
			)

		obs:expectDataWithinTime( timeOut, {
			description = kDefaultDescription,
			errorCount = 0,
			percentComplete = 0,
			processedCount = 0,
			totalCount = 1,
		})

		obs:expectDataWithinTime( timeOut, {
			description = kDefaultDescription,
			errorCount = 0,
			percentComplete = 1,
			processedCount = 1,
			totalCount = 1,
		})

		obs:expectDataWithinTime( timeOut, {
			description = kDefaultDescription,
			errorCount = 0,
			extraInfo = {
				totalCount = 1,
				undoToken = 1,
			},
			percentComplete = 1,
			processedCount = 1,
			totalCount = 1,
		})

		obs:expectCompleted()

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromIndexStore', fakeWFCatalog, {
					assetIds = { 1 },
					keywords = true,
				} ) )
			:expectData {
				{
					keywords = {
						"KeywordA",
					},
				},
			}

	end

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

	function WFCatAssetTest.rxTestKeywordsMultiAssets( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		docStore:bulkSetDocValue {
			{
				_id = "doc_1",
				type = 'asset',
				subtype = 'image',
				_localOnly = {
					files = {
						proxy = {
							pano = {
								relativePath = 'foo',
								volumeGuid = 'bar'
							}
						}
					}
				},
			},

			{
				_id = "doc_2",
				type = 'asset',
				subtype = 'image',
				_localOnly = {
					files = {
						proxy = {
							pano = {
								relativePath = 'foo',
								volumeGuid = 'bar'
							}
						}
					}
				},
			}
		}

		local obs = rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setKeywords', fakeWFCatalog, { 1 }, { "KeywordA", "KeywordB" } )
			:takeLast( 0 ) )

		obs:expectCompleted()

		local obs2 = rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setKeywords', fakeWFCatalog, { 2 }, { "KeywordA" } )
			:takeLast( 0) )

		obs2:expectCompleted()

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromIndexStore', fakeWFCatalog, {
					assetIds = { 1 , 2 },
					keywords = true,
				} ) )
			:expectData {
				{
					keywords = {
						"KeywordB",
						"KeywordA",
					},
				},
				{
					keywords = {
						"KeywordA",
					},
				},
			}

	end

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

	function WFCatAssetTest.rxTestKeywordsDelete( rxTest )

		local timeOut = 10

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		docStore:setDocValue {
			_id = "doc_1",
			type = 'asset',
			subtype = 'image',
			_localOnly = {
				files = {
					proxy = {
						pano = {
							relativePath = 'foo',
							volumeGuid = 'bar'
						}
					}
				}
			},
		}

		-- Add three keywords for test
		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setKeywords', fakeWFCatalog, { 1 }, { "KeywordA", "KeywordB", "KeywordC" } ) )

		-- Test delete keywords

		local optUndoStateToken

		local deleteKeywordObs = rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setKeywords', fakeWFCatalog, { 1 }, { "KeywordA" }, true )
				:map( function( data )
					if data and derefOpt( data, 'extraInfo', 'undoToken' ) then
						setNested( data, 1, 'extraInfo', 'undoToken' )
						optUndoStateToken = derefOpt( data, 'extraInfo', 'undoToken' )
					end
					return data
				end )
			)

		deleteKeywordObs:expectDataWithinTime( timeOut, {
			description = kDefaultDescription,
			errorCount = 0,
			percentComplete = 0,
			processedCount = 0,
			totalCount = 1,
		})

		deleteKeywordObs:expectDataWithinTime( timeOut, {
			description = kDefaultDescription,
			errorCount = 0,
			percentComplete = 1,
			processedCount = 1,
			totalCount = 1,
		})

		deleteKeywordObs:expectDataWithinTime( timeOut, {
			description = kDefaultDescription,
			errorCount = 0,
			extraInfo = {
				totalCount = 1,
				undoToken = 1,
			},
			percentComplete = 1,
			processedCount = 1,
			totalCount = 1,
		})

		deleteKeywordObs:expectCompleted()

		-- Check the result data
		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = { 1 },
				keywords = true,
			} )
			:map( function( info )

				 table.sort( info[1].keywords )
				 return info

			end )
			)
			:expectData {
				{
					keywords = { "KeywordB", "KeywordC" },
				},
			}

	end

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

	function WFCatAssetTest.rxTestCommentsFromIndexStore( rxTest )

		local timeOut = 5

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = WFCatCollaborationActivityTestUtils.createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		local albumInfo, assetInfo = WFCatCollaborationActivityTestUtils.createSharedAlbumWithAsset( rxTest, fakeWFCatalog )

		local currUserId = fakeWFCatalog:ozUserId()

			-- Simulate already added comment

		local comments = {
			WFCatCollaborationActivityTestUtils.createComment( albumInfo.spaceId, assetInfo.globalId, currUserId, "comment1" ),
		}

		rxTest:expectOnlyCompleted(
			docStore:bulkSetDocValue( comments )
				:takeLast( 0 ) )

			-- Collect assetInfo from the index store

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromIndexStore', fakeWFCatalog, {
				assetIds = { assetInfo.localId },
				comments = true,
				albumId = albumInfo.localId
			} ) )

			:expectDataWithinTime( timeOut, {
				{
					comment = true,
				},
			} )

	end

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

	function WFCatAssetTest.rxTestFavoritesFromIndexStore( rxTest )

		local timeOut = 5

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = WFCatCollaborationActivityTestUtils.createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		local albumInfo, assetInfo = WFCatCollaborationActivityTestUtils.createSharedAlbumWithAsset( rxTest, fakeWFCatalog )

		local currUserId = fakeWFCatalog:ozUserId()

			-- Simulate already added favorite

		local favoriteDoc1 = WFCatCollaborationActivityTestUtils.createFavorite( albumInfo.spaceId, assetInfo.globalId, currUserId )
		rxTest:expectOnlyCompleted(
			docStore:bulkSetDocValue( { favoriteDoc1 } )
				:takeLast( 0 ) )

			-- Collect assetInfo from the index store

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromIndexStore', fakeWFCatalog, {
				assetIds = { assetInfo.localId },
				favorites = true,
				albumId = albumInfo.localId
			} ) )

			:expectDataWithinTime( timeOut, {
				{
					favorite = true,
				},
			} )

	end

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

	function WFCatAssetTest.rxTestRatingsFromDocStore( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		rxTest:expectSingleValue(

			docStore:setDocValue {
					_id = randomDocId,
					type = 'asset',
					subtype = 'image',
					_localOnly = {
						files = {
							proxy = {
								pano = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								}
							}
						}
					},
					importSource = {
						filePath = '/path/',
						originalHeight = 150,
						originalWidth = 100,
					},
				},

			':ignore:' )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setRatings', fakeWFCatalog, { 1 }, 4 ), ':ignore:' )

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = { 1 },
				ratings = true,
			} ) )
			:expectDataWithinTime( 3, {
				{
					rating = 4,
				},
			} )

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

	function WFCatAssetTest.rxTestFlagsFromDocStore( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		local docStore = fakeWFCatalog:docStore()

		rxTest:expectSingleValue(

			docStore:setDocValue {
					_id = randomDocId,
					type = 'asset',
					subtype = 'image',
					_localOnly = {
						files = {
							proxy = {
								pano = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								}
							}
						}
					},
					importSource = {
						filePath = '/path/',
						originalHeight = 150,
						originalWidth = 100,
					},
				},

			':ignore:' )

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'setFlags', fakeWFCatalog, { 1 }, 'pick' ),
			':ignore:' )

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = { 1 },
				flags = true,
			} ) )
			:expectDataWithinTime( 3, {
				{
					flag = 'pick',
				}
			} )

	end

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

	local function addTestAsset( rxTest, catalog, localId, params )

		local docId = KSIdGenerator.uuidInLowercaseHex()

		local opts = { }

		if params.spaceId then
			opts.isSpaceAsset = true
			opts.spaceId = params.spaceId
		end

		local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, catalog, docId, opts )
		assetDoc._localId = localId

		assetDoc._serverProvidedMetadata.importedByUser = {
			id = params.importedByUser,
		}
		assetDoc._serverProvidedMetadata.developedByUser = params.maybeDevelopedByUser
		assetDoc.captureDate = params.maybeCaptureDate
		assetDoc.develop = params.maybeDevelop
		assetDoc.importSource.fileName = params.maybeFileName
		assetDoc.xmp = {
			exif = {
				DateTimeOriginal = params.maybeDateTimeOriginal,
			},
			photoshop = {
				DateCreated = params.maybeDateCreated,
			},
			xmp = {
				CreateDate = params.maybeCreateDate,
			}
		}

		rxTest:waitFor( catalog:docStore():bulkSetDocValue { assetDoc } )

		return docId

	end

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

	local function addTestFeedbackuser( rxTest, catalog, maybeFirstName, maybeLastName )

		local docId = KSIdGenerator.uuidInLowercaseHex()

		maybeFirstName = maybeFirstName or 'Asterix'
		maybeLastName = maybeLastName or 'of Gaul'

		local fullName = maybeFirstName .. ' ' .. maybeLastName

		rxTest:waitFor(
			catalog:docStore():bulkSetDocValue {
				{
					_id = docId,
					_serverProvidedMetadata = {
						full_name = fullName,
						first_name = maybeFirstName,
						last_name = maybeLastName,
					},
					type = 'feedbackuser',
				}
			}
		)

		return docId

	end

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

	function WFCatAssetTest.rxTestCollectCaptureDateTimeRangeForAssets( rxTest )

		local timeOut = 10

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		addTestAsset( rxTest, fakeWFCatalog, 1, {
			maybeCaptureDate = '1904-01-01T00:00Z',
			maybeDateTimeOriginal = '1904-01-01T00:00Z',
			maybeDateCreated = '1904-01-01T00:00Z',
			maybeCreateDate = '1904-01-01T00:00Z',
		} )

		addTestAsset( rxTest, fakeWFCatalog, 1, {
			maybeCaptureDate = '2004-01-01T00Z',
			maybeDateTimeOriginal = '2004-01-01T00Z',
			maybeDateCreated = '2004-01-01T00Z',
			maybeCreateDate = '2004-01-01T00Z',
		} )

		local assetIds = { 1, 2 }

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectCaptureDateTimeRangeForAssets', fakeWFCatalog, assetIds ) )
			:expectDataWithinTime( timeOut, {
				captureDateTimeRange = {
					maxCaptureDate = "2004-01-01T00:00:00Z",
					maxTime = 20040101000000,
					minCaptureDate = "1904-01-01T00:00:00Z",
					numOfEmptyCaptureDate = 0,
					minTime = 19040101000000,
					processedImages = 2,
				},
				numberOfAssets = 2,
			} )

	end

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

	function WFCatAssetTest.rxTestSharedInfoForAssetsFromDocStore( rxTest )

		local timeOut = 10

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		addTestAsset( rxTest, fakeWFCatalog, 1, {
			maybeCaptureDate = '1904-01-01T00:00Z',
			maybeDateTimeOriginal = '1904-01-01T00:00Z',
			maybeDateCreated = '1904-01-01T00:00Z',
			maybeCreateDate = '1904-01-01T00:00Z',
			importedByUser = 'foo',
		} )

		addTestAsset( rxTest, fakeWFCatalog, 1, {
			maybeCaptureDate = '2004-01-01T00Z',
			maybeDateTimeOriginal = '2004-01-01T00Z',
			maybeDateCreated = '2004-01-01T00Z',
			maybeCreateDate = '2004-01-01T00Z',
			spaceId = 'bar',
		} )

		local assetIds = { 1, 2 }

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = assetIds,
				importedByUser = true,
				spaceInfo = true,
			} ) )
			:expectDataWithinTime( timeOut, {
				{
					importedByUser = 'foo',
				},
				{
					spaceId = 'bar',
					importedByUser = fakeWFCatalog:ozUserId(),
				},
			} )

	end

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

	function WFCatAssetTest.rxTestUnsupportedFormatForAssets( rxTest )
		local timeOut = 20

		rxTest:setTimeLimit( timeOut )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		addTestAsset( rxTest, fakeWFCatalog, 1, {
			maybeFileName = 'File1.JPG'
		} )

		addTestAsset( rxTest, fakeWFCatalog, 2, {
			maybeFileName = 'File2.HEIC'
		} )

		addTestAsset( rxTest, fakeWFCatalog, 3, {
			maybeFileName = 'File2.PNG'
		} )

		local NmbAcr = require 'NmbAcr'
		local isHeifSupported = NmbAcr.WFCrInfo.supportsHeic()

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = { 1, 2, 3 },
				isUnsupportedFormat = true,
			} ) )
			:expectDataWithinTime( timeOut, {
				{ isUnsupportedFormat = false, },
				{ isUnsupportedFormat = not isHeifSupported, },
				{ isUnsupportedFormat = false, },
			} )

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = { 1, 2, 3 },
				isUnsupportedFormat = true,
				coalesce = true,
			} ) )
			:expectDataWithinTime( timeOut, {
				numberOfAssets = 3,
				isUnsupportedFormat = not isHeifSupported,
			} )

		rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'collectInfoForAssetsFromDocStore', fakeWFCatalog, {
				assetIds = { 1, 3 },
				isUnsupportedFormat = true,
				coalesce = true,
			} ) )
			:expectDataWithinTime( timeOut, {
				numberOfAssets = 2,
				isUnsupportedFormat = false,
			} )

	end

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

	function WFCatAssetTest.rxTestGetAssetLatestEditDetails( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		-- Create a fake user with an associated feedbackuser document

		local editUserId = addTestFeedbackuser( rxTest, fakeWFCatalog )

		local docId1 = addTestAsset( rxTest, fakeWFCatalog, 2, {
			maybeFileName = 'File1.JPG',
			maybeDevelop = {
				userUpdated = "A long time ago",
				device = "Some new Device",
			},
			maybeDevelopedByUser = {
				id = editUserId,
			},
		} )

		local docId2 = addTestAsset( rxTest, fakeWFCatalog, 3, {
			maybeFileName = 'File2.JPG',
			maybeDevelop = {
				userUpdated = "A long time ago",
				device = "Some new Device",
			},
			maybeDevelopedByUser = nil
		} )

		-- the returned table should have data from both the asset and feedback document

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'getAssetLatestEditDetails', fakeWFCatalog,
				2
			),
			{
				userUpdated = "A long time ago",
				userDevice = "Some new Device",
				userName = "Asterix of Gaul",
			} )

		-- if there is no feedback document, userName should not be reported

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'getAssetLatestEditDetails', fakeWFCatalog,
				3
			),
			{
				userUpdated = "A long time ago",
				userDevice = "Some new Device",
			} )

		-- add temp links to each document

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId1,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/xmp/develop/123456789' ] = {
						develop = {
							userUpdated = "More recently",
							device = "The newest device",
						},
						uploadedByUser = {
							id = editUserId
						},
					}
				}
			}
		} )

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId2,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/aux/123456789' ] = {
						develop = {
							userUpdated = "More recently",
							device = "The newest device",
						}
					}
				}
			}
		} )

		-- temp link info should also be included

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'getAssetLatestEditDetails', fakeWFCatalog,
				2
			),
			{
				userUpdated = "A long time ago",
				userDevice = "Some new Device",
				userName = "Asterix of Gaul",
				tempBinary = {
					userUpdated = "More recently",
					userDevice = "The newest device",
					userName = "Asterix of Gaul",
				}
			} )

		-- if there is no feedback document, userName should not be reported

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'getAssetLatestEditDetails', fakeWFCatalog,
				3
			),
			{
				userUpdated = "A long time ago",
				userDevice = "Some new Device",
				tempBinary = {
					userUpdated = "More recently",
					userDevice = "The newest device",
				}
			} )

	end

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

	function WFCatAssetTest.rxTestGetLatestEditDetailsForAssets( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		-- Create a fake user with an associated feedbackuser document

		local editUserId = addTestFeedbackuser( rxTest, fakeWFCatalog )

		local docId1 = addTestAsset( rxTest, fakeWFCatalog, 2, {
			maybeFileName = 'File1.JPG',
			maybeDevelop = {
				userUpdated = "A long time ago",
				device = "Some new Device",
			},
			maybeDevelopedByUser = {
				id = editUserId,
			},
		} )

		local docId2 = addTestAsset( rxTest, fakeWFCatalog, 3, {
			maybeFileName = 'File2.JPG',
			maybeDevelop = {
				userUpdated = "A long time ago",
				device = "Some new Device",
			},
			maybeDevelopedByUser = nil
		} )

		-- the returned table should have data from both the asset and feedback document

		local expected = {
			[ docId2 ] = {
				userDevice = "Some new Device",
				userUpdated = "A long time ago"
			},
			[ docId1 ] = {
				userDevice = "Some new Device",
				userName = "Asterix of Gaul",
				userUpdated = "A long time ago"
			}
		}

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'getLatestEditDetailsForAssets', fakeWFCatalog,
				{ docId1, docId2 } ), expected )

		-- add temp links to each document

		local editUserId2 = addTestFeedbackuser( rxTest, fakeWFCatalog, 'Dorothy', 'Gale' )

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId2,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/xmp/develop/123456789' ] = {
						develop = {
							userUpdated = "More recently",
							device = "The newest device",
						},
						uploadedByUser = {
							id = editUserId2
						},
					}
				}
			}
		} )

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId1,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/aux/123456789' ] = {
						develop = {
							userUpdated = "More recently",
							device = "The newest device",
						}
					}
				}
			}
		} )

		-- the returned table should include the temp link info

		expected[ docId2 ].tempBinary = {
			userUpdated = "More recently",
			userDevice = "The newest device",
			userName = "Dorothy Gale",
		}
		expected[ docId1 ].tempBinary = {
			userUpdated = "More recently",
			userDevice = "The newest device",
		}

		rxTest:expectSingleValue( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'getLatestEditDetailsForAssets', fakeWFCatalog,
				{ docId1, docId2 } ), expected )

	end

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

	function WFCatAssetTest.rxTestGetOtherClientEditDetails( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		-- Create a fake user with an associated feedbackuser document

		local editUserId = addTestFeedbackuser( rxTest, fakeWFCatalog )

		local longTimeAgo = "2020-06-08T20:41:31.478821Z"

		local docId1 = addTestAsset( rxTest, fakeWFCatalog, 2, {
			maybeFileName = 'File1.JPG',
			maybeDevelop = {
				userUpdated = longTimeAgo,
				device = "A device that isn't thisDevice",
			},
			maybeDevelopedByUser = {
				id = editUserId,
			},
		} )

		local moreRecently = "2021-06-08T20:41:31.478821Z"

		local docId2 = addTestAsset( rxTest, fakeWFCatalog, 3, {
			maybeFileName = 'File2.JPG',
			maybeDevelop = {
				userUpdated = moreRecently,
				device = WFDeviceInfo.deviceDescription()
			},
			maybeDevelopedByUser = nil
		} )

		-- the returned table should have data from both the asset and feedback document

		rxTest:expectSingleValue( WFCatAsset.getOtherClientEditDetails( fakeWFCatalog, 2 ),
			{
				hasTempEdits = false,
			} )

		-- if there is no feedback document, userName should not be reported

		rxTest:expectSingleValue( WFCatAsset.getOtherClientEditDetails( fakeWFCatalog, 3 ),
			{
				hasTempEdits = false,
			} )

		-- add temp links to each document

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId1,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/xmp/develop/123456789' ] = {
						develop = {
							userUpdated = moreRecently,
							device = "different user device {device-guid-id}",
						},
						uploadedByUser = {
							id = editUserId
						},
					}
				}
			}
		} )

		local userUpdatedNow = KSDate.timeInIso8601Format()
		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId2,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/aux/123456789' ] = {
						develop = {
							userUpdated = userUpdatedNow,
							device = "The newest device {device-guid-id}",
						}
					}
				}
			}
		} )

		-- don't report for other user
		rxTest:expectSingleValue( WFCatAsset.getOtherClientEditDetails( fakeWFCatalog, 2 ),
			{
				hasTempEdits = false,
			} )

		-- report for this user since device is different than
		-- this device
		rxTest:expectSingleValue( WFCatAsset.getOtherClientEditDetails( fakeWFCatalog, 3)
			:map( function( details )
				details.userUpdatedAsString = type( details.userUpdatedAsString )
				return details
			end ),
			{
				hasTempEdits = true,
				userDeviceWithoutId = "The newest device",
				userUpdatedAsString = 'string',
			} )


		-- Now update the temp link to look like it came from this device
		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId2,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/aux/123456789' ] = {
						develop = {
							userUpdated = userUpdatedNow,
							device = WFDeviceInfo.deviceDescription(),
						}
					}
				}
			}
		} )

		-- don't report hasTempEdits when it's this device
		rxTest:expectSingleValue( WFCatAsset.getOtherClientEditDetails( fakeWFCatalog, 3 ),
			{
				hasTempEdits = false,
			} )

	end

	function WFCatAssetTest.rxTestOngoingGetOtherClientEditDetailsStream( rxTest )

		rxTest:setTimeLimit( 5 )

		local fakeWFCatalog = createFakeWFCatalog( rxTest )

		-- Create a fake user with an associated feedbackuser document

		local editUserId = addTestFeedbackuser( rxTest, fakeWFCatalog )


		local localDocId1 = 2
		local docId1 = addTestAsset( rxTest, fakeWFCatalog, localDocId1, {
			maybeFileName = 'File1.JPG',
			maybeDevelop = {
				userUpdated = "A long time ago",
				device = "A device that isn't thisDevice",
			},
			maybeDevelopedByUser = {
				id = editUserId,
			},
		} )

		local localDocId2 = 3
		local docId2 = addTestAsset( rxTest, fakeWFCatalog, localDocId2, {
			maybeFileName = 'File2.JPG',
			maybeDevelop = {
				userUpdated = "2023-06-08T20:41:31.478821Z",
				device = WFDeviceInfo.deviceDescription()
			},
			maybeDevelopedByUser = nil
		} )

		local doc1OtherClientEditDetailsStream = rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'ongoingGetOtherClientEditDetails', fakeWFCatalog,
				localDocId1
				)
				-- For ease of testing - lets not do the "time" thing
				:map( function( details )
					details.userUpdatedAsString = details.userUpdatedAsString and type( details.userUpdatedAsString )
					return details
				end )
		)

		local doc2OtherClientEditDetailsStream = rxTest:observe( callFunctionOrSubmodel(
				rxTest, fakeWFCatalog,
				'WFCatAsset',
				'ongoingGetOtherClientEditDetails', fakeWFCatalog,
				localDocId2
				)
				-- For ease of testing - lets not do the "time" thing
				:map( function( details )
					details.userUpdatedAsString = details.userUpdatedAsString and type( details.userUpdatedAsString )
					return details
				end )
				)

		-- the returned table should have data from both the asset and feedback document

		doc1OtherClientEditDetailsStream:expectDataWithinTime( 1,
			{
				hasTempEdits = false,
			} )

		-- if there is no feedback document, userName should not be reported

		doc2OtherClientEditDetailsStream:expectDataWithinTime(1,
			{
				hasTempEdits = false,
			} )

		-- add temp links to each document

		local doc1SpmWithTempLinks = {
			links = {
				[ '/rels/temp/xmp/develop/123456789' ] = {
					develop = {
						userUpdated = "More recently",
						device = "different user device {device-guid-id}",
					},
					uploadedByUser = {
						id = editUserId
					},
				}
			}
		}

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay( {
			_id = docId1,
			_serverProvidedMetadata = doc1SpmWithTempLinks
		}) )


		-- Due to the nature of time, and the lack of wanting to retool this
		-- test to exclude a direct match in an expectation - force the update
		-- times to match
		local userUpdatedNow = KSDate.timeInIso8601Format()

		local doc2SpmWithTempLinks = {
			links = {
				[ '/rels/temp/aux/123456789' ] = {
					develop = {
						userUpdated = userUpdatedNow,
						device = "The newest device {device-guid-id}",
					}
				}
			}
		}

		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId2,
			_serverProvidedMetadata = doc2SpmWithTempLinks
		} )

		-- don't report for other user
		-- The underlying asset has changed, so a new data should be generated
		doc1OtherClientEditDetailsStream:expectDataWithinTime( 1,
			{
				hasTempEdits = false,
			} )

		-- report for this user since device is different than
		-- this device
		doc2OtherClientEditDetailsStream
			:expectDataWithinTime( 1,
			{
				hasTempEdits = true,
				userDeviceWithoutId = "The newest device",
				userUpdatedAsString = "string"
			} )


		-- Now update the temp link for the SECOND asset to look like it came from this device (The device running the test)
		rxTest:waitFor( fakeWFCatalog:docStore():applyOverlay {
			_id = docId2,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/temp/aux/123456789' ] = {
						develop = {
							userUpdated = userUpdatedNow,
							device = WFDeviceInfo.deviceDescription(),
						}
					}
				}
			}
		} )

		-- don't report hasTempEdits when it's this device
		doc2OtherClientEditDetailsStream:expectDataWithinTime( 1,
			{
				hasTempEdits = false,
			} )

	end

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

	if not require 'WFPlatformInfo'.hasACR then
		WFCatAssetTest.rxTestUnsupportedFormatForAssets = nil
	end

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

	return maybeExistingTestClass and WFCatAssetTest 
			or WFRxTestCase.wrapSuite( 'WFCatAssetTest', WFCatAssetTest )
	
	--============================================================================--

end
