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

WFCatCollaborationTest.lua

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

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

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

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

local Rx = require 'Rx'
local Observable = Rx.Observable

local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFArguments_uuid = require 'WFArguments_uuid'
local WFCatAlbums = require 'WFCatAlbums'
local WFCatCollaboration = require 'WFCatCollaboration'
local WFCatMetadataPuller = require 'WFCatMetadataPuller'
local WFCatMetadataPusher = require 'WFCatMetadataPusher'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFRxTestCase = require 'WFRxTestCase'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatTestSession = require 'WFCatTestSession'
local WFPlatformInfo = require 'WFPlatformInfo'

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

local cachedSpaceId = "bogus"

local kTestDate1 = "2011-11-11T11:11:11.111111Z"
local kTestDate2 = "2012-12-12T12:12:12.121212Z"
local kConstDate = 'kConstDate'

local kDefaultShareIsPrivate = true
local kMaxmiumDate = '9999-12-31T23:59:59Z'

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

local WFCatCollaborationTest = {}

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

local function createFakeWFCatalog( rxTest, expectedRequestResponse )

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

	return WFCatMockCatalog.createFakeWFCatalog( rxTest, nil, expectedRequestResponse, optArgs )
	
end

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

local function expectedRequestResponse( albumId )
	
	return {
		{
			url = "url", -- "/v2/catalogs/<albumId>/spaces/<spaceId>"
			data = "data",
			options = {
				requestHeaders = {
					{
						header = "Content-Type",
						value = "application/json",
					},
					{
						header = "If-None-Match",
						value = albumId
					}
				},
				requestMethod = "PUT",
			},
			dataFilter = function( data ) return "data" end,
			urlFilter = function( url ) return "url" end,
			response = "none",
			headers = {
				status_code = '201',
			},
		},
		{
			url = "url", -- "/v2/catalogs/<albumId>/spaces/<spaceId>"
			data = "data",
			options = {},
			dataFilter = function( data ) return "data" end,
			urlFilter = function( url ) 
				cachedSpaceId = string.match( url, "spaces/(.+)" )
				return "url" 
			end,
			response = [[while (1) {} {"base":"https://photos-stage.adobe.io/v2/catalogs/0c20896ba35f42c9a3fb478fb82ad09a/","id":"]]..'<spaceId>'..[[",
				"type":"space","subtype":"shared","created":"2018-04-23T17:29:29.524635Z",
				"updated":"2018-04-23T17:29:30.126268Z","country_blocked":false,"createdOnClient":"AdobeNimbus-1.4.developer-OSX",
				"revision_ids":["d7c26c4d7f14139e6a1767f25bbdf3b5","0a236fccbf4ec0eed40fd2ef4e52580f"],"links":{"self":{"href":"spaces/<spaceId>"},
				"/rels/web_space":{"href":"http://bit.ly/2K7ltDy"},"/rels/space":{"href":"/v2/spaces/<spaceId>"},
				"/rels/space_resources":{"href":"/v2/spaces/<spaceId>/resources"}},
				"payload":{"private":]] .. tostring( kDefaultShareIsPrivate ) .. [[,"download":false,"metadata":false,"location":false,"allowInviteRequests":true}}]],
				headers = {
				status_code = '200',
			},

			responseFilter = function( response )
				response = response:gsub( '<spaceId>', cachedSpaceId )
				return response
			end,
			--fail = "Network connection lost",
		}
	}
	
end

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

local function createAlbum( rxTest, catalog, albumId )
	
	rxTest:expectSingleValue(

			WFCatAlbums.createAlbum( catalog, 'WFCatCollaboration', nil, nil, nil, albumId )
				:map( function( localAlbumId, globalAlbumId )
					if albumId then
						return albumId == globalAlbumId
					else
						albumId = globalAlbumId
						return true
					end
				end )
				:takeLast( 1 )
			, true
		)
		
	return albumId
	
end

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

--local function shareAlbum( rxTest, catalog, albumId )
--
--	-- share it
--	local spaceId
--	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result) spaceId = result.id end ), ':ignore:' )
--
--	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
--
--	-- check the get shares API to see if it's shared
--	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = false, spaceId = spaceId } } )
--
--	return sharedAlbums, spaceId
--end

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

