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

WFCatSyncStatusTest.lua

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

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

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

	-- WF.core
local WFArguments = require 'WFArguments'

local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'

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

local WFCatTestSession = require 'WFCatTestSession'

	-- WF.oz_client.managed_catalog
local WFCatAlbums = require 'WFCatAlbums'
local WFCatMetadataPusher = require 'WFCatMetadataPusher'
local WFCatMetadataPuller = require 'WFCatMetadataPuller'
	
	-- WF.oz_client.managed_catalog.sync
local WFCatSyncStatus = require 'WFCatSyncStatus'

local WFOzTestUtils = require 'WFOzTestUtils'

	-- lightroom cc
local NmbVolumeAgent = require 'NmbVolumeAgent'

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

local check = assert( WFArguments.check )

local createFakeWFCatalog = WFCatMockCatalog.createFakeWFCatalog

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

local options = {

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

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

-- @mark WFCatSyncStatusTest

local WFCatSyncStatusTest = {}

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

function WFCatSyncStatusTest.rxTestGetSyncStatusForAssets( rxTest )

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

	local function updateRevIdToMimicOzAcceptedRevision( rxTest, docStore, assetId1 )

		-- Get latest local revId for asset (as we modified it to set importSource.status)

		local assetRevId2
		rxTest:expectSingleValue(
			docStore:docValue( assetId1 )
				:tap( function( docValue )
					assetRevId2 = docValue._revId
				end ), ':ignore:' )

		-- Make it look like Oz accepted our latest revision.

		rxTest:expectSingleValue(
			docStore:applyOverlay {
						_id = assetId1,
						_serverProvidedMetadata = {
							revision_ids = {
								assetRevId2
							}
						}
					},
			':ignore:' )

	end

	----------------------------------------------------------------------------
	
	rxTest:setTimeLimit( 30 )
	
	local catalog = createFakeWFCatalog( rxTest )
	rxTest:addCloseTarget( catalog )
	
	local docStore = catalog:docStore()

	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore, function( docValue )
			return docValue
		end )
	
	local syncStatusObservable = WFCatSyncStatus.getSyncStatusForAssets( catalog )
		:map( function( statuses ) 
			local nonZeroStatuses = {}
			for k, v in pairs( statuses ) do
				if v ~= 0 then
					nonZeroStatuses[ k ] = v
				end
			end
			return nonZeroStatuses
		end )
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{ -- no assets with any status
			} )

	local syncPendingObservable = WFCatSyncStatus.syncPendingForAnyAsset( catalog )
	local persistentSyncErrorObservable = WFCatSyncStatus.persistentSyncErrorForAnyAsset( catalog )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, false )
	rxTest:observe( persistentSyncErrorObservable ):expectDataWithinTime( 5, false )
			
	local assetId1, assetRevId1 = addAssetFn()
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				NeedsMetadataUploaded = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )
			
	-- Make it look the asset can't be uploaded because we ran out of storage on oz.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
			_id = assetId1,
			_localOnly = {
				ozError = {
					subtype = 'StorageExceededError',
				},
			}
		},
		':ignore:' )

	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				StorageLimitExceeded = 1,
			} )

	-- Remove the error field.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						ozError = { _delete = true },
					},
				},
		':ignore:' )

	-- make it look like we received the same from oz and processed
	-- with WFCatMetadataPuller, but still need to upload original
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							original = {
								master = {
									relativePath = 'some/path',
									volumeGuid = '<guid>'
								},
							}
						}
					},
					_serverProvidedMetadata = {
						links = {
						},
						revision_ids = {
							assetRevId1
						}
					}
				},
		':ignore:' )
		
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				NeedsOriginalUploaded = 1,
			} )
	
	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )

	-- Make it looks like we can't upload the original because the storage
	-- has been exceeded.
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						ozError = {
							subtype = 'StorageExceededError',
						},
					},
				},
		':ignore:' )
		
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				StorageLimitExceeded = 1,
			} )

	-- Remove the error field.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						ozError = { _delete = true },
					},
				},
		':ignore:' )

	-- Make it look like oz received the original.
	-- Make our copy of the original disappear.
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					reviews = {
						[ 'foo' ] = {
							flag = 'pick'
						},
					},
					_localOnly = {
						files = {
							original = { _delete = true }
						}
					},
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" },
							["/rels/proxy_type/2560"] = { href = "something" } 
						},
					}
				},
		':ignore:' )
		
	updateRevIdToMimicOzAcceptedRevision( rxTest, docStore, assetId1 )
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				NeedsProxyDownloaded = 1,
			} )
			
	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )

	-- Make it look like we have the original, no need for a proxy.
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							original = {
								master = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								},
							}
						}
					}
				},
		':ignore:' )
			
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				Completed = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, false )

	-- Make it look like the original received by oz was corrupted but
	-- the local copy is still valid.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					importSource = {
						status = {
							error = {
								all = {
									reason = {
										sha256 = 'does not match'
									}
								}
							}
						}
					},
				},
		':ignore:' )

	updateRevIdToMimicOzAcceptedRevision( rxTest, docStore, assetId1 )

	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				NeedsOriginalUploaded = 1,
			} )

	-- Make it look like the original received by oz was corrupted and
	-- local copy is corrupted as well (detected by the file's sha256
	-- hash not matching the hash computed in the importSource earlier).

	rxTest:expectSingleValue(
		docStore:applyOverlay {
			_id = assetId1,
			_localOnly = {
				localOriginalError = 'InvalidLocalOriginal',
			}
		},
		':ignore:' )

	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				InvalidLocalOriginal = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, false )
	rxTest:observe( persistentSyncErrorObservable ):expectDataWithinTime( 5, true )

	-- Remove the error fields and update revision id.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						localOriginalError = { _delete = true },
					},
					importSource = {
						status = { _delete = true },
					},
				},
		':ignore:' )

	updateRevIdToMimicOzAcceptedRevision( rxTest, docStore, assetId1 )

	-- Remove proxy from Oz and remove local original. We should now think we need the original.
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
			_id = assetId1,
			_localOnly = {
				files = {
					original = { _delete = true }
				}
			},
			_serverProvidedMetadata = {
				links = {
					["/rels/proxy_type/2560"] = { _delete = true }
				}
			} 
		},
		':ignore:' )
			
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				NeedsOriginalDownloaded = 1,
			} )
			
	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )

	-- make it look like we received the same from oz and processed
	-- with WFCatMetadataPuller and the asset is complete
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							original = {
								master = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								},
							}
						}
					},
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						wfOzPayloadHash = '67166b4c3ed12f8b93957333a6951a66c0aed286'
					}
				},
		':ignore:' )
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				Completed = 1,
			} )
			
	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, false )

	-- Make it look like we sync has completed without master but because the asset
	-- was uploaded by Lightroom.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							original = { _delete = true },
							proxy = {
								['2560'] = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								},
						}
						}
					},
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { _delete = true },
							["/rels/proxy_type/2560"] = { href = "something" } 
						},
					}
				},
		':ignore:' )

	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				CompletedNoMaster = 1,
			} )
			
	-- Simulate a fully synced asset from the space-feed with no master or proxy
	-- on Oz.

	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							original = { _delete = true },
							proxy = { _delete = true }
						}
					},
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { _delete = true },
							["/rels/proxy_type/2560"] ={ _delete = true } 
						},
					}
				},
		':ignore:' )

	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				CompletedNoNegatives = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, false )

	-- No proxy, no original, but marked for offline use
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = { _delete = true },
						isMarkedForOfflineUse = true
					},
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" },
							["/rels/proxy_type/2560"] = { href = "something" } 
						},

					}
				},
		':ignore:' )
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				NeedsOriginalDownloadedForOffline = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )

	-- proxy, no original, marked for offline use
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							proxy = {
								['2560'] = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								},
							}
						}
					},
				},
		':ignore:' )
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				NeedsOriginalDownloadedForOffline = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )
		
	-- proxy, original, marked for offline use
	
	rxTest:expectSingleValue(
		docStore:applyOverlay {
					_id = assetId1,
					_localOnly = {
						files = {
							original = {
								master = {
									relativePath = 'foo',
									volumeGuid = 'bar'
								},
							}
						}
					},
				},
		':ignore:' )
	
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5, 
			{
				CompletedForOffline = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, false )

	-- proxy download error
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
			_id = assetId1,
			_localOnly = {
				files = { _delete = true },
				isMarkedForOfflineUse = false,
				binaryDownloadErrors = {
					['/rels/proxy_type/2560'] = {
						['something'] = true
					}
				}
			},
			_serverProvidedMetadata = {
				links = {
					["/rels/proxy_type/2560"] = { href = "something" },
					["/rels/master"] = { _delete = true }
				}
			}
		}, ':ignore:' )
		
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				ProxyDownloadError = 1,
			} )
			
	-- original download error
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
			_id = assetId1,
			_localOnly = {
				files = { _delete = true },
				binaryDownloadErrors = {
					['/rels/proxy_type/2560'] = { _delete = true },
					['/rels/master'] = {
						['something'] = true
					}
				}
			},
			_serverProvidedMetadata = {
				links = {
					["/rels/proxy_type/2560"] = { _delete = true },
					["/rels/master"] = { href = "something" },
				}
			}
		}, ':ignore:' )
		
	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				OriginalDownloadError = 1,
			} )
		
	-- deleted but oz not notified of deletion yet.

	-- Cloud trash only considers deleteTrash as assets
	-- that need to be synced. Regular deletes are only
	-- counted by the trash asset that is created.
	rxTest:expectSingleValue(
		docStore:applyOverlay {
			_id = assetId1,
			_deleted = true,
			_localOnly = { universal = { trash = { isDeleteTrash = true } } },
		}
	, ':ignore:' )

	rxTest:observe( syncStatusObservable )
		:expectDataWithinTime( 5,
			{
				NeedsMetadataUploaded = 1,
			} )

	rxTest:observe( syncPendingObservable ):expectDataWithinTime( 5, true )

