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

WFCatSharedWithYouAlbumsTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 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.

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

local Rx = require 'Rx'

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

	-- WF.core.utils
local KSDate = require 'KSDate'
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.managed_catalog
local WFCatAlbums = require 'WFCatAlbums'
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatSharedWithYouAlbums = require 'WFCatSharedWithYouAlbums'

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

local WFCatMockCatalog_createFakeWFCatalog = assert( WFCatMockCatalog.createFakeWFCatalog )

local KSTableUtils_derefOpt = assert( KSTableUtils.derefOpt )

local KSTableUtils_setNested = assert( KSTableUtils.setNested )

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

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 WFCatSharedWithYouAlbumsTest = {}

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

function WFCatSharedWithYouAlbumsTest.rxTestCreateBasicModel( rxTest )

	rxTest:setTimeLimit( 60 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	local model = WFCatSharedWithYouAlbums.makeModel( Rx.Observable.just( wfCatalog ) )
	
	assert( model )
	
end

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

local function buildDocValue( rxTest, wfCatalog, assetId, overrides )

	assetId = assetId or KSIdGenerator.uuidInLowercaseHex()
	
	return WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, overrides )

end

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

function WFCatSharedWithYouAlbumsTest.rxTestGetSpaceIdOfAlbumId( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert a shared-with-you album into the doc store.
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	local albumLocalId1
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( albumId )
		:map( function( docValue )
			albumLocalId1 = docValue._localId
			return true
		end ), true )
	
	-- Insert a normal album into the doc store.
	
	local albumId2 = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc2 = {

		_id = albumId2,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc2 ), ':ignore:' )
	
	-- Verify the space ID of album 1

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.getSpaceIdOfAlbumId( wfCatalog, albumId ), spaceId )
	
	-- Verify the space ID of album 2

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.getSpaceIdOfAlbumId( wfCatalog, albumId2 ), false )
			
	-- Verify the space ID of album 1 using the local id.
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.getSpaceIdOfAlbumId( wfCatalog, albumLocalId1 ), spaceId )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestSetOfSha256sInSpaceId( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId2 = KSIdGenerator.uuidInLowercaseHex()
	
	-- Insert 3 assets into the doc store.
	
	local assetDoc1, expectedData1 = buildDocValue( rxTest, wfCatalog )
	assetDoc1._spaceId = spaceId
	
	local assetDoc2, expectedData2 = buildDocValue( rxTest, wfCatalog )
	assetDoc2._spaceId = spaceId
	
	local assetDoc3, expectedData3 = buildDocValue( rxTest, wfCatalog )
	assetDoc3._spaceId = spaceId2
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Verify the asset SHA256 values in each space.

	rxTest:sleep( 1 )

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.setOfSha256sInSpaceId( wfCatalog, spaceId ), {
				[ expectedData1.originalSha256 ] = true,
				[ expectedData2.originalSha256 ] = true,
			} )
			
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.setOfSha256sInSpaceId( wfCatalog, spaceId2 ), {
				[ expectedData3.originalSha256 ] = true,
			} )
			
	-- Make it look like Oz modified the original and SHA256 because of a metadata
	-- redaction.
	
	rxTest:expectSingleValue(
		wfCatalog:docStore():applyOverlay {
			_id = assetDoc1._id,
			importSource = {
				sha256 = KSIdGenerator.uuidInLowercaseHex(),
				originalSHA256 = expectedData1.originalSha256
			}
		}, ':ignore:' )
		
	local newShaForAssetDoc2 = KSIdGenerator.uuidInLowercaseHex()
		
	rxTest:expectSingleValue( -- Destructive edit: not a metadata redaction.
		wfCatalog:docStore():applyOverlay {
			_id = assetDoc2._id,
			importSource = {
				sha256 = newShaForAssetDoc2
			}
		}, ':ignore:' )

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.setOfSha256sInSpaceId( wfCatalog, spaceId ), {
				[ expectedData1.originalSha256 ] = true,
				[ newShaForAssetDoc2] = true,
			} )
			
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert 3 assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	local assetDoc3 = buildDocValue( rxTest, wfCatalog )
	
	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetDoc3._id )
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	local assetDoc1FromDocStore, assetDoc2FromDocStore, assetDoc3FromDocStore
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc1._id )
		:map( function( docValue )
			assetDoc1FromDocStore = docValue
			return true
		end ), ':ignore:' )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc2._id )
		:map( function( docValue )
			assetDoc2FromDocStore = docValue
			return true
		end ), ':ignore:' )
		
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc3._id )
		:map( function( docValue )
			assetDoc3FromDocStore = docValue
			return true
		end ), ':ignore:' )

	-- Insert a shared-with-you album into the doc store.
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, assetIds, redactionOptions ), ':ignore:', {}, ':ignore:' )
	
	-- Verify that there are now 3 album assets and that the corresponding
	-- asset IDs don't match the asset id of our original assets.
	
	-- Note that we don't validate the copied assets here. That's taken
	-- care of by the WFCatCopies unit tests.
	
	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end

	local query = wfCatalog:indexStore():queryOnce {
		
		sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
		quiesceUntilUpToDate = true
		
	}

	rxTest:expectSingleValue( 
		query:map( function( rows )
		
			for _, row in ipairs( rows ) do
				
				local copiedId = row[ 1 ]
				local copiedAlbumId = row[ 2 ]
				
				rxTest:expectEqual( copiedAlbumId, albumId )
				
				for _, assetId in ipairs( assetIds ) do
				
					rxTest:expectNotEqual( copiedId, assetId )
				
				end
				
			end
		
			return #rows
		
		end ), 3 )
	
	-- Verify source assets remain unchanged.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc1._id ), assetDoc1FromDocStore )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc2._id ), assetDoc2FromDocStore )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc3._id ), assetDoc3FromDocStore )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_DetectInvalidAsset( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = createFakeWFCatalog( rxTest )

	-- Insert 4 assets into the doc store. One valid, three invalid

	local assetDoc1 = buildDocValue( rxTest, wfCatalog )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc1 ))
	local assetDoc1FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( assetDoc1._id ) )

	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	KSTableUtils_setNested(assetDoc2, 'does not match', 'importSource', 'status', 'error' , 'all' , 'reason' , 'sha256' )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc2 ))
	local assetDoc2FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( assetDoc2._id ) )

	local assetDoc3 = buildDocValue( rxTest, wfCatalog )
	KSTableUtils_setNested(assetDoc3, true, '_serverProvidedMetadata', 'links', '/rels/master' , 'invalid' )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc3 ))
	local assetDoc3FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( assetDoc3._id ) )

	local assetDoc4 = buildDocValue( rxTest, wfCatalog )
	KSTableUtils_setNested(assetDoc4, 'InvalidLocalOriginal', '_localOnly', 'localOriginalError')
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc4 ))
	local assetDoc4FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( assetDoc4._id ) )

	local assetIds = { assetDoc1._id, assetDoc2._id, assetDoc3._id, assetDoc4._id }

	local invalidAssetIds = { assetDoc2._id, assetDoc3._id, assetDoc4._id }

	-- Insert a shared-with-you album into the doc store.

	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()

	local albumDoc = {
		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',
	}
	
	rxTest:waitFor( wfCatalog:docStore():setDocValue( albumDoc ))
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, assetIds, redactionOptions ), ':ignore:', {}, invalidAssetIds)
	
	-- Verify that there is 1 album asset and that the corresponding
	-- asset ID does not match the asset id of our original asset.
	
	-- Note that we don't validate the copied assets here. That's taken
	-- care of by the WFCatCopies unit tests.
	
	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end

	local query = wfCatalog:indexStore():queryOnce {
		sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
		quiesceUntilUpToDate = true
	}

	rxTest:expectSingleValue( 
		query:map( function( rows )
		
			for _, row in ipairs( rows ) do
				
				local copiedId = row[ 1 ]
				local copiedAlbumId = row[ 2 ]
				
				rxTest:expectEqual( copiedAlbumId, albumId )
				
				for _, assetId in ipairs( assetIds ) do
				
					rxTest:expectNotEqual( copiedId, assetId )
				
				end
				
			end
		
			return #rows
		
		end ), 1 )

	-- Verify source assets remain unchanged.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc1._id ), assetDoc1FromDocStore )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc2._id ), assetDoc2FromDocStore )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc3._id ), assetDoc3FromDocStore )