function WFCatCollaborationTest.rxTestShareAlbum( rxTest )
	
	rxTest:setTimeLimit( 5 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- create album
	createAlbum( rxTest, catalog, albumId )

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	
	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestUnshareAlbum( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- create album
	createAlbum( rxTest, catalog, albumId )

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	
	-- unshare it
	WFCatCollaboration.unshareAlbum( catalog, albumId ):call( WFRxRunUntilDone )
	
	-- check the shares table
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = true, spaceId = spaceId, allowInviteRequests = false, hide_url = false } } )
	
	WFCatCollaboration.shareAlbum( catalog, albumId ):call( WFRxRunUntilDone )
	
	-- check the shares table
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	
	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestDeleteAlbumShare( rxTest )
	
	rxTest:setTimeLimit( 5 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- create album
	createAlbum( rxTest, catalog, albumId )
	
	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false, } } )
	
	-- delete the album share
	rxTest:expectOnlyCompleted( WFCatCollaboration.deleteSharedAlbum( catalog, albumId ) )

	-- there should be no shares
	sharedAlbums:expectDataWithinTime( 3, {} ) 

	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestDeleteAlbum( rxTest )

	-- when an album is deleted, it's share should be fully cleaned up too
	rxTest:setTimeLimit( 5 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	local autoDeleter = require 'WFCatSyncAutoDeleter'( catalog, { delay = 0.1 } )
	assert( autoDeleter, "failed to instantiate autoDeleter" )
	
	-- create album
	createAlbum( rxTest, catalog, albumId )
	
	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	
	-- delete the album
	rxTest:expectOnlyCompleted( catalog:docStore():deleteDoc( albumId ):takeLast( 0 ) )
	
	local albumTable = rxTest:observe( catalog:indexStore():query { sql = [[ SELECT * FROM albums ]] } )
	
	albumTable:expectDataWithinTime( 3, { } )
	
	albumTable:dispose()
	
	-- check for shares
	sharedAlbums:expectDataWithinTime( 3, { } )
	
	sharedAlbums:dispose()
	
	-- since the high level shares API is unlikely to return any shares once the album is gone, make sure the
	-- space info really is cleared from the spaces tables
	local spaceTable = rxTest:observe( catalog:indexStore():query { sql = [[ SELECT * FROM space WHERE isDeleted == 0]] } )
	
	spaceTable:expectDataWithinTime( 3, { } )
	
	spaceTable:dispose()
	
	local spaceAlbumTable = rxTest:observe( catalog:indexStore():query { sql = [[ SELECT * FROM space_album ]] } )
	
	spaceAlbumTable:expectDataWithinTime( 3, { } )
	
	spaceAlbumTable:dispose()
	
end

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

function WFCatCollaborationTest.rxTestSharedAlbumUrl( rxTest )

	rxTest:setTimeLimit( 5 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- create album
	createAlbum( rxTest, catalog, albumId )
	
	local sharedAlbumUrl = rxTest:observe( WFCatCollaboration.sharedAlbumUrl( catalog, albumId ) )
	
	-- shouldn't have a share url 
	sharedAlbumUrl:expectDataWithinTime( 3, nil ) 
	
	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	
	-- fake add a url for the space
	rxTest:expectSingleValue( catalog:docStore():applyOverlay( 
			{ 
				_id = spaceId, 
				_serverProvidedMetadata = {
					links = {
						['/rels/web_space'] = {
							href = 'someurl'
						}
					}
				}
			} ), ':ignore:' )
	
	sharedAlbumUrl:expectDataWithinTime( 3, 'someurl' )
		
	sharedAlbumUrl:dispose()
	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestSharedAlbumAttributes( rxTest )

	rxTest:setTimeLimit( 5 )

	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )

	-- create album
	createAlbum( rxTest, catalog, albumId )

	local attributes = {

		download = true,
		metadata = true,
		location = true,
		pickedOnly = true,
		ratingAtLeast = 2,	
		hide_url = true,
		allow_anyone_can_upload = false,
		
		author = "Edvard Munch",
		title = "The Scream",
		description = "Anyone can look at The Scream and feel something",
		showTitle = true,
		showAuthor = true,
		showCaptions = true,
		showCover = true,
		themeId = "Single",
		styleId = "Light",
	}

	local setSharedAlbumAttributes = rxTest:observe( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, attributes ) )
	local getSharedAlbumAttributes = rxTest:observe( WFCatCollaboration.getSharedAlbumAttributes( catalog, albumId ) )

	-- should fail if there is no share on the album
	setSharedAlbumAttributes:expectError( "album is not shared" )

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )

	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )

	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )

	-- test the working api
	local expected = {
		comments_disabled = false,
		download = true,
		favorites_disabled = false,
		metadata = true,
		location = true,
		private = kDefaultShareIsPrivate,
		presentationFilters = true,
		id = spaceId,
		url = "http://bit.ly/2K7ltDy",
		pickedOnly = true,
		ratingAtLeast = 2,
		allowInviteRequests = true,
		hide_url = true,
		allow_anyone_can_upload = false,
		
		author = "Edvard Munch",
		title = "The Scream",
		description = "Anyone can look at The Scream and feel something",
		showTitle = true,
		showAuthor = true,
		showCaptions = true,
		showCover = true,
		themeId = "Single",
		styleId = "Light",
		
		invitationSharingDisallowed = false,
		invitationSharingAllowDomains = false,
		publicSharingDisallowed = true,
		requestAccessSharingDisallowed = false,
		anyoneCanUploadDisallowed = false,
		
		feedbackDisallowed = false,
		communityPostingDisallowed = false
	}

	WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, attributes ):call( WFRxRunUntilDone )
	getSharedAlbumAttributes:expectDataWithinTime( 3,  expected  )
	
	-- Make small change to ensure that other untouched changes are not reset to their default values
	expected.download = false
	WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, { download = false } ):call( WFRxRunUntilDone )
	getSharedAlbumAttributes:expectDataWithinTime( 3,  expected  )


	-- attributes also returns a url if one exists, so test that too
	local url = 'someurl'
	rxTest:waitFor( catalog:docStore():applyOverlay(
			{
				_id = spaceId,
				_serverProvidedMetadata = {
					links = {
						['/rels/web_space'] = {
							href = url
						}
					}
				}
			} ), ':ignore:' )

	expected.url = url  -- add to the data we expect
	getSharedAlbumAttributes:expectDataWithinTime( 3,  expected  )
	
	setSharedAlbumAttributes:dispose()
	getSharedAlbumAttributes:dispose()
	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestSharedAlbumAttributes_allow_anyone_can_upload( rxTest )

	rxTest:setTimeLimit( 5 )

	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )

	-- create album
	createAlbum( rxTest, catalog, albumId )

	local attributes = {

		download = true,
		metadata = true,
		location = true,
		pickedOnly = true,
		ratingAtLeast = 2,	
		hide_url = true,
		allow_anyone_can_upload = true,
		
		author = "Edvard Munch",
		title = "The Scream",
		description = "Anyone can look at The Scream and feel something",
		showTitle = true,
		showAuthor = true,
		showCaptions = true,
		showCover = true,
		themeId = "Single",
		styleId = "Light",
	}

	local setSharedAlbumAttributes = rxTest:observe( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, attributes ) )
	local getSharedAlbumAttributes = rxTest:observe( WFCatCollaboration.getSharedAlbumAttributes( catalog, albumId ) )

	-- should fail if there is no share on the album
	setSharedAlbumAttributes:expectError( "album is not shared" )

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )

	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )

	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )

	-- test the working api
	local expected = {
		comments_disabled = false,
		download = true,
		favorites_disabled = false,
		metadata = true,
		location = true,
		private = kDefaultShareIsPrivate,
		presentationFilters = true,
		id = spaceId,
		url = "http://bit.ly/2K7ltDy",
		pickedOnly = true,
		ratingAtLeast = 2,
		allowInviteRequests = true,
		hide_url = true,
		open_upload_until = kMaxmiumDate,
		allow_anyone_can_upload = true,
		
		author = "Edvard Munch",
		title = "The Scream",
		description = "Anyone can look at The Scream and feel something",
		showTitle = true,
		showAuthor = true,
		showCaptions = true,
		showCover = true,
		themeId = "Single",
		styleId = "Light",
		
		invitationSharingDisallowed = false,
		invitationSharingAllowDomains = false,
		publicSharingDisallowed = true,
		requestAccessSharingDisallowed = false,
		anyoneCanUploadDisallowed = false,
		
		feedbackDisallowed = false,
		communityPostingDisallowed = false
	}

	WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, attributes ):call( WFRxRunUntilDone )
	getSharedAlbumAttributes:expectDataWithinTime( 3,  expected  )
	
	-- Make small change to ensure that other untouched changes are not reset to their default values
	expected.download = false
	WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, { download = false } ):call( WFRxRunUntilDone )
	getSharedAlbumAttributes:expectDataWithinTime( 3,  expected  )

	-- Set allow_anyone_can_upload to false and confirm that removes open_upload_until
	expected.open_upload_until = nil
	expected.allow_anyone_can_upload = false
	WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, { allow_anyone_can_upload = false } ):call( WFRxRunUntilDone )
	getSharedAlbumAttributes:expectDataWithinTime( 3, expected )

	-- attributes also returns a url if one exists, so test that too
	local url = 'someurl'
	rxTest:expectSingleValue( catalog:docStore():applyOverlay(
			{
				_id = spaceId,
				_serverProvidedMetadata = {
					links = {
						['/rels/web_space'] = {
							href = url
						}
					}
				}
			} ), ':ignore:' )

	expected.url = url  -- add to the data we expect
	getSharedAlbumAttributes:expectDataWithinTime( 3,  expected  )
	
	setSharedAlbumAttributes:dispose()
	getSharedAlbumAttributes:dispose()
	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestCanInviteEmailAddress( rxTest )

	rxTest:setTimeLimit( 5 )
	
	local sharingPolicies = {
		invitationSharingDisallowed = false,
		invitationSharingAllowDomains = false,
		publicSharingDisallowed = false,
		requestAccessSharingDisallowed = false,
		anyoneCanUploadDisallowed = false,
		feedbackDisallowed = false,
		communityPostingDisallowed = false
	}
	
	rxTest:expectEqual( true, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@adobetest.com' ) )
	
	-- invitation sharing disallowed
	sharingPolicies.invitationSharingDisallowed = true
	
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@adobetest.com' ) )
	
	-- allowDomains with empty domain list
	sharingPolicies.invitationSharingDisallowed = false
	sharingPolicies.invitationSharingAllowDomains = true
	sharingPolicies.allowDomains = {}
	
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@adobetest.com' ) )
	
	-- allowDomains with domain list
	sharingPolicies.allowDomains = {
		'adobetest.com',
    	'gmail.com'
	}
	
	rxTest:expectEqual( true, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@adobetest.com' ) )
	rxTest:expectEqual( true, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@gmail.com' ) )
	rxTest:expectEqual( true, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'ABC@ADOBETEST.COM' ) )
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@adobe.com' ) )
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@adobetest' ) )
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@' ) )
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc' ) )
	
	rxTest:expectEqual( true, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@foo.bar.adobetest.com' ) )
	rxTest:expectEqual( false, WFCatCollaboration.DEBUG_canInviteEmailAddress( sharingPolicies, 'abc@foo.bar.google.com' ) )
	
end

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

function WFCatCollaborationTest.rxTestInviteByEmail( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	local email = "tester.lightroom@gmail.com"
	
	-- create album
	createAlbum( rxTest, catalog, albumId )

	-- Try to invite when space doesn't exist. Should not corrupt
 	rxTest:waitFor( WFCatCollaboration.inviteByEmail( catalog, albumId, email ) )
	
	local observeInvite = rxTest:observe( catalog:indexStore():query {
		sql = "SELECT spaceId, subtype, email, status, role FROM space_invite"
	} )
	
	observeInvite:expectDataWithinTime( 5, {} )

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	
	-- invite again
	rxTest:waitFor( WFCatCollaboration.inviteByEmail( catalog, albumId, email ) )
	
	observeInvite:expectDataWithinTime( 5, {{ spaceId, 'single_use', email, 'open', 'viewer' }} )
	
end

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

local function createFeedbackUser( i )
	return {
		_id = KSIdGenerator.uuidInLowercaseHex(),
		type = 'feedbackuser',
		
		_serverProvidedMetadata = {
			full_name = string.format( 'Full Name %s', tostring( i ) ),
			email = string.format( 'person_%s@gmail.com', tostring( i ) ),
		},
	}
end

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

local function createRequestDocument( spaceId, feedbackuserId, approvedUserId )
	return {
		_id = KSIdGenerator.uuidInLowercaseHex(),
		type = 'spaceinvite',
		subtype = 'request',
		
		userCreated = kTestDate1,
		userUpdated = kTestDate2,
		space = {
			id = spaceId,
		},
		user = {
			id = feedbackuserId,
		},
		space_user = approvedUserId and {
			id = approvedUserId,
		},
			
		status = approvedUserId and "approved" or "open",
		role = "viewer",
	}
end

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

local function createSpaceUserDocument( spaceId, feedbackuserId, optRole )

	return {
		_id = KSIdGenerator.uuidInLowercaseHex(),
		type = 'space_user',

		space = {
			id = spaceId,
		},
		user = {
			id = feedbackuserId,
		},
		
		_serverProvidedMetadata = {
			created = kTestDate1,
			updated = kTestDate2
		},

		status = "accepted",
		role = optRole or "viewer",
	}
	
end

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

local function isUUID( value )
	return WFArguments_uuid.isUuid( value )
end

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

local function sortByEmail( a, b )
	return a.email < b.email 
end

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

local function sanitizeResult( observable, emailToIdMap )
	return observable
		:map( function( result )
		
			for _, data in ipairs( result ) do
				emailToIdMap[ data.email ] = data.id
				if type( data.id ) == 'number' then data.id = "valid_id" end
				if isUUID( data.userId ) then data.userId = "valid_userId" end
				
				if data.created and data.created ~= kTestDate1 then
					data.created = kConstDate
				end

				if data.updated and data.updated ~= kTestDate2 then
					data.updated = kConstDate
				end
			end
			
			return result
		
		end )
end

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

local function sanitizeAndSortResult( observable, emailToIdMap )
	return sanitizeResult( observable, emailToIdMap )
		:map( function( result )
			table.sort( result, sortByEmail )
			return result
		end )
end

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

function WFCatCollaborationTest.rxTestCollaboratorsBasic( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	local docStore = catalog:docStore()
	
	-- create album
	createAlbum( rxTest, catalog, albumId )

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false, } } )
	
	WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, { allowInviteRequests = false, hide_url = true, } ):call( WFRxRunUntilDone )

	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = false, hide_url = true, } } )
	
	-- Add some collaborators
	
	-- 1. Send e-mails
	rxTest:expectOnlyCompleted(
		Observable.combineLatest(
			WFCatCollaboration.inviteByEmail( catalog, albumId, "friend@gmail.com" ),
			WFCatCollaboration.inviteByEmail( catalog, albumId, "brother@gmail.com", 'contributor' ),
			WFCatCollaboration.inviteByEmail( catalog, albumId, "sister@gmail.com"),
			WFCatCollaboration.inviteByEmail( catalog, albumId, "mother@gmail.com", 'editor' )
		)
			:takeLast( 0 )
	)
	
	-- 2. Add some users
	
	local feedbackUsers = {
		createFeedbackUser( 1 ),
		createFeedbackUser( 2 ),
		createFeedbackUser( 3 ),
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( feedbackUsers )
			:takeLast( 0 )
	)
		
	-- 3. Simulate already added members
	local spaceUsers = {
		createSpaceUserDocument( spaceId, feedbackUsers[ 1 ]._id ),
		createSpaceUserDocument( spaceId, feedbackUsers[ 2 ]._id ),
		createSpaceUserDocument( spaceId, feedbackUsers[ 3 ]._id ),
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( spaceUsers )
			:takeLast( 0 )
	)
	
	-- 4. Simulate incoming requests
	local requests = {
		createRequestDocument( spaceId, feedbackUsers[ 1 ]._id ),
		createRequestDocument( spaceId, feedbackUsers[ 2 ]._id ),
		createRequestDocument( spaceId, feedbackUsers[ 2 ]._id, spaceUsers[ 1 ]._id ),
		createRequestDocument( spaceId, feedbackUsers[ 3 ]._id ),
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( requests )
			:takeLast( 0 )
	)
		
	-- Check all the getters
	local invitationIds, requestIds, collaboratorIds = {}, {}, {}
	
	local getInvitations = rxTest:observe( sanitizeAndSortResult( WFCatCollaboration.getInvitations( catalog, albumId ), invitationIds ) )
	local getPendingRequests = rxTest:observe( sanitizeAndSortResult( WFCatCollaboration.getPendingRequests( catalog, albumId ), requestIds ) )
	local getCollaborators = rxTest:observe( sanitizeAndSortResult( WFCatCollaboration.getCollaborators( catalog, albumId ), collaboratorIds ) )

	getInvitations:expectDataWithinTime( 3, table.sort( {
		{
			created = kConstDate,
			updated = kConstDate,
			email = "friend@gmail.com",
			id = "valid_id",
			role = "viewer",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "brother@gmail.com",
			id = "valid_id",
			role = "contributor",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "sister@gmail.com",
			id = "valid_id",
			role = "viewer",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "mother@gmail.com",
			id = "valid_id",
			role = "editor",
			status = "open",
		},
	}, sortByEmail ) )

	getPendingRequests:expectDataWithinTime( 3, table.sort( {
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "open",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "open",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = false,
			role = "viewer",
			status = "approved",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 3 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 3 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "open",
			userId = "valid_userId",
		},
	}, sortByEmail ) )

	getCollaborators:expectDataWithinTime( 3, table.sort( {
		{
			contributionCount = 0,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = 'valid_id',
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kTestDate2,
		},
		{
			contributionCount = 0,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = 'valid_id',
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kTestDate2,
		},
		{
			contributionCount = 0,
			email = feedbackUsers[ 3 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 3 ]._serverProvidedMetadata.full_name,
			id = 'valid_id',
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kTestDate2,
		},
	}, sortByEmail ) )
	
	-- Make some updates
	local updates = {
		{ id = invitationIds[ "friend@gmail.com" ], role = 'contributor' },
		{ id = requestIds[ feedbackUsers[ 1 ]._serverProvidedMetadata.email ], role = 'contributor' },
		{ id = collaboratorIds[ feedbackUsers[ 1 ]._serverProvidedMetadata.email ], role = 'editor' },
		{ id = requestIds[ feedbackUsers[ 2 ]._serverProvidedMetadata.email ], status = 'rejected' },
		{ id = collaboratorIds[ feedbackUsers[ 2 ]._serverProvidedMetadata.email ], status = 'removed' },
		{ id = invitationIds[ 3 ], delete = true },
		{ id = requestIds[ feedbackUsers[ 3 ]._serverProvidedMetadata.email ], delete = true },
		{ id = collaboratorIds[ feedbackUsers[ 3 ]._serverProvidedMetadata.email ], delete = true },
	}
	
	WFCatCollaboration.updateCollaborators( catalog, updates ):call( WFRxRunUntilDone )
	
	-- Check that the updates have been reflected
	getInvitations:expectDataWithinTime( 3, table.sort( {
		{
			created = kConstDate,
			updated = kConstDate,
			email = "friend@gmail.com",
			id = "valid_id",
			role = "contributor",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "brother@gmail.com",
			id = "valid_id",
			role = "contributor",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "mother@gmail.com",
			id = "valid_id",
			role = "editor",
			status = "open",
		},
	}, sortByEmail ) )

	getPendingRequests:expectDataWithinTime( 3, table.sort( {
		{
			created = kTestDate1,
			updated = kConstDate,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "contributor",
			status = "open",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kConstDate,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "rejected",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = false,
			role = "viewer",
			status = "approved",
			userId = "valid_userId",
		},
	}, sortByEmail ) )

	getCollaborators:expectDataWithinTime( 3, table.sort( {
		{
			contributionCount = 0,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			role = "editor",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kConstDate,
		},
		{
			contributionCount = 0,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "removed",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kConstDate,
		},
	}, sortByEmail ) )
	
	-- Unshare the album
	WFCatCollaboration.unshareAlbum( catalog, albumId ):call( WFRxRunUntilDone )
	
	-- Check that all the collaborators have been removed
	getInvitations:expectDataWithinTime( 3, {} )
	getPendingRequests:expectDataWithinTime( 3, {} )
	getCollaborators:expectDataWithinTime( 3, {} )
	
	getInvitations:dispose()
	getPendingRequests:dispose()
	getCollaborators:dispose()
	sharedAlbums:dispose()
	