end

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

function WFCatSyncStatusTest.rxTestGetLocalFileTypeForAsset( rxTest )
	
	local docValue = {
		_localOnly = {
			files = {
				original = {
					master = { 
						relativePath = 'foo',
						volumeGuid = 'bar'
					}
				},
			},
		},
	}
	
	rxTest:expectEqual( 'OriginalOnDisk', WFCatSyncStatus.translateAssetSyncLocalFileType(
			WFCatSyncStatus.getLocalFileTypeForAsset( docValue ) ) )
	
	docValue = {
		_localOnly = {
			files = {
				original = {
					master = { 
						relativePath = 'foo',
						volumeGuid = 'bar'
					}
				},
				proxy = {
					[ '2560' ] = {
						relativePath = 'foo',
						volumeGuid = 'bar'
					}
				}
			},
		},
	}
	
	rxTest:expectEqual( 'OriginalOnDisk', WFCatSyncStatus.translateAssetSyncLocalFileType(
			WFCatSyncStatus.getLocalFileTypeForAsset( docValue ) ) )
	
	docValue = {
		_localOnly = {
			files = {
				proxy = {
					['2560'] = {
						relativePath = 'foo',
						volumeGuid = 'bar'
					}
				}
			},
		},
	}
	
	rxTest:expectEqual( 'ProxyOnDisk', WFCatSyncStatus.translateAssetSyncLocalFileType(
			WFCatSyncStatus.getLocalFileTypeForAsset( docValue ) ) )
	
	docValue = {
		_localOnly = {
			files = {
			},
		},
	}
	
	rxTest:expectEqual( 'NothingOnDisk', WFCatSyncStatus.translateAssetSyncLocalFileType(
			WFCatSyncStatus.getLocalFileTypeForAsset( docValue ) ) )
	