end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_RoleCheck( 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:' )

	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':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 assets:
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	local assetDoc3 = buildDocValue( rxTest, wfCatalog )
	
	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetDoc3._id )
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	local assetDoc1FromDocStore, assetDoc2FromDocStore, assetDoc3FromDocStore
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc1._id )
		:map( function( docValue )
			assetDoc1FromDocStore = docValue
			return true
		end ), ':ignore:' )
		
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc2._id )
		:map( function( docValue )
			assetDoc2FromDocStore = docValue
			return true
		end ), ':ignore:' )
		
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc3._id )
		:map( function( docValue )
			assetDoc3FromDocStore = docValue
			return true
		end ), ':ignore:' )
	
	-- Add asset1 to album as viewer
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDoc1._id }, redactionOptions ), {}, {}, {} )
					
	-- Observe that asset is not added to album
	
	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end
	
	local albumAssetObserve = rxTest:observe( wfCatalog:indexStore():query {
			sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
			quiesceUntilUpToDate = true
		}
		:map( function( rows )
		
			for _, row in ipairs( rows ) do
				
				local copiedId = row[ 1 ]
				local copiedAlbumId = row[ 2 ]
				
				rxTest:expectEqual( copiedAlbumId, albumId )
				
				for _, assetId in ipairs( assetIds ) do
				
					rxTest:expectNotEqual( copiedId, assetId )
				
				end
				
			end
		
			return #rows
		
		end )
	)

	albumAssetObserve:expectDataWithinTime( 2, 0 )
	
	-- set space_user role to contributor and add asset 2 to album
	
	local spaceUserOverlay = {
		_id = spaceUserDoc._id,
		type = 'space_user',
		role = 'contributor'
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDoc2._id }, redactionOptions ), ':ignore:', {}, ':ignore:' )
					
	albumAssetObserve:expectDataWithinTime( 2, 1 )
	
	-- set space_user role to editor and add asset 2 to album
	
	spaceUserOverlay.role = 'editor'
	
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDoc3._id }, redactionOptions ), ':ignore:', {}, ':ignore:' )
					
	albumAssetObserve:expectDataWithinTime( 2, 2 )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_DetectsDuplicates( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert 3 assets into the doc store.
	
	local assetDoc1, expectedData1 = buildDocValue( rxTest, wfCatalog )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	local assetDoc3 = buildDocValue( rxTest, wfCatalog )
	
	local allAssetIds = {}
	table.insert( allAssetIds, assetDoc1._id )
	table.insert( allAssetIds, assetDoc2._id )
	table.insert( allAssetIds, assetDoc3._id )
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Copy just assetDoc1 into the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	local copiedAssetIds = {}
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDoc1._id }, redactionOptions )
				:map( function( newIds, ... )
				
					copiedAssetIds = newIds
					
					return newIds, ...
				
				end ), ':ignore:', {}, ':ignore:' )
	
	-- Verify that there is now 1 album asset.

	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end

	rxTest:expectSingleValue( 
		wfCatalog:indexStore():queryOnce {
		
			sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
			quiesceUntilUpToDate = true
			
		}
		:map( function( rows )

			return #rows
		
		end ), 1 )

	-- Now copy ALL of the assets into the shared-with-you album, including
	-- the asset we just copied. We should not recopy the asset
	-- we previously copied.
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, allAssetIds, redactionOptions )
				:map( function( newIds, ... )
				
					copiedAssetIds = KSTableUtils.appendArray( copiedAssetIds, newIds )
					
					return newIds, ...
				
				end ), ':ignore:', { assetDoc1._id }, ':ignore:' )
	
	-- Verify that there are now 3 album assets and that the corresponding
	-- asset IDs don't match the asset id of our original assets.

	rxTest:expectSingleValue( 
		wfCatalog:indexStore():queryOnce {
		
			sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
			quiesceUntilUpToDate = true
			
		}
		:map( function( rows )

			return #rows
		
		end ), 3 )
		
	-- Now modify all the assets in the shared-with-you album to look like
	-- they were imported by a different user.
	
	for _, copiedId in ipairs( copiedAssetIds ) do
	
		rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( {
			_id = copiedId,
			_serverProvidedMetadata = {
				importedByUser = {
					id = 'foo'
				}
			}
		} ), ':ignore:' )
	
	end
		
	-- Once again, copy ALL of the assets into the shared-with-you album.
	-- Each album member should be able to contribute any image once, 
	-- even if its been contributed by another member. Since all the existing
	-- assets look like they came from a different user, this should succeed.
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, allAssetIds, redactionOptions ), ':ignore:', {}, ':ignore:' )
	
	-- Verify that there are now 6 album assets.

	rxTest:expectSingleValue( 
		wfCatalog:indexStore():queryOnce {
		
			sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
			quiesceUntilUpToDate = true
			
		}
		:map( function( rows )

			return #rows
		
		end ), 6 )
	
	-- Make it look like Oz modified the original and SHA256 because of a metadata
	-- redaction.
	
	rxTest:waitFor(
		wfCatalog:docStore():applyOverlay {
			_id = assetDoc1._id,
			importSource = {
				sha256 = KSIdGenerator.uuidInLowercaseHex(),
				originalSHA256 = expectedData1.originalSha256
			}
		} )
	
	-- Try again to copy asest1 to SWY album. The asset would duplicate because 
	-- the asset with originalSHA256 is a duplicate. We should not recopy the asset
	-- we previously copied.
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDoc1._id }, redactionOptions )
				:map( function( _, duplicateAssetIds, _ )
				
					return duplicateAssetIds
				
				end ), { assetDoc1._id } )
				
	-- Verify that there are still 6 album assets.

	rxTest:expectSingleValue( 
		wfCatalog:indexStore():queryOnce {
		
			sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
			quiesceUntilUpToDate = true
			
		}
		:map( function( rows )

			return #rows
		
		end ), 6 )
	