end

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

function WFCatCollaborationTest.rxTestHideObserverFromInviteeList( rxTest )

	rxTest:setTimeLimit( 10 )

	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	local docStore = catalog:docStore()

	-- create album
	createAlbum( rxTest, catalog, albumId )

	-- share it
	local spaceId = rxTest:getFirstValue( WFCatCollaboration.shareAlbum( catalog, albumId ) :map( function( result ) return result.id end ) )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false, } } )
	
	rxTest:waitFor( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, { allowInviteRequests = false, hide_url = true, } ) )

	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = false, hide_url = true, } } )
	
	-- Add some collaborators
	
	-- 1. Send e-mails
	rxTest:waitFor(
		Observable.combineLatest(
			WFCatCollaboration.inviteByEmail( catalog, albumId, "friend@gmail.com" ),
			WFCatCollaboration.inviteByEmail( catalog, albumId, "brother@gmail.com", 'contributor' ),
			WFCatCollaboration.inviteByEmail( catalog, albumId, "sister@gmail.com"),
			WFCatCollaboration.inviteByEmail( catalog, albumId, "mother@gmail.com", 'editor' )
		)
	)
	
	-- 2. Add some users
	
	local feedbackUsers = {
		createFeedbackUser( 1 ),
		createFeedbackUser( 2 ),
		createFeedbackUser( 3 ),
		createFeedbackUser( 4 ),
	}
	
	rxTest:waitFor( docStore:bulkSetDocValue( feedbackUsers ) )
		
	-- 3. Simulate already added members
	local spaceUsers = {
		createSpaceUserDocument( spaceId, feedbackUsers[ 1 ]._id ),
		createSpaceUserDocument( spaceId, feedbackUsers[ 2 ]._id ),
		createSpaceUserDocument( spaceId, feedbackUsers[ 3 ]._id ),
		-- create observer role
		createSpaceUserDocument( spaceId, feedbackUsers[ 4 ]._id, "observer" ),
	}
	
	rxTest:waitFor( docStore:bulkSetDocValue( spaceUsers ) )
	
	-- 4. Simulate incoming requests
	local requests = {
		createRequestDocument( spaceId, feedbackUsers[ 1 ]._id ),
		createRequestDocument( spaceId, feedbackUsers[ 2 ]._id ),
		createRequestDocument( spaceId, feedbackUsers[ 2 ]._id, spaceUsers[ 1 ]._id ),
		createRequestDocument( spaceId, feedbackUsers[ 3 ]._id ),
	}
	
	rxTest:waitFor( docStore:bulkSetDocValue( requests ) )
		
	-- Check all the getters
	local invitationIds, requestIds, collaboratorIds = {}, {}, {}
	
	local getInvitations = rxTest:observe( sanitizeAndSortResult( WFCatCollaboration.getInvitations( catalog, albumId ), invitationIds ) )
	local getPendingRequests = rxTest:observe( sanitizeAndSortResult( WFCatCollaboration.getPendingRequests( catalog, albumId ), requestIds ) )
	local getCollaborators = rxTest:observe( sanitizeAndSortResult( WFCatCollaboration.getCollaborators( catalog, albumId ), collaboratorIds ) )

	getInvitations:expectDataWithinTime( 3, table.sort( {
		{
			created = kConstDate,
			updated = kConstDate,
			email = "friend@gmail.com",
			id = "valid_id",
			role = "viewer",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "brother@gmail.com",
			id = "valid_id",
			role = "contributor",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "sister@gmail.com",
			id = "valid_id",
			role = "viewer",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "mother@gmail.com",
			id = "valid_id",
			role = "editor",
			status = "open",
		},
	}, sortByEmail ) )

	getPendingRequests:expectDataWithinTime( 3, table.sort( {
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "open",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "open",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = false,
			role = "viewer",
			status = "approved",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 3 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 3 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "open",
			userId = "valid_userId",
		},
	}, sortByEmail ) )

	getCollaborators:expectDataWithinTime( 3, table.sort( {
		{
			contributionCount = 0,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = 'valid_id',
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kTestDate2,
		},
		{
			contributionCount = 0,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = 'valid_id',
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kTestDate2,
		},
		{
			contributionCount = 0,
			email = feedbackUsers[ 3 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 3 ]._serverProvidedMetadata.full_name,
			id = 'valid_id',
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kTestDate2,
		},
	}, sortByEmail ) )
	
	-- Make some updates
	local updates = {
		{ id = invitationIds[ "friend@gmail.com" ], role = 'contributor' },
		{ id = requestIds[ feedbackUsers[ 1 ]._serverProvidedMetadata.email ], role = 'contributor' },
		{ id = collaboratorIds[ feedbackUsers[ 1 ]._serverProvidedMetadata.email ], role = 'editor' },
		{ id = requestIds[ feedbackUsers[ 2 ]._serverProvidedMetadata.email ], status = 'rejected' },
		{ id = collaboratorIds[ feedbackUsers[ 2 ]._serverProvidedMetadata.email ], status = 'removed' },
		{ id = invitationIds[ 3 ], delete = true },
		{ id = requestIds[ feedbackUsers[ 3 ]._serverProvidedMetadata.email ], delete = true },
		{ id = collaboratorIds[ feedbackUsers[ 3 ]._serverProvidedMetadata.email ], delete = true },
	}
	
	WFCatCollaboration.updateCollaborators( catalog, updates ):call( WFRxRunUntilDone )
	-- Check that the updates have been reflected
	getInvitations:expectDataWithinTime( 3, table.sort( {
		{
			created = kConstDate,
			updated = kConstDate,
			email = "friend@gmail.com",
			id = "valid_id",
			role = "contributor",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "brother@gmail.com",
			id = "valid_id",
			role = "contributor",
			status = "open",
		},
		{
			created = kConstDate,
			updated = kConstDate,
			email = "mother@gmail.com",
			id = "valid_id",
			role = "editor",
			status = "open",
		},
	}, sortByEmail ) )

	getPendingRequests:expectDataWithinTime( 3, table.sort( {
		{
			created = kTestDate1,
			updated = kConstDate,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "contributor",
			status = "open",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kConstDate,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = true,
			role = "viewer",
			status = "rejected",
			userId = "valid_userId",
		},
		{
			created = kTestDate1,
			updated = kTestDate2,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			pendingApproval = false,
			role = "viewer",
			status = "approved",
			userId = "valid_userId",
		},
	}, sortByEmail ) )

	getCollaborators:expectDataWithinTime( 3, table.sort( {
		{
			contributionCount = 0,
			email = feedbackUsers[ 1 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			role = "editor",
			isSpaceOwner = false,
			isYou = false,
			status = "accepted",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kConstDate,
		},
		{
			contributionCount = 0,
			email = feedbackUsers[ 2 ]._serverProvidedMetadata.email,
			fullName = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			id = "valid_id",
			role = "viewer",
			isSpaceOwner = false,
			isYou = false,
			status = "removed",
			userId = "valid_userId",
			created = kTestDate1,
			updated = kConstDate,
		},
	}, sortByEmail ) )
	
	-- Unshare the album
	rxTest:waitFor( WFCatCollaboration.unshareAlbum( catalog, albumId ) )
	
	-- Check that all the collaborators have been removed
	getInvitations:expectDataWithinTime( 3, {} )
	getPendingRequests:expectDataWithinTime( 3, {} )
	getCollaborators:expectDataWithinTime( 3, {} )
	
	getInvitations:dispose()
	getPendingRequests:dispose()
	getCollaborators:dispose()
	sharedAlbums:dispose()

end

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

function WFCatCollaborationTest.rxTestAlbumsSharedStatus( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	local docStore = catalog:docStore()
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getSharingStatusByAlbumId( catalog ) )
	
	-- Create a user as a prerequisite
	local feedbackUser = createFeedbackUser( 1 )
	
	rxTest:expectOnlyCompleted(
		docStore:setDocValue( feedbackUser )
			:takeLast( 0 )
	)

	
	-- create album
	createAlbum( rxTest, catalog, albumId )
	

	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = true,
	} } )
	
	
	-- Hide the URL
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		hide_url = true,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )

	
	-- Make the album public
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		private = false,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = false,
	} } )

	
	-- Make the album private
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		private = true,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )

	
	-- Turn on invite requests
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		allowInviteRequests = true,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )

	
	-- Turning on invite requests is useful only if the url is visible
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		hide_url = false,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = true,
	} } )

	
	-- Make it private again & turn off access requests
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		private = true,
		allowInviteRequests = false,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )

	
	-- Send an e-mail
	local invitationId
	local invitations = rxTest:observe( WFCatCollaboration.getInvitations( catalog, albumId )
		:map( function( info )
			if info and info[ 1 ] and info[ 1 ].id then
				invitationId = info[ 1 ].id
				return true
			end
			
			return false
		end ) )
		
	rxTest:expectSingleValue( WFCatCollaboration.inviteByEmail( catalog, albumId, "friend@gmail.com" ), ':ignore:' )
	
	invitations:expectDataWithinTime( 3, true )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = true,
	} } )
	
	-- Revoke the e-mail
	rxTest:expectSingleValue( WFCatCollaboration.updateCollaborators( catalog, { {
		id = invitationId,
		delete = true,
	} } ), ':ignore:' )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )
	
	
	-- New access request
	local requestId
	local requests = rxTest:observe( WFCatCollaboration.getPendingRequests( catalog, albumId )
		:map( function( info )
			if info and info[ 1 ] and info[ 1 ].id then
				requestId = info[ 1 ].id
				return true
			end
			
			return false
		end ) )
		
	local requestDoc = createRequestDocument( spaceId, feedbackUser._id )
	
	rxTest:expectOnlyCompleted(
		docStore:setDocValue( requestDoc )
			:takeLast( 0 )
	)
	
	requests:expectDataWithinTime( 3, true )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = true,
	} } )

	
	-- Deny the access request
	rxTest:expectSingleValue( WFCatCollaboration.updateCollaborators( catalog, { {
		id = requestId,
		status = 'rejected',
	} } ), ':ignore:' )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )

	
	-- Simulate an approved user
	local userId
	local collaborators = rxTest:observe( WFCatCollaboration.getCollaborators( catalog, albumId )
		:map( function( info )
			if info and info[ 1 ] and info[ 1 ].id then
				userId = info[ 1 ].id
				return true
			end
			
			return false
		end ) )
		
	local spaceUserDoc = createSpaceUserDocument( spaceId, feedbackUser._id )
	
	rxTest:expectOnlyCompleted(
		docStore:setDocValue( spaceUserDoc )
			:takeLast( 0 )
	)
	
	collaborators:expectDataWithinTime( 3, true )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = true,
		nCollaborators = 1,
	} } )
	
	local feedbackUser2 = createFeedbackUser( 2 )
	local observerUserDoc = createSpaceUserDocument( spaceId, feedbackUser2._id, true )
	
	rxTest:expectOnlyCompleted(
		docStore:setDocValue( observerUserDoc )
			:takeLast( 0 )
	)
	
	collaborators:expectDataWithinTime( 3, true )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = true,
		nCollaborators = 1,
	} } )
	
	-- Remove the approved user
	rxTest:expectSingleValue( WFCatCollaboration.updateCollaborators( catalog, { {
		id = userId,
		status = 'removed',
	} } ), ':ignore:' )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )

	
	-- Make it public again
	rxTest:expectSingleValue( WFCatCollaboration.setSharedAlbumAttributes( catalog, albumId, {
		private = false,
		allowInviteRequests = false,
	} ), ':ignore:' )

	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = true,
		isPrivate = false,
	} } )

	
	-- Unshare the album
	rxTest:expectSingleValue( WFCatCollaboration.unshareAlbum( catalog, albumId ), ':ignore:' )
	
	sharedAlbums:expectDataWithinTime( 3, { [ albumId ] = {
		isShared = false,
	} } )
	
	sharedAlbums:dispose()
	collaborators:dispose()
	invitations:dispose()
	