end

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

function WFCatSyncStatusTest.rxTestGetSyncStatusForAlbums( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local catalog = createFakeWFCatalog( rxTest )
	rxTest:addCloseTarget( catalog )
	
	local docStore = catalog:docStore()
	
	local addAlbumFn = WFOzTestUtils.v2.makeAdderOfAlbum( rxTest, docStore )
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )

	-- add assets
	local albumId1, albumRevId = addAlbumFn( )
	local assetId1, assetRevId1 = addAssetFn( )
	local assetId2, assetRevId2 = addAssetFn( )
	
	local statusObservable = rxTest:observe( WFCatSyncStatus.getSyncStatusForAlbums( catalog ) )
	
	-- newly created, these shouldn't look synced	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	-- make it look like it is a 'legacy' album received from Oz with an 
	-- empty revision_ids array, this should be counted as 'synced' though 
	-- refer to LrAssetUtils.isLatestMetadataUploaded for more details
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = 1,
					_serverProvidedMetadata = {
						links = {},
						revision_ids = {
						},
					}
				},
		':ignore:' )
	
	-- album should now appear synced
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'synced',  	-- album 1
		} )
		
	-- remove revision_ids and go back to syncing
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = 1,
					_serverProvidedMetadata = {
						links = {},
						revision_ids = { _delete = true },
					}
				},
		':ignore:' )
		
		
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
		
	-- make it look like we received the same from oz and processed
	-- with WFCatMetadataPuller
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = 1,
					_serverProvidedMetadata = {
						links = {},
						revision_ids = {
							albumRevId
						},
					}
				},
		':ignore:' )

	-- album should now appear synced
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'synced',  	-- album 1
		} )
	
	-- change album title
	
	rxTest:expectOnlyCompleted(
		WFCatAlbums.renameAlbumOrFolder( catalog, 1, 'new_album_name' )
	)
	
	local updatedAlbumRevId
	
	rxTest:expectSingleValue( catalog:docStore():docValue( 1 )
		:tap( function( docValue )
			updatedAlbumRevId = docValue._revId
		end ), ':ignore:' )

	-- album should appear to be syncing
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	-- fake WFCatMetadataPuller
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = 1,
					_serverProvidedMetadata = {
						revision_ids = {
							albumRevId,
							updatedAlbumRevId,
						}
					}
				},
		':ignore:' )
	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'synced',  	-- album 1
		} )
	
	-- add some assets to the album
	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( catalog, 1, { assetId1, assetId2 } )
	)
	
	-- since assets aren't uploaded, album should be syncing
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	-- complete first asset by faking WFCatMetadataPuller 
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId1,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId1
						}
					}
				},
		':ignore:' )
		
	-- album should still be syncing
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'syncing',  	-- album 1
		} )
		
	-- complete second asset by faking WFCatMetadataPuller
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId2,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId2
						}
					}
				},
		':ignore:' )
	
	-- album should now be complete 
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'synced',  	-- album 1
		} )
	