end

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

local function verifyRedactions( rxTest, wfCatalog, redactionOptions, assetId )

	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetId )
	
		:map( function( docValue )
		
			rxTest:expectEqual( nil, KSTableUtils_derefOpt( docValue, 'copy' ) )
			rxTest:expectEqual( nil, KSTableUtils_derefOpt( docValue, 'importSource', 'lrcatStoreProviderId' ) )
			rxTest:expectEqual( nil, KSTableUtils_derefOpt( docValue, 'importSource', 'lrcatImageId' ) )
			rxTest:expectEqual( nil, KSTableUtils_derefOpt( docValue, 'importSource', 'status' ) )
			rxTest:expectEqual( nil, KSTableUtils_derefOpt( docValue, 'mergedAssets' ) )
		
			local maybeLocation = KSTableUtils_derefOpt( docValue, 'location' )
				
			local maybeCountryCode = KSTableUtils_derefOpt( docValue, 'xmp', 'iptcCore', 'CountryCode' )
			local maybeXmpLocation = KSTableUtils_derefOpt( docValue, 'xmp', 'iptcCore', 'Location' )
			
			local maybeCity = KSTableUtils_derefOpt( docValue, 'xmp', 'photoshop', 'City' )
			local maybeCountry = KSTableUtils_derefOpt( docValue, 'xmp', 'photoshop', 'Country' )
			local maybeState = KSTableUtils_derefOpt( docValue, 'xmp', 'photoshop', 'State' )
		
			if redactionOptions.redactLocation ~= false then
			
				rxTest:expectEqual( nil, maybeLocation )
				
				rxTest:expectEqual( nil, maybeCountryCode )
				rxTest:expectEqual( nil, maybeXmpLocation )
				
				rxTest:expectEqual( nil, maybeCity )
				rxTest:expectEqual( nil, maybeCountry )
				rxTest:expectEqual( nil, maybeState )
				
			else
			
				rxTest:expectNotEqual( nil, maybeLocation )
				
				rxTest:expectNotEqual( nil, maybeCountryCode )
				rxTest:expectNotEqual( nil, maybeXmpLocation )
				
				rxTest:expectNotEqual( nil, maybeCity )
				rxTest:expectNotEqual( nil, maybeCountry )
				rxTest:expectNotEqual( nil, maybeState )
			
			end
			
			local maybeSubject = KSTableUtils_derefOpt( docValue, 'xmp', 'dc', 'subject' )
			
			if redactionOptions.redactKeywords then
				rxTest:expectEqual( nil, maybeSubject )
			else
				rxTest:expectNotEqual( nil, maybeSubject )
			end
			
			local maybeRatings = KSTableUtils_derefOpt( docValue, 'ratings' )
			local maybeReviews = KSTableUtils_derefOpt( docValue, 'reviews' )
			
			if redactionOptions.redactRatingsReviews then
				rxTest:expectEqual( nil, maybeRatings )
				rxTest:expectEqual( nil, maybeReviews )
			else
				rxTest:expectNotEqual( nil, maybeRatings )
				rxTest:expectNotEqual( nil, maybeReviews )
			end

			return true
		
		end ), true )

end

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