end

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

function WFCatCollaborationTest.rxTestShareAlbum_testNightly( rxTest )
	
	rxTest:setTimeLimit( 60 )

	local catalogTestSession = WFCatTestSession( rxTest )

	local catalog

	rxTest:expectSingleValue(
		catalogTestSession:catalog()
			:tap( function( _catalog )
				catalog = _catalog
			end ),
		':ignore:' )

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

	local options = {
		continuousTaskDelays = continuousTaskDelays,
		isCatalogPuller = true
	}

	local puller = WFCatMetadataPuller( catalog, "Test Puller", options )
	local pusher = WFCatMetadataPusher( catalog, options )
	
	rxTest:addCloseTarget( puller )
	rxTest:addCloseTarget( pusher )
	
	puller:getUpdatesContinuously()
	pusher:sendUpdatesContinuously()
	
	-- create album
	local albumId = createAlbum( rxTest, catalog )
	
	-- wait until album is on Oz
	local albumIsOnOz = rxTest:observe( 
							catalog:docStore():ongoingDocValue( albumId )
								:map( function( docValue )
									return docValue._serverProvidedMetadata ~= nil and 'albumIsOnOz' or 'albumIsNotOnOz'
								end ) )
								
	albumIsOnOz:expectDataWithinTime( 15, 'albumIsOnOz' )
	
	albumIsOnOz:dispose()
	
	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	local sharedAlbums = rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
	
	-- check the get shares API to see if it's shared
	sharedAlbums:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
	sharedAlbums:dispose()
	
	local hasSharedAlbumUrl = rxTest:observe( WFCatCollaboration.sharedAlbumUrl( catalog, albumId )
											:map( function( maybeUrl)
												if maybeUrl then 
													return not not string.match( maybeUrl, '^https://adobe.ly(.*)$' )
												end
												return false
											end ) )
											
	hasSharedAlbumUrl:expectDataWithinTime( 40, true )
	hasSharedAlbumUrl:dispose()
	
	puller:stopContinuousUpdates()
	pusher:stopContinuousUpdates()