end

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

function WFCatSyncStatusTest.rxTestGetSyncStatusForAlbums_IgnoreAlbumStatus( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local catalog = createFakeWFCatalog( rxTest )
	rxTest:addCloseTarget( catalog )
	
	local docStore = catalog:docStore()
	
	local addAlbumFn = WFOzTestUtils.v2.makeAdderOfAlbum( rxTest, docStore )
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )

	-- add assets
	local albumId1 = addAlbumFn( )
	local assetId1, assetRevId1 = addAssetFn( )
	local assetId2, assetRevId2 = addAssetFn( )
	
	local statusObservable = rxTest:observe( WFCatSyncStatus.getSyncStatusForAlbums( catalog, true ) )
	
	-- when ignoring album status, an album should immediately be considered synced
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'synced',  -- album 1
		} )
	
	-- change album title
	
	rxTest:expectOnlyCompleted(
		WFCatAlbums.renameAlbumOrFolder( catalog, 1, 'new_album_name' )
	)
	
	local updatedAlbumRevId
	
	rxTest:expectSingleValue( catalog:docStore():docValue( 1 )
		:tap( function( docValue )
			updatedAlbumRevId = docValue._revId
		end ), ':ignore:' )

	-- add some assets to the album
	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( catalog, 1, { assetId1, assetId2 } )
	)
	
	-- since assets aren't uploaded, album should be syncing
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	-- complete first asset by faking WFCatMetadataPuller 
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId1,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId1
						}
					}
				},
		':ignore:' )
		
	-- album should still be syncing
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'syncing',  	-- album 1
		} )
		
	-- complete second asset by faking WFCatMetadataPuller
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId2,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId2
						}
					}
				},
		':ignore:' )
	
	-- album should now be complete 
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'synced',  	-- album 1
		} )
	