local function createUnredactedAsset( rxTest, wfCatalog )

	-- Insert an asset into the doc store.
	
	local assetDoc = buildDocValue( rxTest, wfCatalog )
	               
	-- Make sure to include a bunch of redactable information.
				   
	KSTableUtils_setNested( assetDoc, 'foo', 'copy' )
	KSTableUtils_setNested( assetDoc, 2, 'importSource', 'lrcatStoreProviderId' )
	KSTableUtils_setNested( assetDoc, 3, 'importSource', 'lrcatImageId' )
	KSTableUtils_setNested( assetDoc, 'success', 'importSource', 'status' )
				   
	KSTableUtils_setNested( assetDoc, { gps = "I'm a location" }, 'location' )
	
	KSTableUtils_setNested( assetDoc, { 1, 2 }, 'mergedAssets' )
						
	KSTableUtils_setNested( assetDoc, 'US', 'xmp', 'iptcCore', 'CountryCode' )
	KSTableUtils_setNested( assetDoc, 'Kitchen', 'xmp', 'iptcCore', 'Location' )
	
	KSTableUtils_setNested( assetDoc, 'Seattle', 'xmp', 'photoshop', 'City' )
	KSTableUtils_setNested( assetDoc, 'USA', 'xmp', 'photoshop', 'Country' )
	KSTableUtils_setNested( assetDoc, 'Washington', 'xmp', 'photoshop', 'State' )
						
	KSTableUtils_setNested( assetDoc, { 'keyword1', 'keyword2' }, 'xmp', 'dc', 'subject' )
						
	KSTableUtils_setNested( assetDoc, { [ wfCatalog:ozUserId() ] = { rating = 1 } }, 'ratings' )
	 KSTableUtils_setNested( assetDoc, { [ wfCatalog:ozUserId() ] = { flag = 'picked' } }, 'reviews' )
	 
	-- Insert the asset into the DB
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc ), ':ignore:' )
	
	return assetDoc._id
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_RedactionOptions( 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 spaceDoc = {
		_id = spaceId,
		type = 'space',
		subtype = 'shared',
		download = false,
		private = false,
		location = false,
		metadata = false,
		_serverProvidedMetadata = {
			owner = {
				id = ownerId
			}
		}
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceDoc ), ':ignore:' )

	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Create an unredacted asset:
	
	local assetDocId1 = createUnredactedAsset( rxTest, wfCatalog )
	
	-- Add the asset to the shared-with-you album. Do not redact anything.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}

	local newAssetId
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDocId1 }, redactionOptions )
				:map( function( copiedAssetIds )
					newAssetId = copiedAssetIds[ 1 ]
					return copiedAssetIds
				end ), ':ignore:' )
	
	-- Verify new asset had nothing redacted.
	
	verifyRedactions( rxTest, wfCatalog, redactionOptions, newAssetId )

	-- Create a new asset:
	
	local assetDocId2 = createUnredactedAsset( rxTest, wfCatalog )
	
	-- Add the asset to the shared-with-you album. Redact Location.
	
	redactionOptions = {
		redactLocation = true,
		redactKeywords = false,
		redactRatingsReviews = false
	}

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDocId2 }, redactionOptions )
					:map( function( copiedAssetIds )
						newAssetId = copiedAssetIds[ 1 ]
						return copiedAssetIds
					end ), ':ignore:' )
	
	-- Verify new asset redactions
	
	verifyRedactions( rxTest, wfCatalog, redactionOptions, newAssetId )

	-- Create a new asset:
	
	local assetDocId3 = createUnredactedAsset( rxTest, wfCatalog )
	
	-- Add the asset to the shared-with-you album. Redact Keywords
	
	redactionOptions = {
		redactLocation = false,
		redactKeywords = true,
		redactRatingsReviews = false
	}

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDocId3 }, redactionOptions )
					:map( function( copiedAssetIds )
						newAssetId = copiedAssetIds[ 1 ]
						return copiedAssetIds
					end ), ':ignore:' )
	
	-- Verify new asset redactions
	
	verifyRedactions( rxTest, wfCatalog, redactionOptions, newAssetId )

	-- Create a new asset:
	
	local assetDocId4 = createUnredactedAsset( rxTest, wfCatalog )
	
	-- Add the asset to the shared-with-you album. Redact Ratings/Reviews
	
	redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = true
	}

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDocId4 }, redactionOptions )
					:map( function( copiedAssetIds )
						newAssetId = copiedAssetIds[ 1 ]
						return copiedAssetIds
					end ), ':ignore:' )
	
	-- Verify new asset redactions
	
	verifyRedactions( rxTest, wfCatalog, redactionOptions, newAssetId )
	
	-- Create a new asset:
	
	local assetDocId5 = createUnredactedAsset( rxTest, wfCatalog )
	
	-- Add the asset to the shared-with-you album. Redact Everything
	
	redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}

	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDocId5 }, redactionOptions )
					:map( function( copiedAssetIds )
						newAssetId = copiedAssetIds[ 1 ]
						return copiedAssetIds
					end ), ':ignore:' )
	
	-- Verify new asset redactions
	
	verifyRedactions( rxTest, wfCatalog, redactionOptions, newAssetId )

end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_RatingOwner( 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 spaceDoc = {
		_id = spaceId,
		type = 'space',
		subtype = 'shared',
		download = false,
		private = false,
		location = false,
		metadata = false,
		_serverProvidedMetadata = {
			owner = {
				id = ownerId
			}
		}
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceDoc ), ':ignore:' )

	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Create an unredacted asset:
	
	local assetDocId1 = createUnredactedAsset( rxTest, wfCatalog )
	
	-- Add the asset to the shared-with-you album. Do not redact the ratings.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}

	local newAssetId
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { assetDocId1 }, redactionOptions )
				:map( function( copiedAssetIds )
					newAssetId = copiedAssetIds[ 1 ]
					return copiedAssetIds
				end ), ':ignore:' )
	
	-- Verify the rating is now using the space owner ID
	
	verifyRedactions( rxTest, wfCatalog, redactionOptions, newAssetId )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetId )
			:pluck( 'ratings' )
			:map( function( ratings )
			
				for userId, _ in pairs( ratings ) do
					return userId
				end
			
			end ), ownerId )

end

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