end 

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

function WFCatCollaborationTest.rxTestShareAlbumWithPreconditionFailedError_testNightly( rxTest )
	
	rxTest:setTimeLimit( 80 )

	local catalogTestSession = WFCatTestSession( rxTest )

	local catalog

	rxTest:expectSingleValue(
		catalogTestSession:catalog()
			:tap( function( _catalog )
				catalog = _catalog
			end ),
		':ignore:' )

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

	local options = {
		continuousTaskDelays = continuousTaskDelays,
		isCatalogPuller = true
	}

	local puller = WFCatMetadataPuller( catalog, "Test Puller", options )
	local pusher = WFCatMetadataPusher( catalog, options )
	
	rxTest:addCloseTarget( puller )
	rxTest:addCloseTarget( pusher )
	
	puller:getUpdatesContinuously()
	pusher:sendUpdatesContinuously()
	
	-- create album
	local albumId = createAlbum( rxTest, catalog )
	
	-- wait until album is on Oz
	local albumIsOnOz = rxTest:observe( 
							catalog:docStore():ongoingDocValue( albumId )
								:map( function( docValue )
									return docValue._serverProvidedMetadata ~= nil and 'albumIsOnOz' or 'albumIsNotOnOz'
								end ) )
								
	albumIsOnOz:expectDataWithinTime( 15, 'albumIsOnOz' )
	
	albumIsOnOz:dispose()
	
	-- share it
	local spaceId
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ):tap( function( result ) spaceId = result.id end ), ':ignore:' )
	
	rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
		:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
		:dispose()
	
	-- check the get shares API to see if it's shared
	local hasSharedAlbumUrl = rxTest:observe( WFCatCollaboration.sharedAlbumUrl( catalog, albumId )
											:map( function( maybeUrl)
												if maybeUrl then 
													return not not string.match( maybeUrl, '^https://adobe.ly(.*)$' )
												end
												return false
											end ) )
											
	hasSharedAlbumUrl:expectDataWithinTime( 40, true )
	hasSharedAlbumUrl:dispose()
	
	-- create space on Oz with the same albumId, this should receive 412 error
	local spaceId2
	rxTest:expectSingleValue( WFCatCollaboration.DEBUG_createShareWithOz( catalog, albumId ):tap( function( result ) spaceId2 = result.id end ), ':ignore:' )
	
	rxTest:expectEqual( spaceId, spaceId2 )
	
	rxTest:observe( WFCatCollaboration.getAllSharedAlbums( catalog ) )
		:expectDataWithinTime( 3, { { albumId = albumId, isPrivate = kDefaultShareIsPrivate, spaceId = spaceId, allowInviteRequests = true, hide_url = false } } )
		:dispose()
	
	puller:stopContinuousUpdates()
	pusher:stopContinuousUpdates()

