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

WFCatNotificationSyncTest.lua

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

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

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

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

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

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local KSFunctionUtils = require "KSFunctionUtils"
local WFPlatformInfo = require 'WFPlatformInfo'
local WFCapabilities = require "WFCapabilities"
local WFLogger = require "WFLogger"

	-- WF.core.os
local KSDate = require 'KSDate'

	-- WF.oz_client.managed_catalog
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatOzTestSession = require 'WFCatOzTestSession'
local WFCatSpecMock = require 'WFCatSpecMock'
local WFMockHttpRequest = require "WFMockHttpRequest"
local WFCatNotification = require 'WFCatNotification'
local WFCatNotificationSync = require 'WFCatNotificationSync'
local WFCatSpaceFeedsManager = require 'WFCatSpaceFeedsManager'
local KSJsonEncoder = require 'KSJsonEncoder'

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

local derefOpt = KSTableUtils.derefOpt

local logger = WFLogger( 'WFCatNotificationSyncTest' )
	:setLevel( 'debug' )
--------------------------------------------------------------------------------

local kUserId = KSIdGenerator.uuidInLowercaseHex()
local catalogId = '98db98bfd554470dab2f862a4b853c40'
local adobeAppId = WFCatNotificationSync.getANSAdobeAppId()

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

local nowEpoch = KSDate.timeIntervalSinceUnixEpoch() * 1000
local tenSecondsAgoEpoch = nowEpoch - ( 10 * 1000 )
local twoHoursAgoEpoch = nowEpoch - ( 2 * 60 * 60 * 1000  )
local fiveHoursAgoEpoch = nowEpoch - ( 5 * 60 * 60 * 1000 )
local tenHoursAgoEpoch = nowEpoch - ( 10 * 60 * 60 * 1000 )
local sixtytwoDaysAgoEpoch = nowEpoch - ( 62 * 24 * 60 * 60 * 1000 )

local nowIso8601 = KSDate.timeInIso8601FormatWithUnixEpochInterval( nowEpoch / 1000 )
local tenSecondsAgoIso8601 = KSDate.timeInIso8601FormatWithUnixEpochInterval( tenSecondsAgoEpoch / 1000 )
local twoHoursAgoIso8601 = KSDate.timeInIso8601FormatWithUnixEpochInterval( twoHoursAgoEpoch / 1000 )
local fiveHoursAgoIso8601 = KSDate.timeInIso8601FormatWithUnixEpochInterval( fiveHoursAgoEpoch / 1000 )

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

local WFCatNotificationSyncTest = {}

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

local WFCatMockCatalog_createWFCatalogForTestWithSpec = assert( WFCatMockCatalog.createWFCatalogForTestWithSpec )

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

local function createWFCatalogForTestWithSpec( rxTest, wfCatalogSpec )

	local indexSpecScript

	if WFPlatformInfo.isMobile() then
		indexSpecScript = 'LrmCatalogIndexDefinitions.lua'
	end

	return WFCatMockCatalog_createWFCatalogForTestWithSpec( rxTest, wfCatalogSpec, nil, indexSpecScript )

end

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

local function createCatalogSpecForTest( rxTest, expectedResponses )

	expectedResponses = expectedResponses or {}
	
	local catalogSpec = WFCatSpecMock( catalogId, kUserId, rxTest, expectedResponses )

	return catalogSpec

end

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

local notificationRequestOptions = {
	requestHeaders = {
		{
			header = 'Accept',
			value = 'application/json',
		},
		{
			header = "Content-Type",
			value = "application/json",
		},
		{
			header = 'x-adobe-app-id',
			value = adobeAppId,
		}
	},
	returnHttpErrorsAsData = true,
}

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

function WFCatNotificationSyncTest.rxTestQueryNotification( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions

		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	notificationObserve:expectDataWithinTime( 5, { } )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 0 }
	} )

end

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

function WFCatNotificationSyncTest.rxTestQueryNotificationTotalList( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580165557711",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580034924750"
				},
				"unread-count": 1
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580034924750",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "favorite",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	notificationObserve:expectData( {} )
	
	rxTest:waitFor( notificationSync:queryNotificationTotalList() )
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "favorite", twoHoursAgoIso8601, 0 },
		{ "024b78da-4631-46a6-a613-e54229e56a7e", "comment", fiveHoursAgoIso8601, 0 }
	} )

end

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

function WFCatNotificationSyncTest.rxTestNotificationExpirationFromLocal( rxTest )

	rxTest:setTimeLimit( 10 )
	-- default notification expiry is 60 days so 'sync' an old notification
	-- from ANS and make sure it gets removed
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580165557711",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580034924750"
				},
				"unread-count": 1
			}]], sixtytwoDaysAgoEpoch, sixtytwoDaysAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580034924750",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "favorite",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324"
				},
				"unread-count": 1
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	notificationObserve:expectData( {} )
	
	rxTest:waitFor( notificationSync:queryNotificationTotalList() )
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "favorite", fiveHoursAgoIso8601, 0 }
	} )
	
end

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

function WFCatNotificationSyncTest.rxTestNotificationExpirationFromList( rxTest )

	rxTest:setTimeLimit( 10 )
	-- default notification expiry is 60 days so 'sync' an old notification
	-- from ANS and make sure it gets removed
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580165557711",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580034924750"
				},
				"unread-count": 1
			}]], sixtytwoDaysAgoEpoch, sixtytwoDaysAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580034924750",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "favorite",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324"
				},
				"unread-count": 1
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, nil, false )
	rxTest:addCloseTarget( notificationSync )
		
	rxTest:waitFor( notificationSync:queryNotificationTotalList() )
	
	local notificationObserve = rxTest:observe( WFCatNotification.notificationList( notificationSync:notificationDb() ) 
		:map( function( notifications ) 
			local result = {}
			
			for _, notification in ipairs( notifications ) do
				result[ #result + 1 ] = notification.notificationId
			end
		
			return result
		end )
	)

	notificationObserve:expectDataWithinTime( 5, {
		"245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
		"a426782c-a96b-4b13-99ef-b77883d16313"
	} )
	
end
--------------------------------------------------------------------------------

function WFCatNotificationSyncTest.rxTestStartNotificationSync( rxTest )

	rxTest:setTimeLimit( 20 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711"
				},
				"unread-count": 1
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580165557711",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580034924750"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580034924750",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "favorite",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580034924750"
				},
				"unread-count": 1
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1581569405324",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "33e4aa57-547d-4953-9c33-08856a7bcd27",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1587569405323,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1581579405324"
				},
				"unread-count": 2
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, true )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	notificationObserve:expectData( {} )
	
	rxTest:waitFor( notificationSync:startNotificationSync{ checkInterval = 1, debug_updateCount = 4 } )
	
	notificationObserve:expectDataWithinTime( 15, {
		{ "33e4aa57-547d-4953-9c33-08856a7bcd27", "comment", nowIso8601, 0 },
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 0 },
		{ "024b78da-4631-46a6-a613-e54229e56a7e", "comment", twoHoursAgoIso8601, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "favorite", fiveHoursAgoIso8601, 0 }
	} )

	rxTest:observe( WFCatNotification.notificationUnreadCount( notificationSync:notificationDb(), notificationSync:clientSubtypeAllowList() ) )
		:expectDataWithinTime( 5, 4 )