function WFCatSharedWithYouAlbumsTest.rxTestDeleteAssets( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	local expectedRequestResponse = {
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			}
	}
	
	local wfCatalog = createFakeWFCatalog( rxTest, expectedRequestResponse )
	
	-- Insert 1 asset into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the asset to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetId
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { originalAssetId1 }, redactionOptions )
			:flatMap( function( _newAssetIds )
			
				newAssetId = _newAssetIds[ 1 ]
				
				-- Make the copied asset(s) appear synced to Oz.
				
				return wfCatalog:docStore():applyOverlay( {
						_id = newAssetId,
						_serverProvidedMetadata = { catalog_doc = { id = KSIdGenerator.uuidInLowercaseHex() } }
					} )
				
			end ), ':ignore:' )
	
	-- Delete the asset from the shared-with-you album.
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, { newAssetId } ), {} )
	
	-- Verify the asset is deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetId )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestDeleteAssets_RoleCheck( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedRequestResponse = {
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			},
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			}
	}
	
	local wfCatalog = createFakeWFCatalog( rxTest, expectedRequestResponse )
	
	-- 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:' )

	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Insert assets into the doc store.
	
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId2 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId3 = KSIdGenerator.uuidInLowercaseHex()
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, originalAssetId2 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	
	local assetDoc3 = buildDocValue( rxTest, wfCatalog, originalAssetId3 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Add the asset to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					{ originalAssetId1, originalAssetId2, originalAssetId3 }, redactionOptions )
				:map( function( _newAssetIds )
				
					newAssetIds = _newAssetIds
					
					-- Make the copied asset(s) appear synced to Oz.
					
					local overlays = {}
					
					for _, newAssetId in ipairs( newAssetIds ) do
					
						overlays[ #overlays + 1 ] = { 
							_id = newAssetId,
							_serverProvidedMetadata = { catalog_doc = { id = KSIdGenerator.uuidInLowercaseHex() } }
						}
					
					end
					
					return wfCatalog:docStore():bulkApplyOverlay( overlays )
					
				end ), ':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:' )
	
	-- Delete asset1 from the shared-with-you album as "viewer"
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, { newAssetIds[ 1 ] } ),
			{
				newAssetIds[ 1 ]
			} )
			
	-- Delete asset2 from the shared-with-you album as "contributor"
	
	local spaceUserOverlay = {
		_id = spaceUserDoc._id,
		type = 'space_user',
		role = 'contributor'
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, { newAssetIds[ 2 ] } ),
			{} )
			
	-- Delete asset3 from the shared-with-you album as "editor"
	
	spaceUserOverlay.role = 'editor'
	
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( spaceUserOverlay ), ':ignore:' )
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, { newAssetIds[ 3 ] } ),
			{} )
			
	-- Verify one asset is deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 1 ] )
		:map( function( docValue )
		
			return not not docValue._deleted
		
		end ), false )
	
	-- Verify the other assets are not deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 2 ] )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 3 ] )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestDeleteAssets_OnlyDeleteUserAdded( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId2 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId3 = KSIdGenerator.uuidInLowercaseHex()
	
	local expectedRequestResponse = {
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			}
	}
	
	local wfCatalog = createFakeWFCatalog( rxTest, expectedRequestResponse )
	
	-- Insert assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, originalAssetId2 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	
	local assetDoc3 = buildDocValue( rxTest, wfCatalog, originalAssetId3 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the asset to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					{ originalAssetId1, originalAssetId2, originalAssetId3 }, redactionOptions )
				:map( function( _newAssetIds )
				
					newAssetIds = _newAssetIds
					
					-- Make the copied asset(s) appear synced to Oz.
					
					local overlays = {}
					
					for _, newAssetId in ipairs( newAssetIds ) do
					
						overlays[ #overlays + 1 ] = { 
							_id = newAssetId,
							_serverProvidedMetadata = { catalog_doc = { id = KSIdGenerator.uuidInLowercaseHex() } }
						}
					
					end
					
					return wfCatalog:docStore():bulkApplyOverlay( overlays )
					
				end ), ':ignore:' )
				
	-- Make it look like two of the assets were added by another user.
	
	local overlay = {}
	KSTableUtils.setNested( overlay, "UserB", '_serverProvidedMetadata', 'importedByUser', 'id' )
	
	overlay._id = newAssetIds[ 2 ]
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( overlay ), ':ignore:' )
	
	overlay._id = newAssetIds[ 3 ]
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( overlay ), ':ignore:' )
				
	-- Delete the asset from the shared-with-you album. Assets assetId2 and assetId3
	-- should not be deleted because they were imported by another user.
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, newAssetIds ),
			{
				newAssetIds[ 2 ],
				newAssetIds[ 3 ]
			} )
	
	-- Verify one asset is deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 1 ] )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
	-- Verify the other assets are not deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 2 ] )
		:map( function( docValue )
		
			return not not docValue._deleted
		
		end ), false )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 3 ] )
		:map( function( docValue )
		
			return not not docValue._deleted
		
		end ), false )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestDeleteAssets_DeletionWindow( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId2 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId3 = KSIdGenerator.uuidInLowercaseHex()
	
	local expectedRequestResponse = {
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			},
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			}
	}
	
	local wfCatalog = createFakeWFCatalog( rxTest, expectedRequestResponse )
	
	-- Insert assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, originalAssetId2 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	
	local assetDoc3 = buildDocValue( rxTest, wfCatalog, originalAssetId3 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the asset to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					{ originalAssetId1, originalAssetId2, originalAssetId3 }, redactionOptions )
				:map( function( _newAssetIds )
				
					newAssetIds = _newAssetIds
					
					-- Make the copied asset(s) appear synced to Oz.
					
					local overlays = {}
					
					for _, newAssetId in ipairs( newAssetIds ) do
					
						overlays[ #overlays + 1 ] = { 
							_id = newAssetId,
							_serverProvidedMetadata = { catalog_doc = { id = KSIdGenerator.uuidInLowercaseHex() } }
						}
					
					end
					
					return wfCatalog:docStore():bulkApplyOverlay( overlays )
					
				end ), ':ignore:' )
				
	-- Make it look like two of the assets were added by another user.
	
	local timeInSeconds = KSDate.timeNow()
	
	local currentTimeStamp = KSDate.timeInIso8601Format( timeInSeconds )
	
	local overlay = {}
	
	overlay._id = newAssetIds[ 1 ]
	KSTableUtils.setNested( overlay, currentTimeStamp, '_serverProvidedMetadata', 'created' )
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( overlay ), ':ignore:' )
	
	local offset = 47 * 60 * 60
	local _47HoursAgoTimestamp = KSDate.timeInIso8601Format( timeInSeconds - offset )
	
	overlay._id = newAssetIds[ 2 ]
	KSTableUtils.setNested( overlay, _47HoursAgoTimestamp, '_serverProvidedMetadata', 'created' )
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( overlay ), ':ignore:' )
	
	offset = 49 * 60 * 60
	local _49HoursAgoTimestamp = KSDate.timeInIso8601Format( timeInSeconds - offset )
	
	overlay._id = newAssetIds[ 3 ]
	KSTableUtils.setNested( overlay, _49HoursAgoTimestamp, '_serverProvidedMetadata', 'created' )
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( overlay ), ':ignore:' )

	-- Delete the asset from the shared-with-you album. AssetId3
	-- should not be deleted because it was added more than 48 hours ago.
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, newAssetIds ),
			{
				newAssetIds[ 3 ]
			} )
	
	-- Verify assets are deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 1 ] )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 2 ] )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
	-- Verify the other asset is not deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 3 ] )
		:map( function( docValue )
		
			return not not docValue._deleted
		
		end ), false )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestDeleteAssets_OnlyDeleteAssetsInSpace( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId2 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId3 = KSIdGenerator.uuidInLowercaseHex()
	
	local expectedRequestResponse = {
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId1,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = "none",
				headers = {
					status_code = '204',
				},
			}
	}
	
	local wfCatalog = createFakeWFCatalog( rxTest, expectedRequestResponse )
	
	-- Insert assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, originalAssetId2 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	
	local assetDoc3 = buildDocValue( rxTest, wfCatalog, originalAssetId3 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add only the first asset to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					{ originalAssetId1 }, redactionOptions )
				:map( function( _newAssetIds )
				
					newAssetIds = _newAssetIds
					
					-- Make the copied asset(s) appear synced to Oz.
					
					local overlays = {}
					
					for _, newAssetId in ipairs( newAssetIds ) do
					
						overlays[ #overlays + 1 ] = { 
							_id = newAssetId,
							_serverProvidedMetadata = { catalog_doc = { id = KSIdGenerator.uuidInLowercaseHex() } }
						}
					
					end
					
					return wfCatalog:docStore():bulkApplyOverlay( overlays )
					
				end ), ':ignore:' )
	
	-- Delete the asset from the shared-with-you album. Assets assetId2 and assetId3
	-- should not be deleted because they are not part of the space.
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, 
				{ 
					newAssetIds[ 1 ],
					originalAssetId2,
					originalAssetId3
				} ),
			{
				originalAssetId2,
				originalAssetId3
			} )
	
	-- Verify one asset is deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetIds[ 1 ] )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
	-- Verify the other assets are not deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( originalAssetId2 )
		:map( function( docValue )
		
			return not not docValue._deleted
		
		end ), false )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( originalAssetId3 )
		:map( function( docValue )
		
			return not not docValue._deleted
		
		end ), false )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestDeleteNotSyncedAssets( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	local expectedRequestResponse = {
			{
				url = "url", --"/v2/spaces/" .. spaceId .."/assets/" .. assetId1,
				data = "nil",
				options = {
					bypassRateLimiter = true,
					requestHeaders = {
						{
							header = "Content-Type",
							value = "application/json",
						},
						{
							header = "Accept",
							value = "application/json",
						},
					},
					requestMethod = "DELETE",
					returnHttpErrorsAsData = true,
				},
				urlFilter = function( url ) return "url" end,
				response = [[{
						"subtype": "ResourceNotFoundError"
					}]],
				headers = {
					status_code = '404',
					[ 'X-Result-Code' ] = '1000'
				},
			}
	}
	
	local wfCatalog = createFakeWFCatalog( rxTest, expectedRequestResponse )
	
	-- Insert 1 asset into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
		
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the asset to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetId
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, { originalAssetId1 }, redactionOptions )
			:map( function( _newAssetIds )
				newAssetId = _newAssetIds[ 1 ]
				return _newAssetIds
			end ), ':ignore:' )
	
	-- Delete the asset from the shared-with-you album. At this point, it is not synced.
	
	rxTest:expectSingleValue(
			WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, { newAssetId } ), {} )
	
	-- Verify the asset is deleted.
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( newAssetId )
		:map( function( docValue )
		
			return docValue._deleted
		
		end ), true )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_AlreadyInSpace( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert 3 assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	local assetDoc3 = buildDocValue( rxTest, wfCatalog )
	
	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetDoc3._id )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	
	-- Add assets 1 and 2 into the space.
	
	assetDoc1._spaceId = spaceId
	assetDoc2._spaceId = spaceId
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, assetIds, redactionOptions ), ':ignore:', {}, ':ignore:' )
	
	-- Verify that there are now 3 album assets for:
	-- assetDoc1, assetDoc2, and a copy of assetDoc3 but with a different ID.
	
	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end

	local query = wfCatalog:indexStore():queryOnce {
		
		sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
		quiesceUntilUpToDate = true
		
	}

	rxTest:expectSingleValue( 
		query:map( function( rows )
		
			local foundAssetDoc1 = false
			local foundAssetDoc2 = false
			local foundAssetDoc3 = false
		
			for _, row in ipairs( rows ) do
				
				local copiedAssetId = row[ 1 ]
				local copiedAlbumId = row[ 2 ]
				
				rxTest:expectEqual( copiedAlbumId, albumId )
				
				if not foundAssetDoc1 then
					foundAssetDoc1 = copiedAssetId == assetDoc1._id
				end
				
				if not foundAssetDoc2 then
					foundAssetDoc2 = copiedAssetId == assetDoc2._id
				end
				
				if not foundAssetDoc3 then
					foundAssetDoc3 = copiedAssetId == assetDoc3._id
				end
				
			end
		
			return #rows, foundAssetDoc1, foundAssetDoc2, foundAssetDoc3
		
		end ), 3, true, true, false )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_AlreadyInAlbum( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert 3 assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	local assetDoc3 = buildDocValue( rxTest, wfCatalog )
	
	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetDoc3._id )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	
	-- Add assets 1 and 2 into the space.
	
	assetDoc1._spaceId = spaceId
	assetDoc2._spaceId = spaceId
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add assets 1 and 2 into the album.
	
	local albumAsset1 = WFCatAlbums.createAlbumAssetDocument( albumId, assetDoc1._id, spaceId )
	local albumAsset2 = WFCatAlbums.createAlbumAssetDocument( albumId, assetDoc2._id, spaceId )
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumAsset1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumAsset2 ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, assetIds, redactionOptions ), ':ignore:', { assetDoc1._id, assetDoc2._id }, ':ignore:' )
	
	-- Verify that there are now 3 album assets for:
	-- assetDoc1, assetDoc2, and a copy of assetDoc3 but with a different ID.
	
	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end

	local query = wfCatalog:indexStore():queryOnce {
		
		sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
		quiesceUntilUpToDate = true
		
	}

	rxTest:expectSingleValue( 
		query:map( function( rows )
		
			local foundAssetDoc1 = false
			local foundAssetDoc2 = false
			local foundAssetDoc3 = false
		
			for _, row in ipairs( rows ) do
				
				local copiedAssetId = row[ 1 ]
				local copiedAlbumId = row[ 2 ]
				
				rxTest:expectEqual( copiedAlbumId, albumId )
				
				if not foundAssetDoc1 then
					foundAssetDoc1 = copiedAssetId == assetDoc1._id
				end
				
				if not foundAssetDoc2 then
					foundAssetDoc2 = copiedAssetId == assetDoc2._id
				end
				
				if not foundAssetDoc3 then
					foundAssetDoc3 = copiedAssetId == assetDoc3._id
				end
				
			end
		
			return #rows, foundAssetDoc1, foundAssetDoc2, foundAssetDoc3
		
		end ), 3, true, true, false )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_IgnoreStacks( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert 3 assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog )
	local assetStackDoc3 = buildDocValue( rxTest, wfCatalog )
	assetStackDoc3.subtype = 'stack'
	
	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetStackDoc3._id )
	
	rxTest:expectSingleValue( wfCatalog:docStore():bulkSetDocValue( 
			{ assetDoc1, assetDoc2, assetStackDoc3 } ), ':ignore:' )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					assetIds, redactionOptions )
				:map( function( _newAssetIds )
					newAssetIds = _newAssetIds
					return _newAssetIds
				end ), ':ignore:' )
	
	-- Verify that the stack asset wasn't copied.
	
	rxTest:expectEqual( 2, #newAssetIds )
	
	for _, id in ipairs( newAssetIds ) do
	
		rxTest:expectSingleValue( wfCatalog:docStore():docValue( id )
		
			:pluck( 'subtype' ), 'image' )
	
	end
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbum_willNotUploadMaster( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert 3 assets into the doc store
	-- assetDoc1: willNotUploadMaster = true, no master link, or local master
	-- assetDoc2: willNotUploadMaster = true, master link, no local master
	-- assetDoc3: willNotUploadMaster = true, no master link, local master
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, nil, { 
			noLocalOriginal = true,
			noMasterLink = true,
			noLocalProxy = true } )
			
	KSTableUtils.setNested( assetDoc1, true, 'importSource', 'willNotUploadMaster' )
	
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, nil, { noLocalOriginal = true }  )	
	KSTableUtils.setNested( assetDoc1, true, 'importSource', 'willNotUploadMaster' )

	local assetDoc3 = buildDocValue( rxTest, wfCatalog, nil, { noMasterLink = true }  )
	KSTableUtils.setNested( assetDoc1, true, 'importSource', 'willNotUploadMaster' )
	
	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetDoc3._id )
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	local assetDoc1FromDocStore, assetDoc2FromDocStore, assetDoc3FromDocStore
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc1._id )
		:map( function( docValue )
			assetDoc1FromDocStore = docValue
			return true
		end ), ':ignore:' )
	
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc2._id )
		:map( function( docValue )
			assetDoc2FromDocStore = docValue
			return true
		end ), ':ignore:' )
		
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( assetDoc3._id )
		:map( function( docValue )
			assetDoc3FromDocStore = docValue
			return true
		end ), ':ignore:' )

	-- Insert a shared-with-you album into the doc store.
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	local contributedAssetIds
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, assetIds, {} )
			:map( function( newAssetIds )
				contributedAssetIds = newAssetIds
				return newAssetIds
			end ), ':ignore:' )
	
	local contributedDoc1FromDocStore, contributedDoc2FromDocStore, contributedDoc3FromDocStore
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( contributedAssetIds[ 1 ] )
		:map( function( dv ) contributedDoc1FromDocStore = dv end ) )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( contributedAssetIds[ 2 ] )
		:map( function( dv ) contributedDoc2FromDocStore = dv end ) )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( contributedAssetIds[ 3 ] )
		:map( function( dv ) contributedDoc3FromDocStore = dv end ) )
	
	assert( KSTableUtils.derefOpt( contributedDoc1FromDocStore, 'importSource', 'willNotUploadMaster' ), "contributed doc 1 should be willNotUploadMaster = true" )
	assert( KSTableUtils.derefOpt( contributedDoc2FromDocStore, 'importSource', 'willNotUploadMaster' ) == nil, "contributed doc 2 should be willNotUploadMaster nil" )
	assert( KSTableUtils.derefOpt( contributedDoc3FromDocStore, 'importSource', 'willNotUploadMaster' ) == nil, "contributed doc 3 should be willNotUploadMaster nil" )