end

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

function WFCatSyncStatusTest.rxTestGetSyncStatusForOfflineAlbums( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local catalog = createFakeWFCatalog( rxTest )
	rxTest:addCloseTarget( catalog )
	
	local keptLocalInfo = {
		keepAllOriginals = true,
		keepAllProxies = true,
	}
	
	local originalsPathInfo = catalog:fileSpec():originalsPathInfo()
	local volumeGuid = originalsPathInfo.volumeGuid
	local relativePath = originalsPathInfo.relativePath
	
	local volumeAgent = NmbVolumeAgent( catalog, nil, volumeGuid, relativePath, 1, keptLocalInfo )
	 volumeAgent:setInternetConnectionStatus( true )
	
	catalog:addVolumeAgent( volumeAgent )
	
	local docStore = catalog:docStore()
	
	local addAlbumFn = WFOzTestUtils.v2.makeAdderOfAlbum( rxTest, docStore )
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )

	-- add assets
	local albumId1, _ = addAlbumFn( )
	local assetId1, assetRevId1 = addAssetFn( )
	local assetId2, assetRevId2 = addAssetFn( )
	
	local statusObservable = rxTest:observe( WFCatSyncStatus.getSyncStatusForAlbums( catalog ) )
	
	-- newly created, these shouldn't look synced	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	-- Mark the album for offline use.
	
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = albumId1,
					_localOnly = {
						isMarkedForOfflineUse = true
					}
				},
		':ignore:' )
	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncingForOfflineUse',  -- album 1
		} )
	
	-- make it look like it is a 'legacy' album received from Oz with an 
	-- empty revision_ids array, this should be counted as 'synced' though 
	-- refer to LrAssetUtils.isLatestMetadataUploaded for more details
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = 1,
					_serverProvidedMetadata = {
						links = {},
						revision_ids = {
						},
					}
				},
		':ignore:' )
	
	-- album should now appear synced
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncedForOfflineUse',  	-- album 1
		} )

	-- add some assets to the album
	
	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( catalog, 1, { assetId1, assetId2 } )
	)
	
	-- since assets aren't uploaded, album should be syncing
	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncingForOfflineUse',  -- album 1
		} )
	
	-- complete first asset by faking WFCatMetadataPuller 
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId1,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId1
						}
					},
					_localOnly = {
						isMarkedForOfflineUse = true
					}
				},
		':ignore:' )
		
	-- album should still be syncing
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'syncingForOfflineUse',  	-- album 1
		} )
		
	-- complete second asset by faking WFCatMetadataPuller
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId2,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId2
						}
					},
					_localOnly = {
						isMarkedForOfflineUse = true
					}
				},
		':ignore:' )
	
	-- album should now be complete 
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'syncedForOfflineUse',  	-- album 1
		} )
	
end

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