end

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

function WFCatNotificationSyncTest.rxTestStartNotificationSyncInvalidResponse( rxTest )

	rxTest:setTimeLimit( 20 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711"
				},
				"unread-count": 1
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		-- a completely invalid response
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = 'invalid json',

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		-- just an invalid payload
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "invalid json in payload",
						"muted": false,
						"notification-id": "bed9f295-29e3-45c8-b74f-6fda23b16e6f",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580165557711",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580034924750"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580034924750",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1581579405324"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, true )
	rxTest:addCloseTarget( notificationSync )
		
	rxTest:waitFor( notificationSync:startNotificationSync{ checkInterval = 1, debug_updateCount = 4 } )
	
	rxTest:expectSingleValue( notificationSync:notificationDb():query{
			sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC",
			queryOnce = true
		}
		:take( 1 ),
		{ 
			{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 0 },
			{ "024b78da-4631-46a6-a613-e54229e56a7e", "comment", twoHoursAgoIso8601, 0 }
		}
	)
	
end

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

function WFCatNotificationSyncTest.rxTestNotificationExpirationFromANS( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions

		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "EXPIRED",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions

		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 0 }
	} )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {} )

end

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

function WFCatNotificationSyncTest.rxTestSetVisitedNotification( rxTest )

	rxTest:setTimeLimit( 240 )

	local catalogSpec = createCatalogSpecForTest( rxTest )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications' )
			:respond( '200', string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "READ",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 0
			}]], nowEpoch, nowEpoch ) ) )
		

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, true )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 1 }
	} )

	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:put( 'https://notify.adobe.io/ans/v1/notifications' )
			:expectRequestData( KSJsonEncoder.prettyPrint( { 
				notifications = {
					notification = {
						{
							[ 'notification-id' ] = '245d24e5-09fe-4175-80c7-ebad6c8f3a6b',
							state = "READ",
							metadata = {
								entry = {
									{
										[ 'key-value' ] = true,
										[ 'key-name' ] = 'visited'
									}
								}
							}
						}
					}
				}
			} ) )
			:respond( '200', [[{
				"notifications": {
					"notification": [
						{
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b'"
						}
					]
				}
			}]] ) )


	rxTest:waitFor( notificationSync:setVisitedNotification( { "245d24e5-09fe-4175-80c7-ebad6c8f3a6b" }) )
	
	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications' )
			:respond( '200', string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "READ",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"metadata": { 
								"entry": [
									{
										"key-name": "visited",
										"key-value": true
									} 
								]
							},
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 0
			}]], nowEpoch, nowEpoch ) ) )
	

	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 1 }
	} )

end

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

function WFCatNotificationSyncTest.rxTestDefaultAllowListNotificationSubtype( rxTest )

	rxTest:setTimeLimit( 10 )
		
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						},
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"favorite\"}",
							"muted": false,
							"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "favorite",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						},
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"asset-added\"}",
							"muted": false,
							"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "asset-added",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch, twoHoursAgoEpoch, twoHoursAgoEpoch, fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 0 },
		{ "024b78da-4631-46a6-a613-e54229e56a7e", "favorite", twoHoursAgoIso8601, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "asset-added", fiveHoursAgoIso8601, 0 },
	} )

end

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