end

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

function WFCatSharedWithYouAlbumsTest.rxTestAddAssetsToAlbumBulk_testNightly( rxTest )

	rxTest:setTimeLimit( 60 )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	local assetIds = {}
	local assetDocs = {}
	
	for i=250, 1, -1 do
	
		local assetDoc1 = buildDocValue( rxTest, wfCatalog )

		table.insert( assetIds, assetDoc1._id )
		table.insert( assetDocs, assetDoc1 )
	
	end
	
	rxTest:expectSingleValue( wfCatalog:docStore():bulkSetDocValue( assetDocs ), ':ignore:' )
	
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = true,
		redactKeywords = true,
		redactRatingsReviews = true
	}
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, assetIds, redactionOptions ), ':ignore:', {}, {} )
	
	-- Verify that there are now 3 album assets and that the corresponding
	-- asset IDs don't match the asset id of our original assets.
	
	-- Note that we don't validate the copied assets here. That's taken
	-- care of by the WFCatCopies unit tests.
	
	local albumAssetTable = "album_asset"

	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
	end

	local query = wfCatalog:indexStore():query {
		
		sql = [[SELECT assetId, albumId FROM ]] .. albumAssetTable,
		quiesceUntilUpToDate = true,
		calm = 1,
	}

	local observer = rxTest:observe( 
		query:map( function( rows )
		
			for _, row in ipairs( rows ) do
				
				local copiedId = row[ 1 ]
				local copiedAlbumId = row[ 2 ]
				
				rxTest:expectEqual( copiedAlbumId, albumId )
				
				for _, assetId in ipairs( assetIds ) do
				
					rxTest:expectNotEqual( copiedId, assetId )
				
				end
				
			end
		
			return #rows
		
		end ) )
		
	observer:expectDataWithinTime( 20, #assetIds )
	
end

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

function WFCatSharedWithYouAlbumsTest.rxTestLeaveSpace( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceUserId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId2 = KSIdGenerator.uuidInLowercaseHex()
	local originalAssetId3 = KSIdGenerator.uuidInLowercaseHex()
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert a space doc into the doc store.
	
	local spaceDoc = {
		_id = spaceId,
		type = 'space',
		subtype = 'shared',
		download = false,
		private = false,
		location = false,
		metadata = false,
		_serverProvidedMetadata = {
			this_space_user = {
				id = spaceUserId
			}
		}
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceDoc ), ':ignore:' )
	
	-- Insert a space_user doc into the doc store.
	
	local spaceUserDoc = {
		_id = spaceUserId,
		type = 'space_user',
		space = {
			id = spaceId,
		},
		user = {
			id = wfCatalog:ozUserId(),
		},
		status = "accepted",
		role = "viewer",
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceUserDoc ), ':ignore:' )
	
	-- Insert assets into the doc store.
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, originalAssetId2 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc2 ), ':ignore:' )
	
	local assetDoc3 = buildDocValue( rxTest, wfCatalog, originalAssetId3 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc3 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					{ originalAssetId1, originalAssetId2, originalAssetId3 }, redactionOptions )
				:map( function( _newAssetIds )
					newAssetIds = _newAssetIds
					return _newAssetIds
				end ), ':ignore:' )
	
	-- Leave the space.
	
	rxTest:expectSingleValue( WFCatSharedWithYouAlbums.leaveSpace( wfCatalog, spaceId ), true )
	
	-- Verify the space is now abadoned. Deletion of the other documents is handled
	-- within the space feeds manager.
		
	rxTest:expectSingleValue(

		wfCatalog:docStore():docValue( spaceUserId )
			:map( function( docValue )
				docValue._changeCounter = nil
				docValue._revId = nil
				docValue.userUpdated = nil
				return docValue
			end ),
		{
			_id = spaceUserId,
			_localId = 2,
			role = "viewer",
			space = {
				id = spaceId,
			},
			status = "abandoned",
			type = "space_user",
			user = {
				id = wfCatalog:ozUserId(),
			},
		} )
	