end

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

function WFCatCollaborationTest.rxTestHasSharedAlbumsWithDisplayFilters( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- create album
	
	createAlbum( rxTest, catalog, albumId )

	rxTest:expectSingleValue( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters( catalog ),
		false )

	-- share it
	
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ), ':ignore:' )

	rxTest:expectSingleValue( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters( catalog ),
		false )
	
	-- Add a display filter
	
	rxTest:expectSingleValue( catalog:docStore():applyOverlay( {
			_id = albumId,
			presentation = {
				filters = {
					pickedOnly = true
				}
			}
		} ), ':ignore:' )
		
	rxTest:expectSingleValue( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters( catalog ),
		true )
	
end

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

function WFCatCollaborationTest.rxTestGetMigratedToUnfilteredAlbumId( rxTest )
	
	rxTest:setTimeLimit( 5 )
	
	local catalog = createFakeWFCatalog( rxTest )
	
	-- create album
	
	local albumId = createAlbum( rxTest, catalog )
		
	rxTest:expectSingleValue( WFCatCollaboration.getMigratedToUnfilteredAlbumId( catalog, albumId ),
		nil )
	
	-- "migrate" the album
	
	local migratedAlbumId = KSIdGenerator.uuidInLowercaseHex()
	
	rxTest:expectSingleValue( catalog:docStore():applyOverlay( {
			_id = albumId,
			_serverProvidedMetadata = {
				migratedToUnfilteredAlbum = {
					id = migratedAlbumId
				}
			}
		} ), ':ignore:' )
	
	rxTest:expectSingleValue( WFCatCollaboration.getMigratedToUnfilteredAlbumId( catalog, albumId ),
		migratedAlbumId )
	