function WFCatNotificationSyncTest.rxTestSetVisitedAndAggregation( rxTest )

	rxTest:setTimeLimit( 240 )

	local catalogSpec = createCatalogSpecForTest( rxTest )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local assetAdded1_payload = {
		aggregated_notifications = {
			{
				album = {
					id = "79c2d76d0be643b49bda50af3622a1ea",
					name = "Sammlung Falckenberg",
				},
				asset = {
					id = "a7c8f6b64fa3400f80c19fbb6336747d",
					subtype = "image",
					thumbnail_url = "https://photos-stage.adobe.io/v2/catalogs/dc513417b7374775a2c071b679c6959b/assets/a7c8f6b64fa3400f80c19fbb6336747d/revisions/db0e71ed0df141db81842dd680145e6d/renditions/f21dbf5de1b059aed69eb6695093ea8f",
				},
				catalog_id = "dc513417b7374775a2c071b679c6959b",
				person = {
					full_name = "St2 Immisch",
					id = "b2d1cad69bf1f9c840e3b90cf6dfe7b5",
					thumbnail_url = "https://photos-stage.adobe.io/v2/avatars/b2d1cad69bf1f9c840e3b90cf6dfe7b5/20201203143017/small",
				},
				space_id = "b44deae353c44887bbc1153b6317204f",
				subtype = "asset-added",
				type = "com.adobe.lightroom",
			},
		},
		album = {
			id = "79c2d76d0be643b49bda50af3622a1ea",
			name = "Sammlung Falckenberg",
		},
		asset = {
			id = "a7c8f6b64fa3400f80c19fbb6336747d",
			subtype = "image",
			thumbnail_url = "https://photos-stage.adobe.io/v2/catalogs/dc513417b7374775a2c071b679c6959b/assets/a7c8f6b64fa3400f80c19fbb6336747d/revisions/db0e71ed0df141db81842dd680145e6d/renditions/f21dbf5de1b059aed69eb6695093ea8f",
		},
		catalog_id = "dc513417b7374775a2c071b679c6959b",
		person = {
			full_name = "St2 Immisch",
			id = "b2d1cad69bf1f9c840e3b90cf6dfe7b5",
			thumbnail_url = "https://photos-stage.adobe.io/v2/avatars/b2d1cad69bf1f9c840e3b90cf6dfe7b5/20201203143017/small",
		},
		primaryImage = "asset",
		primaryImageUI = "asset",
		secondaryImage = "person",
		secondaryImageUI = "person",
		space_id = "b44deae353c44887bbc1153b6317204f",
		subtext = "{full_period_phrase} in {album/name}",
		subtype = "asset-added",
		targetDestination = "album",
		text = "{person/full_name} added a photo.",
		type = "com.adobe.lightroom",
		unique_count = 1,
		unique_count_field = "asset",
	}

	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications' )
			:respond( '200', KSJsonEncoder.prettyPrint( {
				notifications = {
					notification = {
						{
							type = "com.adobe.lightroom",
							state = "NEW",
							timestamp = nowEpoch,
							payload = KSJsonEncoder.prettyPrint( assetAdded1_payload ),
							muted = false,
							[ "notification-id" ] = "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							[ "user-id" ] = "753BB84C58E219220A49403C@AdobeID",
							[ "sub-type" ] = "asset-added",
							[ "app-id" ] = { "LightroomConsumeriOS" },
							[ "created-timestamp" ] = 1580409542930,
							[ "delivery-type" ] = "UNICAST",
							[ "sort-timestamp" ] = nowEpoch
						}
					}
				},
				[ "server-timestamp" ] = 1582585572223,
				paging = {
					next = "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					previous = "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				[ "unread-count" ] = 0
			} ) ) )
		

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, true )
	rxTest:addCloseTarget( notificationSync )
	
	-- include visited in the query
	local notificationObserve = rxTest:observe( 
		notificationSync:notificationDb():query{
			sql = "SELECT notificationId, subtype, time, read, visited FROM notifications ORDER BY time DESC"
		} )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "asset-added", nowIso8601, 0, 0 }
	} )

	logger:info( "Received first asset-added notification" )

	-- mark as read
	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:put( 'https://notify.adobe.io/ans/v1/notifications' )
			:expectRequestData( KSJsonEncoder.prettyPrint( { 
				notifications = {
					notification = {
						{
							[ 'notification-id' ] = '245d24e5-09fe-4175-80c7-ebad6c8f3a6b',
							state = "READ",
							metadata = {
								entry = {
									{
										[ 'key-value' ] = false,
										[ 'key-name' ] = 'visited'
									}
								}
							}
						}
					}
				}
			} ) )
			:respond( '200', [[{
				"notifications": {
					"notification": [
						{
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b"
						}
					]
				}
			}]] ) )


	rxTest:expectSingleValue( notificationSync:setReadNotification( { "245d24e5-09fe-4175-80c7-ebad6c8f3a6b" }), 1 )

	logger:info( "Notification is marked as read" )
	
	-- mark as visited
	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:put( 'https://notify.adobe.io/ans/v1/notifications' )
			:expectRequestData( KSJsonEncoder.prettyPrint( { 
				notifications = {
					notification = {
						{
							[ 'notification-id' ] = '245d24e5-09fe-4175-80c7-ebad6c8f3a6b',
							state = "READ",
							metadata = {
								entry = {
									{
										[ 'key-value' ] = true,
										[ 'key-name' ] = 'visited'
									}
								}
							}
						}
					}
				}
			} ) )
			:respond( '200', [[{
				"notifications": {
					"notification": [
						{
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b"
						}
					]
				}
			}]] ) )


	rxTest:expectSingleValue( notificationSync:setVisitedNotification( { "245d24e5-09fe-4175-80c7-ebad6c8f3a6b" }), 1 )
	
	logger:info( "Notification is marked as visited" )

	-- simulate a re-aggregated 
	local assetAdded2_payload = {
		aggregated_notifications = {
			{
				album = {
					id = "79c2d76d0be643b49bda50af3622a1ea",
					name = "Sammlung Falckenberg",
				},
				asset = {
					id = "dc7a6c2aecf94748aa5c87fda62a8648",
					subtype = "image",
					thumbnail_url = "https://photos-stage.adobe.io/v2/catalogs/dc513417b7374775a2c071b679c6959b/assets/dc7a6c2aecf94748aa5c87fda62a8648/revisions/8f18047c0f5d4cc580da8983e6d6fb28/renditions/bef35d50494c16b4b7d21a362d6ba386",
				},
				catalog_id = "dc513417b7374775a2c071b679c6959b",
				person = {
					full_name = "St2 Immisch",
					id = "b2d1cad69bf1f9c840e3b90cf6dfe7b5",
					thumbnail_url = "https://photos-stage.adobe.io/v2/avatars/b2d1cad69bf1f9c840e3b90cf6dfe7b5/20201203143017/small",
				},
				space_id = "b44deae353c44887bbc1153b6317204f",
				subtype = "asset-added",
				type = "com.adobe.lightroom",
			},
			{
				album = {
					id = "79c2d76d0be643b49bda50af3622a1ea",
					name = "Sammlung Falckenberg",
				},
				asset = {
					id = "a7c8f6b64fa3400f80c19fbb6336747d",
					subtype = "image",
					thumbnail_url = "https://photos-stage.adobe.io/v2/catalogs/dc513417b7374775a2c071b679c6959b/assets/a7c8f6b64fa3400f80c19fbb6336747d/revisions/db0e71ed0df141db81842dd680145e6d/renditions/f21dbf5de1b059aed69eb6695093ea8f",
				},
				catalog_id = "dc513417b7374775a2c071b679c6959b",
				person = {
					full_name = "St2 Immisch",
					id = "b2d1cad69bf1f9c840e3b90cf6dfe7b5",
					thumbnail_url = "https://photos-stage.adobe.io/v2/avatars/b2d1cad69bf1f9c840e3b90cf6dfe7b5/20201203143017/small",
				},
				space_id = "b44deae353c44887bbc1153b6317204f",
				subtype = "asset-added",
				type = "com.adobe.lightroom",
			},
		},
		album = {
			id = "79c2d76d0be643b49bda50af3622a1ea",
			name = "Sammlung Falckenberg",
		},
		asset = {
			id = "dc7a6c2aecf94748aa5c87fda62a8648",
			subtype = "image",
			thumbnail_url = "https://photos-stage.adobe.io/v2/catalogs/dc513417b7374775a2c071b679c6959b/assets/dc7a6c2aecf94748aa5c87fda62a8648/revisions/8f18047c0f5d4cc580da8983e6d6fb28/renditions/bef35d50494c16b4b7d21a362d6ba386",
		},
		catalog_id = "dc513417b7374775a2c071b679c6959b",
		person = {
			full_name = "St2 Immisch",
			id = "b2d1cad69bf1f9c840e3b90cf6dfe7b5",
			thumbnail_url = "https://photos-stage.adobe.io/v2/avatars/b2d1cad69bf1f9c840e3b90cf6dfe7b5/20201203143017/small",
		},
		primaryImage = "asset",
		primaryImageUI = "asset-stack",
		secondaryImage = "person",
		secondaryImageUI = "person",
		space_id = "b44deae353c44887bbc1153b6317204f",
		subtext = "{full_period_phrase} in {album/name}",
		subtype = "asset-added",
		targetDestination = "album",
		text = "{person/full_name} added {unique_count} photos.",
		type = "com.adobe.lightroom",
		unique_count = 2,
		unique_count_field = "asset",
	}

	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications' )
			:respond( '200', KSJsonEncoder.prettyPrint( {
				notifications = {
					notification = {
						{
							type = "com.adobe.lightroom",
							state = "NEW",
							metadata = {
								visited = 'true',
							},
							timestamp = nowEpoch,
							payload = KSJsonEncoder.prettyPrint( assetAdded2_payload ),
							muted = false,
							[ "notification-id" ] = "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							[ "user-id" ] = "753BB84C58E219220A49403C@AdobeID",
							[ "sub-type" ] = "asset-added",
							[ "app-id" ] = { "LightroomConsumeriOS" },
							[ "created-timestamp" ] = 1580409542930,
							[ "delivery-type" ] = "UNICAST",
							[ "sort-timestamp" ] = nowEpoch
						}
					}
				},
				[ "server-timestamp" ] = 1582585572223,
				paging = {
					next = "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					previous = "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				[ "unread-count" ] = 0
			} ) ) )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	logger:info( "Notification has bee re-aggregated" )
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "asset-added", nowIso8601, 0 --[[ read --]] , 0 --[[ visited --]] }
	} )

	-- mark as visited
	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:put( 'https://notify.adobe.io/ans/v1/notifications' )
			:expectRequestData( KSJsonEncoder.prettyPrint( { 
				notifications = {
					notification = {
						{
							[ 'notification-id' ] = '245d24e5-09fe-4175-80c7-ebad6c8f3a6b',
							state = "NEW",
							metadata = {
								entry = {
									{
										[ 'key-value' ] = false,
										[ 'key-name' ] = 'visited'
									}
								}
							}
						}
					}
				}
			} ) )
			:respond( '200', [[{
				"notifications": {
					"notification": [
						{
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b"
						}
					]
				}
			}]] ) )

	logger:info( "Verify notification is set to unvisited" )

	-- make sure the update is sent to ANS
	rxTest:expectSingleValue( notificationSync:updateNotificationStateOnce()
		:takeWhile( function( notifications )
			return next( notifications )
		end ), 
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b" } )

end

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

function WFCatNotificationSyncTest.rxTestAllowListNotificationSubtype( rxTest )

	rxTest:setTimeLimit( 10 )

	WFCapabilities.setCapability( 'WFCatNotification.subtypeAllowList', 'comment,favorite' )
	rxTest:finally( function()
		-- be sure to reset it after the test
		WFCapabilities.setCapability( 'WFCatNotification.subtypeAllowList', nil )
	end )
		
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						},
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"favorite\"}",
							"muted": false,
							"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "favorite",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						},
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"asset-added\"}",
							"muted": false,
							"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "asset-added",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch, twoHoursAgoEpoch, twoHoursAgoEpoch, fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	-- Verify the data appears in the db
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", nowIso8601, 0 },
		{ "024b78da-4631-46a6-a613-e54229e56a7e", "favorite", twoHoursAgoIso8601, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "asset-added", fiveHoursAgoIso8601, 0 },
	} )

	-- Verify that asset-added is filtered
	rxTest:expectSingleValue( notificationSync:notificationList()
		:take( 1 )
		:map( function( notifications )
		
			local results = {}
			
			for i, notification in ipairs( notifications ) do
				results[ i ] = notification.notificationId
			end
		
			return results
		
		end ),
		{
			"245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
			"024b78da-4631-46a6-a613-e54229e56a7e"
		} )

	-- The asset-added should also not appear in notificationUnreadCount
	rxTest:expectSingleValue( notificationSync:notificationUnreadCount()
		:take( 1 ),
		2 )