end


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

function WFCatSharedWithYouAlbumsTest.rxTestReportAbuse( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local spaceUserId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	
	local originalAssetId1 = KSIdGenerator.uuidInLowercaseHex()

	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Insert a space doc into the doc store.
	
	local spaceDoc = {
		_id = spaceId,
		type = 'space',
		subtype = 'shared',
		download = false,
		private = false,
		location = false,
		metadata = false,
		_serverProvidedMetadata = {
			this_space_user = {
				id = spaceUserId
			}
		}
	}
	
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( spaceDoc ), ':ignore:' )

	-- Insert assets into the doc store.
	
	local reportAbuseAssetLink = 'https://ReportAbuseAsset.com'
	
	local assetDoc1 = buildDocValue( rxTest, wfCatalog, originalAssetId1 )
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( assetDoc1 ), ':ignore:' )
	
	-- Insert a shared-with-you album into the doc store.
	
	local reportAbuseAlbumLink = 'https://ReportAbuseAlbum.com'
	
	local albumDoc = {

		_id = albumId,
		_spaceId = spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'foo',

	}
	
	KSTableUtils.setNested( albumDoc, reportAbuseAlbumLink, '_serverProvidedMetadata', 'links', '/rels/web_abuse_report', 'href' )
		
	rxTest:expectSingleValue( wfCatalog:docStore():setDocValue( albumDoc ), ':ignore:' )
	
	-- Add the assets to the shared-with-you album.
	
	local redactionOptions = {
		redactLocation = false,
		redactKeywords = false,
		redactRatingsReviews = false
	}
	
	local newAssetIds
	
	rxTest:expectSingleValue( 
			WFCatSharedWithYouAlbums.addAssetsToAlbum( 
					wfCatalog, spaceId, albumId, 
					{ originalAssetId1 }, redactionOptions )
				:map( function( _newAssetIds )
					newAssetIds = _newAssetIds
					return _newAssetIds
				end ), ':ignore:' )
	
	-- Add report abuse link to the asset doc..
	
	local overlay = {
		_id = newAssetIds[ 1 ],
	}
	
	KSTableUtils.setNested( overlay, reportAbuseAssetLink, '_serverProvidedMetadata', 'links', '/rels/web_abuse_report', 'href' )
		
	rxTest:expectSingleValue( wfCatalog:docStore():applyOverlay( overlay ), ':ignore:' )
	
	-- Fetch the report abuse link for the album:
	
	rxTest:expectSingleValue( WFCatSharedWithYouAlbums.reportAbuse( wfCatalog, albumId ),
		reportAbuseAlbumLink )
	
	-- Fetch the report abuse link for the asset:
	
	rxTest:expectSingleValue( WFCatSharedWithYouAlbums.reportAbuse( wfCatalog, newAssetIds[ 1 ] ),
		reportAbuseAssetLink )
	
end

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

return WFRxTestCase.wrapSuite( WFCatSharedWithYouAlbumsTest )