end

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

function WFCatCollaborationTest.rxTestDeleteDisplayFiltersFromAlbum( rxTest )
	
	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- create album
	
	createAlbum( rxTest, catalog, albumId )

	rxTest:expectSingleValue( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters( catalog ),
		false )

	-- share it
	
	rxTest:expectSingleValue( WFCatCollaboration.shareAlbum( catalog, albumId ), ':ignore:' )

	
	-- Add a display filter
	
	rxTest:expectSingleValue( catalog:docStore():applyOverlay( {
			_id = albumId,
			presentation = {
				filters = {
					pickedOnly = true
				}
			}
		} ), ':ignore:' )
		
	rxTest:expectSingleValue( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters( catalog ),
		true )
	
	-- Delete the display filter
	
	rxTest:expectSingleValue( WFCatCollaboration.deleteDisplayFiltersFromAlbum( catalog, albumId ),
		':ignore:' )
	
	-- Verify
	
	rxTest:expectSingleValue( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters( catalog ),
		false )
	
end

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

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

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

end

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

function WFCatCollaborationTest.rxTestGetCollaboratorsCount( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	local docStore = catalog:docStore()
	
	-- Create and share album.
	
	createAlbum( rxTest, catalog, albumId )

	local spaceId
	
	rxTest:expectSingleValue( 
			WFCatCollaboration.shareAlbum( catalog, albumId )
				:tap( function( result ) 
					spaceId = result.id 
				end ), ':ignore:' )
	
	-- Add some feedbackUsers
	
	local feedbackUsers = {
		createFeedbackUser( 1 ),
		createFeedbackUser( 2 )
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( feedbackUsers )
			:takeLast( 0 ) )
		
	-- Simulate already added members
	
	local spaceUsers = {
		createSpaceUserDocument( spaceId, feedbackUsers[ 1 ]._id ),
		createSpaceUserDocument( spaceId, feedbackUsers[ 2 ]._id )
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( spaceUsers )
			:takeLast( 0 )
	)
		
	local collaboratorIds = {}
		
	local getCollaborators = rxTest:observe( 
			sanitizeResult( WFCatCollaboration.getCollaborators( catalog, albumId ), collaboratorIds )
				:map( function( collaborators )
				
					local simplifiedResults = {}
				
					for _, collaborator in ipairs( collaborators ) do
					
						simplifiedResults[ #simplifiedResults + 1 ] = {
						
							count = collaborator.contributionCount,
							name = collaborator.fullName
						
						}
						
					end
				
					return simplifiedResults
					
				end ) )

	getCollaborators:expectDataWithinTime( 3, {
		{
			count = 0,
			name = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
		},
		{
			count = 0,
			name = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
		}
	} )
	
	-- Add images from collaborators and retest count.
	
	local owner1AssetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	local owner1AssetDoc1 = buildDocValue( rxTest, catalog, owner1AssetId1 )
	KSTableUtils.setNested( owner1AssetDoc1, feedbackUsers[ 1 ]._id, '_serverProvidedMetadata', 'importedByUser', 'id' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( owner1AssetDoc1 ), ':ignore:' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue {
			_id = KSIdGenerator.uuidInLowercaseHex(),
			type = 'album_asset',
			album = { id = albumId },
			asset = { id = owner1AssetId1 }
		}, ':ignore:' )
		
	local owner1AssetId2 = KSIdGenerator.uuidInLowercaseHex()
	
	local owner1AssetDoc2 = buildDocValue( rxTest, catalog, owner1AssetId2 )
	KSTableUtils.setNested( owner1AssetDoc2, feedbackUsers[ 1 ]._id, '_serverProvidedMetadata', 'importedByUser', 'id' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( owner1AssetDoc2 ), ':ignore:' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue {
			_id = KSIdGenerator.uuidInLowercaseHex(),
			type = 'album_asset',
			album = { id = albumId },
			asset = { id = owner1AssetId2 }
		}, ':ignore:' )
	
	local owner2AssetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	local owner2AssetDoc1 = buildDocValue( rxTest, catalog, owner2AssetId1 )
	KSTableUtils.setNested( owner2AssetDoc1, feedbackUsers[ 2 ]._id, '_serverProvidedMetadata', 'importedByUser', 'id' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( owner2AssetDoc1 ), ':ignore:' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue {
			_id = KSIdGenerator.uuidInLowercaseHex(),
			type = 'album_asset',
			album = { id = albumId },
			asset = { id = owner2AssetId1 }
		}, ':ignore:' )
	
	getCollaborators:expectDataWithinTime( 3, {
		{
			count = 2,
			name = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
		},
		{
			count = 1,
			name = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
		}
	} )
	
	getCollaborators:dispose()
	
end


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

function WFCatCollaborationTest.rxTestGetCollaboratorsSpaceOwnerAndYou( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	local docStore = catalog:docStore()
	
	-- Create and share album.
	
	createAlbum( rxTest, catalog, albumId )

	local spaceId
	
	rxTest:expectSingleValue( 
			WFCatCollaboration.shareAlbum( catalog, albumId )
				:tap( function( result ) 
					spaceId = result.id 
				end ), ':ignore:' )
	
	-- Make album a shared-with-you album.
	
	rxTest:expectSingleValue( 
			catalog:docStore():applyOverlay {
				_id = albumId,
				_spaceId = spaceId,
				_localOnly = {
					JeffTest = true
				}
			}, ':ignore:' )
	
	-- Add some feedbackUsers
	
	local feedbackUsers = {
		createFeedbackUser( 1 ),
		createFeedbackUser( 2 )
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( feedbackUsers )
			:takeLast( 0 ) )
		
	-- Set owner and "you" on space.
	
	local youSpaceUserDoc = createSpaceUserDocument( spaceId, feedbackUsers[ 2 ]._id )

	rxTest:expectOnlyCompleted(
		docStore:setDocValue( youSpaceUserDoc )
			:takeLast( 0 )
	)
	
	rxTest:expectSingleValue( 
			catalog:docStore():applyOverlay {
			
				_id = spaceId,
				_serverProvidedMetadata = {
					owner = {
						id = feedbackUsers[ 1 ]._id
					},
					this_space_user = {
						id = youSpaceUserDoc._id
					}
				}
			}, ':ignore:' )
		
	local collaboratorIds = {}
		
	local getCollaborators = rxTest:observe( 
			sanitizeResult( WFCatCollaboration.getCollaborators( catalog, albumId ), collaboratorIds )
				:map( function( collaborators )
				
					local simplifiedResults = {}
				
					for _, collaborator in ipairs( collaborators ) do
					
						simplifiedResults[ #simplifiedResults + 1 ] = {
						
							name = collaborator.fullName,
							isYou = collaborator.isYou,
							isSpaceOwner = collaborator.isSpaceOwner,
							count = collaborator.contributionCount
						}
						
					end
				
					return simplifiedResults
					
				end ) )

	getCollaborators:expectDataWithinTime( 3, {
		{
			name = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			isYou = true,
			isSpaceOwner = false,
			count = 0
		},
		{
			name = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			isYou = false,
			isSpaceOwner = true,
			count = 0
		}
	} )
	
	-- Add images from the syncing user and owner to verify the count.
	
	local owner1AssetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	local owner1AssetDoc1 = buildDocValue( rxTest, catalog, owner1AssetId1 )
	KSTableUtils.setNested( owner1AssetDoc1, feedbackUsers[ 1 ]._id, '_serverProvidedMetadata', 'importedByUser', 'id' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( owner1AssetDoc1 ), ':ignore:' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue {
			_id = KSIdGenerator.uuidInLowercaseHex(),
			type = 'album_asset',
			album = { id = albumId },
			asset = { id = owner1AssetId1 }
		}, ':ignore:' )
		
	local owner2AssetId1 = KSIdGenerator.uuidInLowercaseHex()
	
	local owner2AssetDoc1 = buildDocValue( rxTest, catalog, owner2AssetId1 )
		-- Don't set the importedByUser field here since it's "you"
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( owner2AssetDoc1 ), ':ignore:' )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue {
			_id = KSIdGenerator.uuidInLowercaseHex(),
			type = 'album_asset',
			album = { id = albumId },
			asset = { id = owner2AssetId1 }
		}, ':ignore:' )
	
	getCollaborators:expectDataWithinTime( 3, {
		{
			name = feedbackUsers[ 2 ]._serverProvidedMetadata.full_name,
			isYou = true,
			isSpaceOwner = false,
			count = 1
		},
		{
			name = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			isYou = false,
			isSpaceOwner = true,
			count = 1
		}
	} )
	
	getCollaborators:dispose()
	