end

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

function WFCatNotificationSyncTest.rxTestNotificationListRefreshDueToLocaleChange( rxTest )

	rxTest:setTimeLimit( 20 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711"
				},
				"unread-count": 1
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580165557711",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580034924750"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580034924750",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "favorite",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580034924750"
				},
				"unread-count": 1
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1581569405324",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "33e4aa57-547d-4953-9c33-08856a7bcd27",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1587569405323,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1581579405324"
				},
				"unread-count": 2
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		
		-- locale changes to zh_TW
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&to=1580165557711"
				},
				"unread-count": 1
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&to=1580165557711",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&from=1580165557711",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&to=1580034924750"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&to=1580034924750",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "a426782c-a96b-4b13-99ef-b77883d16313",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "favorite",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=zh_TW&from=1580034924750"
				},
				"unread-count": 1
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=zh_TW&from=1581569405324",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "33e4aa57-547d-4953-9c33-08856a7bcd27",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1587569405323,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=zh_TW&from=1581579405324"
				},
				"unread-count": 2
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
		
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, "en_US", catalogSpec )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read, visited FROM notifications ORDER BY time DESC"
	} )
	local notificationUnreadCountObserve = rxTest:observe( 
		WFCatNotification.notificationUnreadCount( notificationSync:notificationDb(), notificationSync:clientSubtypeAllowList() ) )
	
	notificationObserve:expectData( {} )
	notificationUnreadCountObserve:expectData( 0 )
	
	rxTest:waitFor( notificationSync:startNotificationSync{ checkInterval = 1, debug_updateCount = 4 } )
	
	notificationObserve:expectDataWithinTime( 15, {
		{ "33e4aa57-547d-4953-9c33-08856a7bcd27", "comment", nowIso8601, 0, 0 },
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 0, 0 },
		{ "024b78da-4631-46a6-a613-e54229e56a7e", "comment", twoHoursAgoIso8601, 0, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "favorite", fiveHoursAgoIso8601, 0, 0 }
	} )

	rxTest:observe( WFCatNotification.notificationUnreadCount( notificationSync:notificationDb(), notificationSync:clientSubtypeAllowList() ) )
		:expectDataWithinTime( 5, 4 )
		
	-- Monitor locale changes. Close WFCatNotificationSync and restart
	
	notificationSync:close()
	
	notificationSync = WFCatNotificationSync( wfCatalog, "zh_TW", catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	notificationObserve:expectDataWithinTime( 5, {} )
	
	notificationUnreadCountObserve:expectDataWithinTime( 5, 0 )
		
	rxTest:waitFor( notificationSync:startNotificationSync{ checkInterval = 1, debug_updateCount = 4 } )
	
	notificationObserve:expectDataWithinTime( 15, {
		{ "33e4aa57-547d-4953-9c33-08856a7bcd27", "comment", nowIso8601, 0, 0 },
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 0, 0 },
		{ "024b78da-4631-46a6-a613-e54229e56a7e", "comment", twoHoursAgoIso8601, 0, 0 },
		{ "a426782c-a96b-4b13-99ef-b77883d16313", "favorite", fiveHoursAgoIso8601, 0, 0 }
	} )

	notificationUnreadCountObserve:expectDataWithinTime( 5, 4 )
		
end

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