function WFCatSyncStatusTest.rxTestGetSyncStatusForOfflineAlbums_IgnoreAlbumStatus( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local catalog = createFakeWFCatalog( rxTest )
	rxTest:addCloseTarget( catalog )
	
	local keptLocalInfo = {
		keepAllOriginals = true,
		keepAllProxies = true,
	}
	
	local originalsPathInfo = catalog:fileSpec():originalsPathInfo()
	local volumeGuid = originalsPathInfo.volumeGuid
	local relativePath = originalsPathInfo.relativePath
	
	local volumeAgent = NmbVolumeAgent( catalog, nil, volumeGuid, relativePath, 1, keptLocalInfo )
	 volumeAgent:setInternetConnectionStatus( true )
	
	catalog:addVolumeAgent( volumeAgent )
	
	local docStore = catalog:docStore()
	
	local addAlbumFn = WFOzTestUtils.v2.makeAdderOfAlbum( rxTest, docStore )
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )

	-- add assets
	local albumId1, _ = addAlbumFn( )
	local assetId1, assetRevId1 = addAssetFn( )
	local assetId2, assetRevId2 = addAssetFn( )
	
	local statusObservable = rxTest:observe( WFCatSyncStatus.getSyncStatusForAlbums( catalog, true ) )
		
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'synced',  -- album 1
		} )
	
	-- Mark the album for offline use.
	
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = albumId1,
					_localOnly = {
						isMarkedForOfflineUse = true
					}
				},
		':ignore:' )
	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncedForOfflineUse',  -- album 1
		} )
	
	-- make it look like it is a 'legacy' album received from Oz with an 
	-- empty revision_ids array, this should be counted as 'synced' though 
	-- refer to LrAssetUtils.isLatestMetadataUploaded for more details
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = 1,
					_serverProvidedMetadata = {
						links = {},
						revision_ids = {
						},
					}
				},
		':ignore:' )
	
	-- album should now appear synced
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncedForOfflineUse',  	-- album 1
		} )

	-- add some assets to the album
	
	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( catalog, 1, { assetId1, assetId2 } )
	)
	
	-- since assets aren't uploaded, album should be syncing
	
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncingForOfflineUse',  -- album 1
		} )
	
	-- complete first asset by faking WFCatMetadataPuller 
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId1,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId1
						}
					},
					_localOnly = {
						isMarkedForOfflineUse = true
					}
				},
		':ignore:' )
		
	-- album should still be syncing
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'syncingForOfflineUse',  	-- album 1
		} )
		
	-- complete second asset by faking WFCatMetadataPuller
	rxTest:expectSingleValue( 
		docStore:applyOverlay {
					_id = assetId2,
					_serverProvidedMetadata = {
						links = {
							["/rels/master"] = { href = "something" } 
						},
						revision_ids = {
							assetRevId2
						}
					},
					_localOnly = {
						isMarkedForOfflineUse = true
					}
				},
		':ignore:' )
	
	-- album should now be complete 
	statusObservable:expectDataWithinTime( 5, 
		{
			[albumId1] = 'syncedForOfflineUse',  	-- album 1
		} )
	
end

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

function WFCatSyncStatusTest.rxTestGetSyncStatusForAlbums_testNightly( rxTest )

	rxTest:setTimeLimit( 80 )
	
	local catalogTestSession = WFCatTestSession( rxTest, 'NmbCatalog' )
	
	local catalog

	rxTest:expectSingleValue(

		catalogTestSession:catalog()
			:tap( function( _catalog )
				check( 'WFCatalog' )
				catalog = _catalog
			end ),

		':ignore:' )
	
	local pusher = WFCatMetadataPusher( catalog, options )
	pusher:sendUpdatesContinuously()
	
	local puller = WFCatMetadataPuller( catalog, "Test Puller", options )
	puller:getUpdatesContinuously()
	
	catalog:addCloseAction( function()
		pusher:stopContinuousUpdates()
		puller:stopContinuousUpdates()
	end )
	
	local docStore = catalog:docStore()
	
	local addRealId = function( docValue )
		docValue._id = KSIdGenerator.uuidInLowercaseHex()
		return docValue
	end
	
	local addAlbumFn = WFOzTestUtils.v2.makeAdderOfAlbum( rxTest, docStore, addRealId )
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore, addRealId )

	-- add assets
	local albumId1 = addAlbumFn( )
	local assetId1 = addAssetFn( )
	
	local statusObservable = rxTest:observe( WFCatSyncStatus.getSyncStatusForAlbums( catalog ) )
	
	-- newly created, these shouldn't look synced
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )

	-- album should now appear synced
	statusObservable:expectDataWithinTime( 40,
		{
			[albumId1] = 'synced',  	-- album 1
		} )
	
	-- change album title
	rxTest:expectOnlyCompleted(
		WFCatAlbums.renameAlbumOrFolder( catalog, 1, 'new_album_name' )
	)

	-- album should appear to be syncing
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	statusObservable:expectDataWithinTime( 40,
		{
			[albumId1] = 'synced',  	-- album 1
		} )
	
	-- add an assets to the album
	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( catalog, 1, { assetId1 } )
	)
	
	-- since assets aren't uploaded, album should be syncing
	statusObservable:expectDataWithinTime( 5,
		{
			[albumId1] = 'syncing',  -- album 1
		} )
	
	-- Without fully uploading an asset it's hard to test that the status
	-- is complete when assets are complete. We could hack the doc store with 
	-- an overlay, but that's not really different than the non-nightly tests