end


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

function WFCatCollaborationTest.rxTestGetInfoOfAllAlbumOwnersByAlbumId( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	local docStore = catalog:docStore()
	
	-- Create and share albums.
	
	createAlbum( rxTest, catalog, albumId )

	local spaceId
	
	rxTest:expectSingleValue( 
			WFCatCollaboration.shareAlbum( catalog, albumId )
				:tap( function( result ) 
					spaceId = result.id 
				end ), ':ignore:' )
	
	-- Add some feedbackUsers
	
	local feedbackUsers = {
		createFeedbackUser( 1 ),
		createFeedbackUser( 2 )
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( feedbackUsers )
			:takeLast( 0 ) )
		
	-- Set owner and "you" on space.
	
	rxTest:expectSingleValue( 
			catalog:docStore():applyOverlay {
			
				_id = spaceId,
				_serverProvidedMetadata = {
					owner = {
						id = feedbackUsers[ 1 ]._id
					},
					this_space_user = {
						id = feedbackUsers[ 2 ]._id
					}
				}
			}, ':ignore:' )
		
	-- Simulate already added members
	
	local spaceUsers = {
		createSpaceUserDocument( spaceId, feedbackUsers[ 1 ]._id ),
		createSpaceUserDocument( spaceId, feedbackUsers[ 2 ]._id )
	}
	
	rxTest:expectOnlyCompleted(
		docStore:bulkSetDocValue( spaceUsers )
			:takeLast( 0 )
	)
		
	local ownersByAlbumIdStream = rxTest:observe(
			WFCatCollaboration.getInfoOfAllAlbumOwnersByAlbumId( catalog ) )

	ownersByAlbumIdStream:expectDataWithinTime( 3, {
		[ albumId ] =  {
			id = feedbackUsers[ 1 ]._id,
			name = feedbackUsers[ 1 ]._serverProvidedMetadata.full_name,
			outOfQuota = false,
			subscriptionLapsed = false,
		},
		
	} )
	
	ownersByAlbumIdStream:dispose()
	
end

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

function WFCatCollaborationTest.rxTestGetLrWebShareUrl( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- Create album.
	
	createAlbum( rxTest, catalog, albumId )

	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId ), false )

	-- Share album.
	
	local spaceId
	
	rxTest:expectSingleValue( 
			WFCatCollaboration.shareAlbum( catalog, albumId )
				:tap( function( result ) 
					spaceId = result.id 
				end ), ':ignore:' )
	
	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId ), 
			string.format( "https://lightroom.adobe.com/shares/%s", spaceId ) )
	
	-- Test with Asset.
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	local docValue = buildDocValue( rxTest, catalog, assetId )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( docValue ), ':ignore:' )
	
	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId, assetId ),
			string.format( "https://lightroom.adobe.com/shares/%s/albums/%s/assets/%s/activity",
					spaceId, albumId, assetId ) )
	
end

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

function WFCatCollaborationTest.rxTestGetLrWebShareUrl_RejectedAsset( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local albumId = KSIdGenerator.uuidInLowercaseHex()
	local catalog = createFakeWFCatalog( rxTest, expectedRequestResponse( albumId ) )
	
	-- Create album.
	
	createAlbum( rxTest, catalog, albumId )

	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId ), false )

	-- Share album.
	
	local spaceId
	
	rxTest:expectSingleValue( 
			WFCatCollaboration.shareAlbum( catalog, albumId )
				:tap( function( result ) 
					spaceId = result.id 
				end ), ':ignore:' )

	-- Test with Asset.
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	local docValue = buildDocValue( rxTest, catalog, assetId )
	
	rxTest:expectSingleValue( catalog:docStore():setDocValue( docValue ), ':ignore:' )
					
	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId, assetId ),
			string.format( "https://lightroom.adobe.com/shares/%s/albums/%s/assets/%s/activity",
					spaceId, albumId, assetId ) )
					
	-- Mark the asset as rejected from non-owner.
	
	rxTest:expectSingleValue(

		catalog:docStore():applyOverlay {
				_id = assetId,
				reviews = {
					[ 'foo' ] = {
						flag = 'reject'
					},
				},
			}, ':ignore:' )
					
	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId, assetId ),
			string.format( "https://lightroom.adobe.com/shares/%s/albums/%s/assets/%s/activity",
					spaceId, albumId, assetId ) )
	
	-- Mark the asset as rejected from owner.
	
	local userId = catalog:ozUserId()
	
	rxTest:expectSingleValue(

		catalog:docStore():applyOverlay {
				_id = assetId,
				reviews = {
					[ userId ] = {
						flag = 'reject'
					},
				},
			}, ':ignore:' )
					
	rxTest:expectSingleValue( WFCatCollaboration.getLrWebShareUrl( catalog, albumId ), 
			string.format( "https://lightroom.adobe.com/shares/%s", spaceId ) )
	
end

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

return WFRxTestCase.wrapSuite( 'WFCatCollaborationTest', WFCatCollaborationTest )