function WFCatNotificationSyncTest.rxTestNotificationConfiguration( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0",
			
			response = [[{
				"notification-configuration": {
			        "config-params": {
			            "config-param": [
			                {
			                    "value": [
			                        "300"
			                    ],
			                    "key": "pollingIntervalMax"
			                },
			                {
			                    "value": [
			                        "60"
			                    ],
			                    "key": "pollingBackoff"
			                },
			                {
			                    "value": [
			                        "60"
			                    ],
			                    "key": "pollingIntervalInit"
			                }
			            ]
			        },
			        "version": 1,
			        "complete-version": "1.0"
			    },
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": 1581569405323,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": 1581569405323
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?config_version=0&to=1580165557711"
				},
				"unread-count": 1
			}]],

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions

		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT key, value FROM notificationInfo WHERE key LIKE ? ORDER BY key ASC", 
		"polling%" 
	} )
	
	local url = "https://notify.adobe.io/ans/v1/notifications?config_version=0"
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce( url )
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?config_version=1&to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?config_version=1&from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "pollingBackoff", 60 },
		{ "pollingIntervalInit", 60 },
		{ "pollingIntervalMax", 300 }
	} )
	
end 
--------------------------------------------------------------------------------

function WFCatNotificationSyncTest.rxTestNotificationPollingStrategy( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedResponses = {}

	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	rxTest:waitFor( notificationSync:notificationDb():bulkInsert( "notificationInfo", {
		{ "pollingIntervalInit", 60 },
		{ "pollingIntervalMax", 120 },
		{ "pollingBackoff", 30 },
	} ) )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_getPollingInterval( false ), 60 )
	rxTest:expectSingleValue( notificationSync:DEBUG_getPollingInterval( false ), 90 )
	rxTest:expectSingleValue( notificationSync:DEBUG_getPollingInterval( false ), 120 )
	rxTest:expectSingleValue( notificationSync:DEBUG_getPollingInterval( false ), 120 )
	
	-- startOver 
	rxTest:expectSingleValue( notificationSync:DEBUG_getPollingInterval( true ), 60 )
	rxTest:expectSingleValue( notificationSync:DEBUG_getPollingInterval( false ), 90 )
	
end

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

function WFCatNotificationSyncTest.rxTestNotificationListWaitUntilSyncCycleComplete( rxTest )

	rxTest:setTimeLimit( 10 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US",
			
			response = string.format([[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = string.format([[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580165557711"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1581569405324",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "33e4aa57-547d-4953-9c33-08856a7bcd27",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1587569405323,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1587569405323",
					"previous": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&to=1587569405323"
				},
				"unread-count": 2
			}]], fiveHoursAgoEpoch, fiveHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&to=1587569405323",
			
			response = string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "8475sdk3-547d-4953-9c33-08856a7bcd27",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1586569405323,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?config_version=0&locale=en_US&from=1586569405323"
				},
				"unread-count": 2
			}]], tenHoursAgoEpoch, tenHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		}
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, nil, true )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationListObserve = rxTest:observe( notificationSync:notificationList( true )
		:map( function( notificationList, syncCycleComplete )
			return #notificationList, syncCycleComplete
		end )
	)
	
	notificationListObserve:expectData( 0, true )
	
	rxTest:sleep( 1 )
	
	rxTest:waitFor( notificationSync:startNotificationSync{ checkInterval = 1, debug_updateCount = 4 , debug_previousPageDelay = 1, debug_noANSStatusUpdate = true } )
	
	notificationListObserve:expectData( 1, false )
	notificationListObserve:expectData( 2, true )
	notificationListObserve:expectData( 4, true )
	
end

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

function WFCatNotificationSyncTest.rxTestNotificationKillSwitch( rxTest )

	rxTest:setTimeLimit( 30 )
	
	local catalogSpec = createCatalogSpecForTest( rxTest )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	catalogSpec:expectHttpRequest( 
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications' )
			:respond( '200', string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"notification-configuration": {
					"config-params": {
						"config-param": [
							{
								"value": ["1"],
								"key": "lrNotificationVersion"
							}
						]
					}
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ) ) )


	catalogSpec:expectHttpRequest(
		WFMockHttpRequest()
			:put( 'https://notify.adobe.io/ans/v1/notifications' )
			:expectRequestData( KSJsonEncoder.prettyPrint( { 
				notifications = {
					notification = {
						{
							[ 'notification-id' ] = '245d24e5-09fe-4175-80c7-ebad6c8f3a6b',
							state = "READ",
							metadata = {
								entry = {
									{
										[ 'key-value' ] = false,
										[ 'key-name' ] = 'visited'
									}
								}
							}
						}
					}
				}
			} )
			)
			:respond( '200', [[{
				"notifications": {
					"notification": [
						{
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b"
						}
					]
				}
			}]] ) )
			
	catalogSpec:expectHttpRequest( 
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications')
			:respond( '200', string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "READ",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "comment",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"notification-configuration": {
					"config-params": {
						"config-param": [
							{
								"value": ["1"],
								"key": "lrNotificationVersion"
							}
						]
					}
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 0
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ) ) )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationObserve = rxTest:observe( notificationSync:notificationDb():query{
		sql = "SELECT notificationId, subtype, time, read FROM notifications ORDER BY time DESC"
	} )
	rxTest:finally( function()
		notificationObserve:dispose()
	end )
		
	local killSwitchObserve = rxTest:observe( notificationSync:notificationKillSwitch() )
	
	-- This would sync down lrNotificationVersion=1, which matches current version
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	killSwitchObserve:expectData( false )
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 0 }
	} )
	
	rxTest:waitFor( notificationSync:setReadNotification( { "245d24e5-09fe-4175-80c7-ebad6c8f3a6b" } ) )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 1 }
	} )


	-- Sync down a new notification with lrNotificationVersion=2, this would set kill switch
	
	catalogSpec:expectHttpRequest( 
		WFMockHttpRequest()
			:get( 'https://notify.adobe.io/ans/v1/notifications')
			:respond( '200', string.format( [[{
				"notifications": {
					"notification": [
						{
							"type": "com.adobe.lightroom",
							"state": "NEW",
							"timestamp": %s,
							"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
							"muted": false,
							"notification-id": "8475sdk3-547d-4953-9c33-08856a7bcd27",
							"user-id": "753BB84C58E219220A49403C@AdobeID",
							"sub-type": "favorite",
							"app-id": ["LightroomConsumeriOS"],
							"created-timestamp": 1580409542930,
							"delivery-type": "UNICAST",
							"sort-timestamp": %s
						}
					]
				},
				"notification-configuration": {
					"config-params": {
						"config-param": [
							{
								"value": ["2"],
								"key": "lrNotificationVersion"
							}
						]
					}
				},
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v1/notifications?from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				},
				"unread-count": 1
			}]], nowEpoch, nowEpoch ) ) )	
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce()
		:map( function( previousLink, nextLink ) 
			return previousLink == "https://notify.adobe.io/ans/v1/notifications?to=1580165557711"
				and nextLink == "https://notify.adobe.io/ans/v1/notifications?from=1581569405324"
		end ),
		true
	)
	
	killSwitchObserve:expectData( true )
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "8475sdk3-547d-4953-9c33-08856a7bcd27", "favorite", nowIso8601, 0 },
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 1 }
	} )
		
	-- This startNotificationSync should not sync anything, since kill switch is on
	rxTest:waitFor( notificationSync:startNotificationSync{ checkInterval = 1, debug_updateCount = 2 } )
	
	notificationObserve:expectDataWithinTime( 5, {
		{ "8475sdk3-547d-4953-9c33-08856a7bcd27", "favorite", nowIso8601, 0 },
		{ "245d24e5-09fe-4175-80c7-ebad6c8f3a6b", "comment", tenSecondsAgoIso8601, 1 }
	} )
	

	rxTest:expectSingleValue(
		notificationSync:notificationList( true ):take( 1 ),
		{
			{
				notificationId = "8475sdk3-547d-4953-9c33-08856a7bcd27",
				read = false,
				visited = false,
				subtype = "favorite",
				time = nowIso8601,
				type = "com.adobe.lightroom",
			},
			{
				notificationId = "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
				read = true,
				visited = false,
				subtype = "comment",
				time = tenSecondsAgoIso8601,
				type = "com.adobe.lightroom",
			},
		}
	)
	