end

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

function WFCatSyncStatusTest.rxTestInitialSyncCompleted( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = createFakeWFCatalog( rxTest )
	
	local obs = rxTest:observe( WFCatSyncStatus.isDoingInitialSync( wfCatalog ) )
	
	obs:expectDataWithinTime( 3, true )
	
	rxTest:expectOnlyCompleted( 
		wfCatalog:docStore():setLocalDocValue {
			_id = 'Oz-isDoingInitialSync',
			isDoingInitialSync = true,
		} )
		
	obs:expectDataWithinTime( 3, true )
	
	rxTest:expectOnlyCompleted( 
		wfCatalog:docStore():setLocalDocValue {
			_id = 'Oz-isDoingInitialSync',
			isDoingInitialSync = false,
		} )
		
	obs:expectDataWithinTime( 3, false )
	
end

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

local function observeIsOnOz( rxTest, catalog, docId )

	return rxTest:observe( catalog:docStore():ongoingDocValue( docId )
		:map( function( docValue )

			local isLabeledOz = not not ( docValue._labels and docValue._labels.oz )
			
			return isLabeledOz

		end )
	)

end

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

function WFCatSyncStatusTest.rxTestIsMetadataUpload_testNightly( rxTest )

	rxTest:setTimeLimit( 80 )
	
	local catalogTestSession = WFCatTestSession( rxTest, 'NmbCatalog' )
	
	local catalog

	rxTest:expectSingleValue(

		catalogTestSession:catalog()
			:tap( function( _catalog )
				check( 'WFCatalog' )
				catalog = _catalog
			end ),

		':ignore:' )
	
	local puller = WFCatMetadataPuller( catalog, "Test Puller", options )
	local pusher = WFCatMetadataPusher( catalog, options )
	
	puller:getUpdatesContinuously()
	
	catalog:addCloseAction( function()
		pusher:close()
		puller:close()
	end )
	
	local docStore = catalog:docStore()
	
	local addRealId = function( docValue )
		docValue._id = KSIdGenerator.uuidInLowercaseHex()
		return docValue
	end
	
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore, addRealId )
	
	-- add assets
	local assetId1 = addAssetFn()
	
	-- Send changes to Oz
	
	local sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()
	
	-- Verify that the asset status is set to metadata upload pending
	rxTest:expectSingleValue( 
	
		catalog:docStore():docValue( assetId1 )
			:map( function( docValue )
			
				local assetSyncStatus = WFCatSyncStatus.getAssetStatus( docValue )
				
				return assetSyncStatus
				
			end ), 1 )
			
	local observeAssetIsOnOz = observeIsOnOz( rxTest, catalog, assetId1 )
	observeAssetIsOnOz:expectDataWithinTime( 40, true )
	
	-- Verify the asset status is no longer in metadata upload pending
	
	rxTest:expectSingleValue( 
	
		catalog:docStore():docValue( assetId1 )
			:map( function( docValue )
			
				local assetSyncStatus = WFCatSyncStatus.getAssetStatus( docValue )
				
				return assetSyncStatus
				
			end ), 2 )
		
	-- Send another change to Oz
	
	rxTest:expectSingleValue(
	
		catalog:docStore():applyOverlay {
			_id = assetId1,
			importSource = {
				originalWidth = 100,
			}
		}, ':ignore:' )
	
	observeAssetIsOnOz:expectDataWithinTime( 40, false )
	
	sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()
	
	-- Verify again the asset metadata needs to be uploaded
	
	rxTest:expectSingleValue( 
	
		catalog:docStore():docValue( assetId1 )
			:map( function( docValue )
			
				local assetSyncStatus = WFCatSyncStatus.getAssetStatus( docValue )
				
				return assetSyncStatus
				
			end ), 1 )
			
	observeAssetIsOnOz:expectDataWithinTime( 40, true )
	
	-- Asset status is no longer metadata upload pending
	
	rxTest:expectSingleValue( 
	
		catalog:docStore():docValue( assetId1 )
			:map( function( docValue )
			
				local assetSyncStatus = WFCatSyncStatus.getAssetStatus( docValue )
				
				return assetSyncStatus
				
			end ), 2 )
			