end

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

-- @mark testNightly

--------------------------------------------------------------------------------
-- notificationList only get first row. In case multiple integration tests run at the same time
--------------------------------------------------------------------------------

local function albumAssetsForFilteredAssetsQueryFn( catalog, assetIds )

	if WFPlatformInfo.isMobile() then

		return catalog:indexStore():queryOnce {
					sql = [[SELECT album_asset.docId, album_asset.assetId FROM CARRAY( ?, ?, 'char*' ) as ai JOIN album_asset ON 
								ai.value = album_asset.assetId]],
					assetIds,
					#assetIds,
			}
			
	else
	
		return catalog:indexStore():queryOnce {
				sql = [[SELECT album_asset_v2.docId, album_asset_v2.assetId FROM CARRAY( ?, ?, 'char*' ) as ai JOIN album_asset_v2 ON 
							ai.value = album_asset_v2.assetId]],
				assetIds,
				#assetIds,
			}
	
	end
		
end

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

local function stripWobblyBits( rxTest, notifications )

	local result = {}

	for i = 1, #notifications do
				
		local notification = notifications[ i ]
	
		notification.notificationId = "notificationId-" .. tostring( i )
		notification.time = "time"
		
		if derefOpt( notification, "asset", "thumbnail_url" ) then
			notification.asset.thumbnail_url = "asset.thumbnail_url"
		end 
		
		if derefOpt( notification, "person", "full_name" ) then
			notification.person.full_name = "person.full_name"
		end 
		
		if derefOpt( notification, "person", "thumbnail_url" ) then
			notification.person.thumbnail_url = "person.thumbnail_url"
		end 

		notification.primaryImageUI = nil
		notification.secondaryImageUI = nil			
		
		if notification.ansTemplateVersion then
			rxTest:logf( 'template version: %s', notification.ansTemplateVersion )
		end
		
		notification.ansTemplateVersion = nil

		-- strip aggregated bits
		notification.aggregated_notifications = nil
		notification.text_count = nil
		notification.unique_count = nil
		notification.unique_count_field = nil

		result[ i ] = notifications[ i ]

	end
	
	return result

end

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

function WFCatNotificationSyncTest.rxTestBasicCommentNotification_testNightly( rxTest )

	rxTest:setTimeLimit( 240 )
	
	local ownerWFCatalog, ownerCatalogSpec, ownerCatalogTestSession,
			memberWFCatalog, memberCatalogSpec, _,
			spaceId, _, albumId, assetId = WFCatOzTestSession.setupSpaceOzTest( rxTest, 'viewer', 'notifications' )
			
	local spaceFeedsManagerArgs = {
		taskDelays = {
			progressiveBackoff = false,
			noWorkDelay = 0.5,
			maxNoWorkDelay = 1,
			errorDelay = 1,
			maxDelay = 1,
		},
		isPrefetchEnabled = true,
		albumAssetsForFilteredAssetsQueryFn = albumAssetsForFilteredAssetsQueryFn,
		testMode = true,
	}
	
	local spaceFeedsManager = WFCatSpaceFeedsManager( memberWFCatalog, spaceFeedsManagerArgs )
	rxTest:addCloseTarget( spaceFeedsManager )
	
	local downloadObserver = rxTest:observe( spaceFeedsManager:isDownloadingMetadata() )
	
	spaceFeedsManager:reportCatalogInitialSyncIsComplete()
	spaceFeedsManager:startFetchingUpdates()
	spaceFeedsManager:startSendingUpdates()
	
	downloadObserver:expectDataWithinTime( 60, true )
	downloadObserver:expectDataWithinTime( 10, false )
	
	downloadObserver:dispose()
	
	-- Add comment from member
	
	local timeInUnixEpochMillisecond = math.floor( KSDate.timeIntervalSinceUnixEpoch() * 1000 )
	
	local commentId = KSIdGenerator.uuidInLowercaseHex()
	
	local commentDocPutUrl = string.format( '/v2/spaces/%s/assets/%s/comments/%s',
										  spaceId,
										  assetId,
										  commentId)

	local commentDocContent = KSJsonEncoder.encodeOzUploadJson {
		author_name = "John Smith",
		payload = {
			body = "Nice picture!",
			
		},
	}

	local commentDocPutArgs = {
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
			{ header = 'Accept', value = 'application/json' },
		},
	}

	rxTest:expectOnlyCompleted(
		memberCatalogSpec:put( commentDocPutUrl, commentDocContent, commentDocPutArgs )
			:filter( function() return false end ) )
	
	rxTest:sleep( 80 )
	
	-- Switch to owner catalog
	
	rxTest:expectOnlyCompleted( ownerCatalogTestSession:reloadAccessAndRefreshTokens() )
	
	-- Query notification once to check if comment notification is added correctly
	
	local notificationSync = WFCatNotificationSync( ownerWFCatalog, nil, ownerCatalogSpec )
	rxTest:addCloseTarget( notificationSync )
					
	local url = "https://notify-stage.adobe.io/ans/v1/notifications?from=" .. tostring( timeInUnixEpochMillisecond )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce( url )
		:map( function( previousLink, nextLink ) 
			return type( previousLink ) == 'string'
				and type( nextLink ) == 'string'
		end ),
		true
	)
	
	rxTest:expectSingleValueWithinTimeLimits( 0, 10,
		WFCatNotification.notificationList( notificationSync:notificationDb() )
			:take( 1 )
			:map( KSFunctionUtils.partial( stripWobblyBits, rxTest ) ),
			{
				{
					notificationId = "notificationId-1",
					subtype = "comment",
					time = "time",
					read = false,
					visited = false,
					
					type = "com.adobe.lightroom",
					catalog_id = ownerCatalogSpec:catalogId(),
					space_id = spaceId,
					
					targetDestination = "asset",
					primaryImage = "asset",
					secondaryImage = "person",
					text = "{person/full_name} commented on your photo.",
					subtext = "{full_period_phrase} in {album/name}",

					asset = {
						id = assetId,
						thumbnail_url = "asset.thumbnail_url",
						subtype = "image"
					},
					album = {
						id = albumId,
						name = "WFCatOzTestSession space album"
					},
					person = {
						id = memberCatalogSpec:ozUserId(),
						full_name = "person.full_name",
						thumbnail_url = "person.thumbnail_url"
					},
					comment = {
						id = commentId,
						body = "Nice picture!"
					}
				} 
			})
	
end

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

function WFCatNotificationSyncTest.rxTestBasicFavoriteNotification_testNightly( rxTest )

	rxTest:setTimeLimit( 240 )
	
	local ownerWFCatalog, ownerCatalogSpec, ownerCatalogTestSession,
			memberWFCatalog, memberCatalogSpec, _,
			spaceId, _, albumId, assetId = WFCatOzTestSession.setupSpaceOzTest( rxTest, 'viewer', 'notifications' )
			
	local spaceFeedsManagerArgs = {
		taskDelays = {
			progressiveBackoff = false,
			noWorkDelay = 0.5,
			maxNoWorkDelay = 1,
			errorDelay = 1,
			maxDelay = 1,
		},
		isPrefetchEnabled = true,
		albumAssetsForFilteredAssetsQueryFn = albumAssetsForFilteredAssetsQueryFn,
		testMode = true,
	}
	
	local spaceFeedsManager = WFCatSpaceFeedsManager( memberWFCatalog, spaceFeedsManagerArgs )
	rxTest:addCloseTarget( spaceFeedsManager )
	
	local downloadObserver = rxTest:observe( spaceFeedsManager:isDownloadingMetadata() )
	
	logger:info( "starting initial sync for member" )
	
	spaceFeedsManager:reportCatalogInitialSyncIsComplete()
	spaceFeedsManager:startFetchingUpdates()
	spaceFeedsManager:startSendingUpdates()
	
	downloadObserver:expectDataWithinTime( 60, true )
	downloadObserver:expectDataWithinTime( 10, false )
	
	downloadObserver:dispose()
	
	-- Add favorite from member
	
	local timeInUnixEpochMillisecond = math.floor( KSDate.timeIntervalSinceUnixEpoch() * 1000 )
	
	local favoriteId = KSIdGenerator.uuidInLowercaseHex()
	
	local favoriteDocPutUrl = string.format( '/v2/spaces/%s/assets/%s/favorites/%s',
										  spaceId,
										  assetId,
										  favoriteId)

	local favoriteDocContent = KSJsonEncoder.encodeOzUploadJson( {} )

	local favoriteDocPutArgs = {
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
			{ header = 'Accept', value = 'application/json' },
		},
	}

	rxTest:expectOnlyCompleted(
		memberCatalogSpec:put( favoriteDocPutUrl, favoriteDocContent, favoriteDocPutArgs )
			:filter( function() return false end ) )
	
	local sleep = 80
	
	logger:infof( "added favourite to member catalog, will sleep %d seconds", sleep )
	
	rxTest:sleep( sleep )
	
	-- Switch to owner catalog
	
	rxTest:expectOnlyCompleted( ownerCatalogTestSession:reloadAccessAndRefreshTokens() )
	
	logger:info( "switched to owner catalog, waiting for notifications" )
	
	-- Query notification once to check if favorite notification is added correctly
	
	local notificationSync = WFCatNotificationSync( ownerWFCatalog, nil, ownerCatalogSpec )
	rxTest:addCloseTarget( notificationSync )
		
	local url = "https://notify-stage.adobe.io/ans/v1/notifications?from=" .. tostring( timeInUnixEpochMillisecond )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce( url )
		:map( function( previousLink, nextLink ) 
			return type( previousLink ) == 'string'
				and type( nextLink ) == 'string'
		end ),
		true
	)
	
	rxTest:expectSingleValueWithinTimeLimits(0, 10, 
		WFCatNotification.notificationList( notificationSync:notificationDb() )
			:take( 1 )
			:map( KSFunctionUtils.partial( stripWobblyBits, rxTest ) ),
		{
			{
				notificationId = "notificationId-1",
				subtype = "favorite",
				time = "time",
				read = false,
				visited = false,
				
				type = "com.adobe.lightroom",
				catalog_id = ownerCatalogSpec:catalogId(),
				space_id = spaceId,
				
				targetDestination = "asset",
				primaryImage = "asset",
				secondaryImage = "person",
				text = "{person/full_name} liked your photo.",
				subtext = "{full_period_phrase} in {album/name}",

				asset = {
					id = assetId,
					thumbnail_url = "asset.thumbnail_url",
					subtype = "image"
				},
				album = {
					id = albumId,
					name = "WFCatOzTestSession space album"
				},
				person = {
					id = memberCatalogSpec:ozUserId(),
					full_name = "person.full_name",
					thumbnail_url = "person.thumbnail_url"
				},
				favorite = {
					id = favoriteId
				}
			} 
		} )
	
	logger:info( "received notification successfully" )

end

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