end

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

function WFCatSyncStatusTest.rxTestIsAssetCompleted( rxTest )
	
	local nonSyncingStates = WFCatSyncStatus.getNonSyncingAssetSyncStatusList()
	local nonSyncingStateByStr = KSTableUtils.mapArrayToSet( nonSyncingStates )
	
	local assetSyncStatusList = WFCatSyncStatus.getAssetSyncStatusList()

	for state in pairs( assetSyncStatusList ) do
	
		local isAssetCompleted = WFCatSyncStatus.isAssetCompleted( state )
	
		if nonSyncingStateByStr[ state ] then
		
			rxTest:expectEqual( true, isAssetCompleted )
		
		else
		
			rxTest:expectEqual( false, isAssetCompleted )
		
		end
	
	end


end

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

function WFCatSyncStatusTest.rxTestGetSyncStatusOfAssetsOfVersions( rxTest )

	-- [jandrew 2022-01-04]: WF-1644: The following test verifies reduceSyncStatuses,
	-- which reduces asset sync states into a single is-document-synced boolean.
	rxTest:setTimeLimit( 30 )
	
	local catalog = createFakeWFCatalog( rxTest )
	rxTest:addCloseTarget( catalog )
	
	local docStore = catalog:docStore()

	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore, function( docValue )
			return docValue
		end )

	local assetId1, assetRevId1 = addAssetFn()
	
	local versionId = KSIdGenerator.uuidInLowercaseHex()
	
	rxTest:waitFor( 
		docStore:setDocValue {
			_id = versionId,
			asset = {
				id = assetId1,
			},
			type = "version",
			subtype = "edit",
		} )
	
	local versionLocalId = rxTest:getFirstValue( docStore:docIdToLocalId( versionId ) )
	
	local reducedSyncStatusObserver = rxTest:observe( 
			WFCatSyncStatus.getSyncStatusOfAssetsOfVersions( catalog, { versionLocalId } ) )
	
	--- Initial asset is in a needs-metadata-sync state.
	
	reducedSyncStatusObserver:expectDataWithinTime( 5,  { [ assetId1 ] = false } )
		
	-- Make it look like the document is synced.
	
	rxTest:waitFor(
		docStore:applyOverlay {
			_id = assetId1,
			_serverProvidedMetadata = { 
				links = {
					["/rels/master"] = { href = "something" },
					["/rels/proxy_type/2560"] = { href = "something" } 
				},
				revision_ids = {
					assetRevId1
				}
			}
		} )

	reducedSyncStatusObserver:expectDataWithinTime( 5,  { [ assetId1 ] = true } )
		
	-- Make it look the asset can't be uploaded because we ran out of storage on oz.

	rxTest:waitFor(
		docStore:applyOverlay {
			_id = assetId1,
			_localOnly = {
				ozError = {
					subtype = 'StorageExceededError',
				}
			},
			name = 'foo',
			_serverProvidedMetadata = { _deleted = true }
		} )
		
	reducedSyncStatusObserver:expectDataWithinTime( 5,  { [ assetId1 ] = false } )
	
end

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

return WFRxTestCase.wrapSuite( WFCatSyncStatusTest )