function WFCatNotificationSyncTest.rxTestSetVisitedNotification_testNightly( rxTest )

	rxTest:setTimeLimit( 240 )
	
	local ownerWFCatalog, ownerCatalogSpec, ownerCatalogTestSession,
			memberWFCatalog, memberCatalogSpec, _,
			spaceId, _, albumId, assetId = WFCatOzTestSession.setupSpaceOzTest( rxTest, 'viewer', 'notifications' )
			
	local spaceFeedsManagerArgs = {
		taskDelays = {
			progressiveBackoff = false,
			noWorkDelay = 0.5,
			maxNoWorkDelay = 1,
			errorDelay = 1,
			maxDelay = 1,
		},
		isPrefetchEnabled = true,
		albumAssetsForFilteredAssetsQueryFn = albumAssetsForFilteredAssetsQueryFn,
		testMode = true,
	}
	
	local spaceFeedsManager = WFCatSpaceFeedsManager( memberWFCatalog, spaceFeedsManagerArgs )
	rxTest:addCloseTarget( spaceFeedsManager )
	
	local downloadObserver = rxTest:observe( spaceFeedsManager:isDownloadingMetadata() )
	
	spaceFeedsManager:reportCatalogInitialSyncIsComplete()
	spaceFeedsManager:startFetchingUpdates()
	spaceFeedsManager:startSendingUpdates()
	
	downloadObserver:expectDataWithinTime( 60, true )
	downloadObserver:expectDataWithinTime( 10, false )
	
	downloadObserver:dispose()
	
	-- Add favorite from member
	
	local timeInUnixEpochMillisecond = math.floor( KSDate.timeIntervalSinceUnixEpoch() * 1000 )
	
	local favoriteId = KSIdGenerator.uuidInLowercaseHex()
	
	local favoriteDocPutUrl = string.format( '/v2/spaces/%s/assets/%s/favorites/%s',
										  spaceId,
										  assetId,
										  favoriteId)

	local favoriteDocContent = KSJsonEncoder.encodeOzUploadJson( {} )

	local favoriteDocPutArgs = {
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
			{ header = 'Accept', value = 'application/json' },
		},
	}

	rxTest:expectOnlyCompleted(
		memberCatalogSpec:put( favoriteDocPutUrl, favoriteDocContent, favoriteDocPutArgs )
			:filter( function() return false end ) )
	
	rxTest:sleep( 80 )
	
	-- Switch to owner catalog
	
	rxTest:expectOnlyCompleted( ownerCatalogTestSession:reloadAccessAndRefreshTokens() )
	
	-- Query notification once to check if favorite notification is added correctly
	
	local notificationSync = WFCatNotificationSync( ownerWFCatalog, nil, ownerCatalogSpec )
	rxTest:addCloseTarget( notificationSync )
	
	local notificationId
	
	local function transformNotifications( notifications )

		notificationId = derefOpt( notifications, 1, 'notificationId' )

		return stripWobblyBits( rxTest, notifications )

	end
		
	local url = "https://notify-stage.adobe.io/ans/v1/notifications?from=" .. tostring( timeInUnixEpochMillisecond )
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce( url )
		:map( function( previousLink, nextLink ) 
			return type( previousLink ) == 'string'
				and type( nextLink ) == 'string'
		end ),
		true
	)
	
	rxTest:expectSingleValueWithinTimeLimits( 0, 10, 
		WFCatNotification.notificationList( notificationSync:notificationDb() )
			:take( 1 )
			:map( transformNotifications ),
		{
			{
				notificationId = "notificationId-1",
				subtype = "favorite",
				time = "time",
				read = false,
				visited = false,
				
				type = "com.adobe.lightroom",
				catalog_id = ownerCatalogSpec:catalogId(),
				space_id = spaceId,
				
				targetDestination = "asset",
				primaryImage = "asset",
				secondaryImage = "person",
				text = "{person/full_name} liked your photo.",
				subtext = "{full_period_phrase} in {album/name}",

				asset = {
					id = assetId,
					thumbnail_url = "asset.thumbnail_url",
					subtype = "image"
				},
				album = {
					id = albumId,
					name = "WFCatOzTestSession space album"
				},
				person = {
					id = memberCatalogSpec:ozUserId(),
					full_name = "person.full_name",
					thumbnail_url = "person.thumbnail_url"
				},
				favorite = {
					id = favoriteId
				}
			} 
		} )
	
	-- set notification as read & visited
	
	rxTest:expectEqual( notificationId ~= nil, true )
	rxTest:waitFor( notificationSync:setReadNotification( { notificationId } ) )
	rxTest:waitFor( notificationSync:setVisitedNotification( { notificationId } ) )
		
	rxTest:sleep( 1 )
	
	-- query notification again to see if visited is set to true
	
	rxTest:expectSingleValue( notificationSync:DEBUG_queryNotificationOnce( url )
		:map( function( previousLink, nextLink ) 
			return type( previousLink ) == 'string'
				and type( nextLink ) == 'string'
		end ),
		true
	)
	
	rxTest:expectSingleValueWithinTimeLimits( 0, 10, 
		WFCatNotification.notificationList( notificationSync:notificationDb() )
			:take( 1 )
			:map( transformNotifications ),
		{
			{
				notificationId = "notificationId-1",
				subtype = "favorite",
				time = "time",
				read = true,
				visited = true,
				
				type = "com.adobe.lightroom",
				catalog_id = ownerCatalogSpec:catalogId(),
				space_id = spaceId,
				
				targetDestination = "asset",
				primaryImage = "asset",
				secondaryImage = "person",
				text = "{person/full_name} liked your photo.",
				subtext = "{full_period_phrase} in {album/name}",

				asset = {
					id = assetId,
					thumbnail_url = "asset.thumbnail_url",
					subtype = "image"
				},
				album = {
					id = albumId,
					name = "WFCatOzTestSession space album"
				},
				person = {
					id = memberCatalogSpec:ozUserId(),
					full_name = "person.full_name",
					thumbnail_url = "person.thumbnail_url"
				},
				favorite = {
					id = favoriteId
				}
			} 
		})
	
end

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

function WFCatNotificationSyncTest.rxTestNotificationSyncErrorResponse_testNightly( rxTest )

	rxTest:setTimeLimit( 200 )
	
	local expectedResponses = {
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "245d24e5-09fe-4175-80c7-ebad6c8f3a6b",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1581569405324",
					"previous": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711"
				},
				"unread-count": 1
			}]], tenSecondsAgoEpoch, tenSecondsAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = "",

			headers = {
				status_code = '429',
			},

			options = notificationRequestOptions
		},
		{
			url = "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&to=1580165557711",
			
			response = string.format( [[{
				"notifications": [
					{
						"type": "com.adobe.lightroom",
						"state": "NEW",
						"timestamp": %s,
						"payload": "{\"type\": \"com.adobe.lightroom\", \"subtype\": \"comment\"}",
						"muted": false,
						"notification-id": "024b78da-4631-46a6-a613-e54229e56a7e",
						"user-id": "753BB84C58E219220A49403C@AdobeID",
						"sub-type": "comment",
						"app-id": ["LightroomConsumeriOS"],
						"created-timestamp": 1580409542930,
						"delivery-type": "UNICAST",
						"sort-timestamp": %s
					}
				],
				"server-timestamp": 1582585572223,
				"paging": {
					"next": "https://notify.adobe.io/ans/v2/notifications/timeline?config_version=0&locale=en_US&from=1580165557711"
				},
				"unread-count": 1
			}]], twoHoursAgoEpoch, twoHoursAgoEpoch ),

			headers = {
				status_code = '200',
			},

			options = notificationRequestOptions
		},
	}
	
	local catalogSpec = createCatalogSpecForTest( rxTest, expectedResponses )
	local wfCatalog = createWFCatalogForTestWithSpec( rxTest, catalogSpec )

	local notificationSync = WFCatNotificationSync( wfCatalog, nil, catalogSpec, true )
	rxTest:addCloseTarget( notificationSync )

	local syncInterval = rxTest:observe( notificationSync:startNotificationSync() )

	syncInterval:expectData( 0 )
	syncInterval:expectData( 60 )
	syncInterval:expectData( 120 )
	syncInterval:expectData( 60 )
	
end

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

return WFRxTestCase.wrapSuite( WFCatNotificationSyncTest )