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

WFCatBinaryUploaderTestUtils.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2022 Adobe
All Rights Reserved.

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

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

	-- WF.oz_client.managed_catalog
local WFCatAssetUtils = require 'WFCatAssetUtils'

	-- WF.core.os
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFDeviceInfo = require 'WFDeviceInfo'
local WFFileUtils = require 'WFFileUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.web.utils
local WFURI = require 'WFURI'

	-- WF.web.json
local KSJsonEncoder = require 'KSJsonEncoder'

	-- WF.oz_client.managed_catalog
local WFCatAssetAux = require 'WFCatAssetAux'

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

local kOZBase = "https://photos-stage.adobe.io/v2/"
	
local kOzBasePattern = "https://photos%-stage%.adobe%.io"

local kDirectBasePattern = "https://storage%.adbephotos%-stage%.com"

local MB = 1024 * 1024

local kChunkSize = 5 * MB

local kOZChunkSize = 5242880 -- Oz default chunk size: from chunkedUploader

local kDefaultUserUpdatedEncoded = '2023-05-15T21%3A09%3A57Z'
local kDefaultUserUpdatedUnencoded = '2023-05-15T21:09:57Z'

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

local function lowercaseDataForWindows( receivedData, expectedData )

	if WFPlatformInfo.isWindows then
		-- on windows the build machine is putting is using a lower case c for
		-- the drive, local machines may have upper case and run fine without this.
		receivedData = string.lower( receivedData )
		expectedData = string.lower( expectedData )
	end

	return receivedData, expectedData

end

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

local function readMetadataHeaderValue(readMetadataNone)

	if readMetadataNone then
		return 'none'
	else
		return 'xmp,location'
	end

end

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

-- @mark Aux Mocks

local function generateAssetMainPartsMock(partsRequestUrl, uploadDestinationURL, uploadCompleteUrl, fileSize, contentType, readMetadataNone)

	local assetResponse = KSJsonEncoder.encodeOzUploadJson({
		base = kOZBase,
		part_size = kOZChunkSize,
		file_size = fileSize,
		num_parts = 1,
		expires = "2100-07-25T22:27:41.466149Z",
		parts = {
			{
				part_num        = 1,
				href            = uploadDestinationURL,
				href_expires    = "2100-07-25T22:27:41.466149Z",
				upload_status   = "pending",
				byte_start      = 0,
				byte_end        = fileSize - 1
			}
		},

		links = {
			["rels/complete_upload"] = {
				href = uploadCompleteUrl
			},
			["rels/abort_upload"] = {
				href = "main/parts"
			}
		}
	})

	-- Set up mock for direct upload URL request

	return {
		url = partsRequestUrl,

		response = assetResponse,

		headers = {
			status_code = '200',
		},

		options = {
			requestHeaders = {
				{
					header = 'Accept',
					value = 'application/json',
				},
				{
					header = 'X-Generate-Renditions',
					value = 'all'
				},
				{
					header = 'X-Generate-Proxies',
					value = 'all'
				},
				{
					header = "Content-Type",
					value = contentType,
				},
				{
					header = 'X-Read-Meta',
					value = readMetadataHeaderValue( readMetadataNone ),
				},
			},
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
		}
	}

end

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

local function generateAssetUploadMock(destinationUrl, localFilePath, fileSize, contentType, readMetadataNone)

	-- Set up mock for s3 upload
	return {
			url = destinationUrl,
			data = localFilePath,
			dataTransformation = lowercaseDataForWindows,
			options = {
				requestHeaders = {
					{
						header = 'X-Generate-Renditions',
						value = 'all',
					},
					{
						header = 'Content-Type',
						value = contentType,
					},
					{
						header = 'Accept',
						value = 'application/json',
					},
					{
						header = 'X-Generate-Proxies',
						value = 'all'
					},
					{
						header = 'X-Read-Meta',
						value = readMetadataHeaderValue( readMetadataNone ),
					},
				},
				dataFormat = "filePath",
				requestCost = 2,
				notifyWhenHttpRequestIsInProgress = true,
				withoutAdobeAuthorization = true,
				requestPriority = 3,
				offset = 1,
				length = fileSize,
			},
			headers = {
				status_code = '200',
			},
		}

end

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

local function generateAssetCompleteMock( markCompleteUrl, contentType, readMetadataNone )

	return {
		url = kOZBase .. markCompleteUrl,

		data = "",

		headers = {
			status_code = '202',
		},

		options = {
			requestMethod = "POST",
			requestHeaders = {
				{
					header = 'X-Generate-Renditions',
					value = 'all',
				},
				{
					header = 'Content-Type',
					value = contentType,
				},
				{
					header = 'Accept',
					value = 'application/json',
				},
				{
					header = 'X-Generate-Proxies',
					value = 'all'
				},
				{
					header = 'X-Read-Meta',
					value = readMetadataHeaderValue( readMetadataNone ),
				},
			},
			requestCost = 1,
			requestPriority = 3,
			bypassRateLimiter = true,
		}
	}

end

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

local function generateDirectPartsMock( partsRequestUrl, uploadDestinationURL, uploadCompleteUrl, fileSize, contentType, options )

	options = options or {}

	local partsResponse = {
		base = kOZBase,
		part_size = kOZChunkSize,
		file_size = fileSize,
		num_parts = 1,
		file_status = "pending",
		parts = {
			{
				part_num        = 1,
				href            = uploadDestinationURL,
				href_expires    = "2100-07-25T22:27:41.466149Z",
				upload_status   = "pending",
				byte_start      = 0,
				byte_end        = fileSize - 1
			},
		},
		links = {
			["rels/complete_upload"] = {
				href = uploadCompleteUrl
			}
		}
	}

	if options.fileStatusUploaded then
		partsResponse.file_status = "uploaded"
		partsResponse.parts = nil
		partsResponse.links = nil
	end

	local encodedPartsResponse = KSJsonEncoder.encodeWebSafeJson( partsResponse )

	local requestHeaders = {
		{
			header = 'Accept',
			value = 'application/json',
		},
		{
			header = "Content-Type",
			value = contentType,
		},
	}
	if options.atomicDevelopSettings then
		table.insert( requestHeaders, {
			header = 'Prefer',
			value ='update-atomic',
		} )
	end

	return {
		url = partsRequestUrl,

		response = encodedPartsResponse,

		headers = {
			status_code = '200',
		},

		options = {
			requestHeaders = requestHeaders,
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
			ignoreMockResponse = true,
		},

		urlFilter = function( url )
			local strippedUrl = url:gsub( kDirectBasePattern, "" )
			local strippedUrl2 = strippedUrl:gsub( kOzBasePattern, "" )
			return strippedUrl2
		end,

	}

end

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

local function generateDirectUploadMock( uploadDestinationURL, localFilePath,  fileSize, contentType, options )

	options = options or {}

	local mock =  {

		url = uploadDestinationURL,
		data = localFilePath,
		dataTransformation = lowercaseDataForWindows,
		options = {
			requestHeaders = {
				{
					header = 'Content-Type',
					value = contentType,
				},
				{
					header = 'Accept',
					value = 'application/json',
				},
			},
			dataFormat = "filePath",
			requestCost = 2,
			notifyWhenHttpRequestIsInProgress = true,
			withoutAdobeAuthorization = true,
			requestPriority = 3,
			offset = 1,
			length = kOZChunkSize > fileSize and fileSize or kOZChunkSize,
		},
		headers = {
			status_code = '200',
		}

	}

	if options.atomicDevelopSettings then
		table.insert( mock.options.requestHeaders, {
			header = 'Prefer',
			value ='update-atomic',
		} )
	end

	-- this filter, which simply replaces the actual URL with the expected one,
	-- is necessary for nightly tests that use WFRxHttpClientMock as a spy on
	-- CatalogSpec but for mock tests that allow requests to come in arbitrary
	-- order, it is a real problem. So only enable for nightly tests. (This is
	-- to support WFCatTrashTest.lua)
	if options.forTestNightly then
		mock.urlFilter = function( url ) return uploadDestinationURL end
	end

	return mock

end

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

local function generateDirectCompleteMock( uploadCompleteUrl, contentType, options )

	options = options or {}

	local requestHeaders = {
		{
			header = 'Accept',
			value = 'application/json',
		},
		{
			header = "Content-Type",
			value = contentType,
		},
	}
	if options.atomicDevelopSettings then
		table.insert( requestHeaders, {
			header = 'Prefer',
			value ='update-atomic',
		} )
	end

	return {

		url = uploadCompleteUrl,

		data = "",

		headers = {
			status_code = '202',
		},

		options = {
			requestMethod = "POST",
			requestHeaders = requestHeaders,
			requestCost = 1,
			requestPriority = 3,
			bypassRateLimiter = true,
		},

		urlFilter = function( url )
			local strippedUrl = url:gsub( kDirectBasePattern, "" ):gsub( kOzBasePattern, "" )

			if options.noDevelopUserUpdated then

				-- santize userUpdated value from url since we won't know what it is in this case
				local _, userUpdatedStart = string.find( strippedUrl, 'userUpdated=' )
				if userUpdatedStart then
					return string.sub( strippedUrl, 1, userUpdatedStart ) .. kDefaultUserUpdatedEncoded
						-- parameters are sorted as part of encoding process, so userUpdated is always last
				end

			end

			return strippedUrl
		end,

	}

end

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

local function generateAuxCopyMock( copyUrl, sourceAssetLinkForAux, auxId )

	local data = string.format( '{ "source_href": "%s", "aux_ids": [ "%s" ] }', sourceAssetLinkForAux, auxId )

	return {

		url = copyUrl,
		data = data,
		dataTransformation = lowercaseDataForWindows,
		options = {
			requestHeaders = {
				{
					header = 'Content-Type',
					value = 'application/json',
				},
				{
					header = 'Accept',
					value = 'application/json',
				},
			},
			requestCost = 1,
			notifyWhenHttpRequestIsInProgress = true,
			requestPriority = 3,
		},
		headers = {
			status_code = '200',
		}

	}

end

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

local function generateCreateMasterResponseImp( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, filePath, errorCode, optAdditionalHeaders, optBypassRateLimiter )

	local url = '/v2/'.. catalogsOrSpaces.. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/master'

	local contentType = 'image/jpeg'
	if string.sub(filePath, -4) == '.RAW' then
		contentType = 'application/octet-stream'
	end

	errorCode = errorCode or '201'
	local requestHeaders = {
			{
				header = 'X-Generate-Renditions',
				value = 'all',
			},
			{
				header = 'Content-Type',
				value = contentType,
			},
			{
				header = 'Accept',
				value = 'application/json',
			},
			{
				header = 'X-Generate-Proxies',
				value = 'all',
			},
		}

	for _, requestHeader in ipairs( optAdditionalHeaders or {} ) do
		table.insert( requestHeaders, requestHeader )
	end

	return {
		url = url,
		data = filePath,
		dataTransformation = lowercaseDataForWindows,
		options = {
			dataFormat = "filePath",
			requestCost = 2,
			notifyWhenHttpRequestIsInProgress = true,
			requestPriority = 3,
			requestHeaders = requestHeaders,
			bypassRateLimiter = optBypassRateLimiter
		},

		headers = {
			status_code = errorCode,
		},
	}

end

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

local function generateCreateMasterResponseWithXReadMeta( catalogId, assetId, revisionId, filePath, errorCode )

	local xReadMetaHeader = {
		{
			header = 'X-Read-Meta',
			value = 'xmp,location',
		},
	}

	return generateCreateMasterResponseImp( 'catalogs', catalogId, assetId, revisionId, filePath, errorCode, xReadMetaHeader )

end

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

local function generateCreateMasterResponseForInAppContributeWithXReadMeta( spaceId, assetId, revisionId, filePath, errorCode, optRedactionsHeaderValue, optBypassRateLimiter )

	local optionalHeaders = {
		{
			header = 'X-Read-Meta',
			value = 'xmp,location',
		},
	}

	if optRedactionsHeaderValue then
		table.insert( optionalHeaders, {
			header = 'X-Redact-Meta',
			value = optRedactionsHeaderValue
		} )
	end

	return generateCreateMasterResponseImp( 'spaces', spaceId, assetId, revisionId, filePath, errorCode, optionalHeaders, optBypassRateLimiter )

end

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

local function generateCopyMasterResponseImp( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode, optAdditionalHeaders )

	local url = '/v2/' .. catalogsOrSpaces .. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/master'

	errorCode = errorCode or '202'

	local requestHeaders = {
			{
				header = 'Content-Type',
				value = 'application/json',
			},
			{
				header = 'Accept',
				value = 'application/json',
			},
			{
				header = 'X-Async-Copy',
				value = "true",
			},
		}

	for _, requestHeader in ipairs( optAdditionalHeaders or {} ) do
		table.insert( requestHeaders, requestHeader )
	end

	local response = {
		[ 'Content-Length' ] = 0,
		Location = "",
	}

	local fail
	if errorCode == '404' then
		fail = {
			{
				code = 1000,
				description = "Resource not found",
				errors = {
					revision = {
						"does not exist",
					},
				},
				subtype = "ResourceNotFoundError",
			},
			{
				status_code = errorCode,
				[ 'X-Result-Code' ] = '1000',
			}
		}
	elseif errorCode == '403' then
		fail = {
			{
				code = 1016,
				description = "Requested operation not permitted",
				subtype = "PermissionError",
			},
			{
				status_code = errorCode,
				[ 'X-Result-Code' ] = '1016',
			}
		}
	end


	return {
		url = url,
		data = '{ "file_href": "/v2/catalogs/' .. catalogId .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/master" }',
		options = {
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
			requestHeaders = requestHeaders,
		},

		response = response,
		fail = fail,

		headers = {
			status_code = errorCode,
		},
	}

end

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

local function generateCopyMasterResponse( catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId, errorCode, readMetadataNone )

	local optionalHeaders = {
		{
			header = 'X-Generate-Renditions',
			value = 'all',
		},
		{
			header = 'X-Generate-Proxies',
			value = 'all',
		},
		{
			header = 'X-Read-Meta',
			value = readMetadataHeaderValue( readMetadataNone ),
		},
	}

	return generateCopyMasterResponseImp( 'catalogs', catalogId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode, optionalHeaders )

end

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

local function generateCopyMasterResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode, optRedactionsHeaderValue )

	local optionalHeaders = {
		{
			header = 'X-Generate-Renditions',
			value = 'all',
		},
		{
			header = 'X-Generate-Proxies',
			value = 'all',
		},
		{
			header = 'X-Read-Meta',
			value = 'none',
		},
	}

	if optRedactionsHeaderValue then
		table.insert( optionalHeaders, {
			header = 'X-Redact-Meta',
			value = optRedactionsHeaderValue
		} )
	end

	return generateCopyMasterResponseImp( 'spaces', spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode, optionalHeaders )

end

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

local function generateCopyMasterResponseWithUrlForInAppContribute( spaceId, assetId, revisionId, sourceOriginalLink, readMetadataNone )

	local url = '/v2/spaces/' .. spaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/master'

	local requestHeaders = {
			{
				header = 'Content-Type',
				value = 'application/json',
			},
			{
				header = 'Accept',
				value = 'application/json',
			},
			{
				header = 'X-Async-Copy',
				value = "true",
			},
			{
				header = 'X-Generate-Renditions',
				value = 'all',
			},
			{
				header = 'X-Generate-Proxies',
				value = 'all',
			},
			{
				header = 'X-Read-Meta',
				value = readMetadataHeaderValue( readMetadataNone ),
			},
		}

	local response = {
		[ 'Content-Length' ] = 0,
		Location = "",
	}

	return {
		url = url,
		data = '{ "file_href": "' .. sourceOriginalLink .. '" }',
		options = {
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
			requestHeaders = requestHeaders,
		},

		response = response,

		headers = {
			status_code = '202',
		},
	}


end

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

local function generateCopyMasterResponseForVideo( catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId, specialProxiesRenditions )

	local renditionsValue, proxiesValue
	if specialProxiesRenditions then
		renditionsValue = 'all,1080p709'
		proxiesValue = 'all;1080p709'
	else
		renditionsValue = 'all'
		proxiesValue = 'all'
	end

	local optionalHeaders = {
		{
			header = 'X-Generate-Renditions',
			value = renditionsValue,
		},
		{
			header = 'X-Generate-Proxies',
			value = proxiesValue,
		},
		{
			header = 'X-Read-Meta',
			value = 'xmp,location',
		},
	}

	local errorCode = nil
	return generateCopyMasterResponseImp( 'catalogs', catalogId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode, optionalHeaders )

end


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

local function generateCopyProxyResponseImp( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, proxyId, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId, errorCode, optAdditionalHeaders )

	local url = '/v2/' .. catalogsOrSpaces .. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/proxies/' .. proxyId

	errorCode = errorCode or '202'

	local response = {
		[ 'Content-Length' ] = 0,
		Location = "",
	}

	local fail
	if errorCode == '404' then
		fail = {
			{
				code = 1000,
				description = "Resource not found",
				errors = {
					revision = {
						"does not exist",
					},
				},
				subtype = "ResourceNotFoundError",
			},
			{
				status_code = errorCode,
				[ 'X-Result-Code' ] = '1000',
			}
		}
	end

	local requestHeaders =  {
		{
			header = 'X-Generate-Renditions',
			value = 'all',
		},
		{
			header = 'Content-Type',
			value = 'application/json',
		},
		{
			header = 'Accept',
			value = 'application/json',
		},
		{
			header = 'X-Async-Copy',
			value = "true",
		},
	}

	for _, requestHeader in ipairs( optAdditionalHeaders or {} ) do
		table.insert( requestHeaders, requestHeader )
	end

	return {
		url = url,
		urlFilter = function( str )
			-- filter out the new proxy id that is created but not stored anywhere
			str = str:gsub( '/([^/]*)$', '/' .. proxyId )
			return str
		end,
		data = '{ "file_href": "/v2/catalogs/' .. catalogId .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/proxies/' .. sourceProxyId .. '" }',
		options = {
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
			requestHeaders = requestHeaders,
		},

		response = response,
		fail = fail,

		headers = {
			status_code = errorCode,
		},
	}

end

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

local function generateCopyProxyResponse( catalogId, assetId, revisionId, proxyId, sourceAssetId, sourceRevisionId, sourceProxyId, errorCode )

	local xReadMetaHeader = {
		{
				header = 'X-Read-Meta',
				value = 'none',
		},
	}

	return generateCopyProxyResponseImp( 'catalogs', catalogId, assetId, revisionId, proxyId, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId, errorCode, xReadMetaHeader )
end

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

local function generateCopyProxyResponseForInAppContribute( spaceId, assetId, revisionId, proxyId, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId, errorCode, optRedactionsHeaderValue )

	local optionalHeaders = {
		{
			header = 'X-Read-Meta',
			value = 'none',
		},
	}

	if optRedactionsHeaderValue then
		table.insert( optionalHeaders, {
			header = 'X-Redact-Meta',
			value = optRedactionsHeaderValue
		} )
	end

	return generateCopyProxyResponseImp( 'spaces', spaceId, assetId, revisionId, proxyId, catalogId, sourceAssetId, sourceRevisionId, sourceProxyId, errorCode, optionalHeaders )

end

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

local function generateCreateProxyResponseImp( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, proxyId, filePath, errorCode, optAdditionalHeaders )

	local url = '/v2/' .. catalogsOrSpaces .. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/proxies/' .. proxyId

	errorCode = errorCode or '201'

	local requestHeaders = {
				{
					header = "X-Proxy-Type",
					value = "2560",
				},
				{
					header = "Content-Type",
					value = "image/dng",
				},
				{
					header = "X-Generate-Renditions",
					value = "all",
				},
				{
					header = "X-Read-Meta",
					value = "xmp"
				},
				{
					header = "Accept",
					value = "application/json",
				},
			}

	for _, requestHeader in ipairs( optAdditionalHeaders or {} ) do
		table.insert( requestHeaders, requestHeader )
	end

	return {
		url = url,
		urlFilter = function( str )
			-- filter out the new proxy id that is created but not stored anywhere
			str = str:gsub( '/([^/]*)$', '/' .. proxyId )
			return str
		end,

		data = filePath,
		dataTransformation = lowercaseDataForWindows,
		options = {
			dataFormat = "filePath",
			requestCost = 2,
			notifyWhenHttpRequestIsInProgress = true,
			requestHeaders = requestHeaders,
			requestPriority = 3,
		},
		response = errorCode == '201' and {
			[ 'Content-Length' ] = 0,
			Location = "",
		} or nil,
		headers = {
			status_code = errorCode,
		},
	}

end

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

local function generateCreateProxyResponse( catalogId, assetId, revisionId, proxyId, filePath, optHttpResponseCode )

	return generateCreateProxyResponseImp( 'catalogs', catalogId, assetId, revisionId, proxyId, filePath, optHttpResponseCode )

end

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

local function generateCreateProxyResponseForInAppContribute( spaceId, assetId, revisionId, proxyId, filePath, optHttpResponseCode, optRedactionsHeaderValue )

	local fullRedactionsHeader
	if optRedactionsHeaderValue then
		fullRedactionsHeader = {
			header = 'X-Redact-Meta',
			value = optRedactionsHeaderValue
		}
	end

	return generateCreateProxyResponseImp( 'spaces', spaceId, assetId, revisionId, proxyId, filePath, optHttpResponseCode, { fullRedactionsHeader } )

end

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

local function generateUploadXmpDevelopFileResponseImp( catalogsOrSpaces, assetsOrVersions, catalogOrSpaceId, assetId, revisionId, filePath )

	local url = '/v2/' .. catalogsOrSpaces.. '/' .. catalogOrSpaceId .. '/' .. assetsOrVersions .. '/' .. assetId .. '/revisions/' .. revisionId .. '/xmp/develop'

	return {
		url = url,
		data = filePath,
		options = {
			dataFormat = "filePath",
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 2,
			requestHeaders = {
				{
					header = 'X-Generate-Renditions',
					value = 'all',
				},
				{
					header = 'X-Generate-Proxies',
					value = 'all',
				},
				{
					header = 'Content-Type',
					value = 'application/rdf+xml',
				},
				{
					header = 'Accept',
					value = 'application/json',
				},
			},
			requestPriority = 3,
		},
		response = {
			status_code = '201'
		},
	}

end

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

local function generateUploadXmpDevelopFileResponse( catalogId, assetId, revisionId, filePath )

	return generateUploadXmpDevelopFileResponseImp( 'catalogs', 'assets', catalogId, assetId, revisionId, filePath )

end

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

local function generateUploadXmpDevelopFileResponseForInAppContribute( spaceId, assetId, revisionId, filePath )

	return generateUploadXmpDevelopFileResponseImp( 'spaces', 'assets', spaceId, assetId, revisionId, filePath )

end

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

local function generateUploadXmpDevelopFileResponseForVersion( catalogId, versionId, revisionId, filePath )

	return generateUploadXmpDevelopFileResponseImp( 'catalogs', 'versions', catalogId, versionId, revisionId, filePath )

end

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

local function generateUploadXmpDevelop404ResponseForVersion( catalogOrSpaceId, versionId, revisionId, filePath )

	local url = '/v2/catalogs/' .. catalogOrSpaceId .. '/versions/' .. versionId .. '/revisions/' .. revisionId .. '/xmp/develop'

	local fail = {
		{
			code = 1000,
			description = "Resource not found",
			errors = {
				version = {
					"does not exist",
				},
			},
			subtype = "ResourceNotFoundError",
		},
		{
			status_code = '404',
			[ 'X-Result-Code' ] = '1000',
		}
	}

	return {
		url = url,
		data = filePath,
		fail = fail,
		options = {
			dataFormat = "filePath",
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 2,
			requestHeaders = {
				{
					header = 'X-Generate-Renditions',
					value = 'all',
				},
				{
					header = 'X-Generate-Proxies',
					value = 'all',
				},
				{
					header = 'Content-Type',
					value = 'application/rdf+xml',
				},
				{
					header = 'Accept',
					value = 'application/json',
				},
			},
			requestPriority = 3,
		},
		response = {
			status_code = '404'
		},
	}

end

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

local function generate404ResponseForGetVersion( catalogOrSpaceId, assetId, revisionId, filePath )

	local url = '/v2/catalogs/' .. catalogOrSpaceId .. '/versions/' .. assetId .. '/revisions/' .. revisionId

	local fail = {
		{
			code = 1000,
			description = "Resource not found",
			errors = {
				version = {
					"does not exist",
				},
			},
			subtype = "ResourceNotFoundError",
		},
		{
			status_code = '404',
			[ 'X-Result-Code' ] = '1000',
		}
	}

	return {
		url = url,
		fail = fail,
		options = {
			returnHttpErrorsAsData = true,
		},
		response = {
			status_code = '404'
		},
	}

end

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

local function generateCopyXmpDevelopResponseImp( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, sourceUrl, errorCode )

	local url = '/v2/' .. catalogsOrSpaces .. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/xmp/develop'

	errorCode = errorCode or '202'

	local response = {
		[ 'Content-Length' ] = 0,
		Location = "",
	}

	local fail

	if errorCode == '404' then
		fail = {
			{
				code = 1000,
				description = "Resource not found",
				errors = {
					revision = {
						"does not exist",
					},
				},
				subtype = "ResourceNotFoundError",
			},
			{
				status_code = errorCode,
				[ 'X-Result-Code' ] = '1000',
			}
		}
	end

	local requestHeaders =  {
				{
					header = "Accept",
					value = "application/json",
				},
				{
					header = "Content-Type",
					value = "application/json",
				},
				{
					header = "X-Generate-Proxies",
					value = "all",
				},
				{
					header = "X-Generate-Renditions",
					value = "all",
				}
			}

	return {
		url = url,
		data = '{ "file_href": "' .. sourceUrl .. '" }',
		options = {
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
			requestHeaders = requestHeaders,
		},

		response = response,
		fail = fail,

		headers = {
			status_code = errorCode,
		},
	}

end

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

local function generateCopyXmpDevelopResponseWithUrl( catalogId, assetId, revisionId, sourceUrl )

	return generateCopyXmpDevelopResponseImp( 'catalogs', catalogId, assetId, revisionId, sourceUrl )

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

local function generateCopyXmpDevelopResponse( catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId, errorCode )

	local sourceUrl = '/v2/catalogs/' .. catalogId .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/xmp/develop'

	return generateCopyXmpDevelopResponseImp( 'catalogs', catalogId, assetId, revisionId, sourceUrl, errorCode )

end

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

local function generateCopyXmpDevelopResponseWithUrlForInAppContribute( spaceId, assetId, revisionId, sourceUrl )

	return generateCopyXmpDevelopResponseImp( 'spaces', spaceId, assetId, revisionId, sourceUrl )

end

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

local function generateCreateXmpSidecarResponseImpl( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, filePath )

	local url = '/v2/' .. catalogsOrSpaces .. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/xmp'

	return {
		url = url,
		data = filePath,
		options = {
			dataFormat = "filePath",
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 2,
			requestPriority = 3,
			requestHeaders = {
				{
					header = "Accept",
					value = "application/json",
				},
				{
					header = "Content-Type",
					value = "application/rdf+xml",
				}
			},
		},

		headers = {
			status_code = '201',
		},
	}

end

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

local function generateCreateXmpSidecarResponse( catalogId, assetId, revisionId, filePath )

	return generateCreateXmpSidecarResponseImpl( 'catalogs', catalogId, assetId, revisionId, filePath )

end

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

local function generateCreateXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, filePath )

	return generateCreateXmpSidecarResponseImpl( 'spaces', spaceId, assetId, revisionId, filePath )

end

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

local function generateCopyXmpSidecarResponseImp( catalogsOrSpaces, catalogOrSpaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, optAdditionalHeaders, errorCode )

	local url = '/v2/' .. catalogsOrSpaces .. '/' .. catalogOrSpaceId .. '/assets/' .. assetId .. '/revisions/' .. revisionId .. '/xmp'

	errorCode = errorCode or '202'

	local response = {
		[ 'Content-Length' ] = 0,
		Location = "",
	}

	local fail

	if errorCode == '404' then
		fail = {
			{
				code = 1000,
				description = "Resource not found",
				errors = {
					revision = {
						"does not exist",
					},
				},
				subtype = "ResourceNotFoundError",
			},
			{
				status_code = errorCode,
				[ 'X-Result-Code' ] = '1000',
			}
		}
	end

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

	for _, requestHeader in ipairs( optAdditionalHeaders or {} ) do
		table.insert( requestHeaders, requestHeader )
	end

	return {
		url = url,
		data = '{ "file_href": "/v2/catalogs/' .. catalogId .. '/assets/' .. sourceAssetId .. '/revisions/' .. sourceRevisionId .. '/xmp" }',
		options = {
			notifyWhenHttpRequestIsInProgress = true,
			requestCost = 1,
			requestPriority = 3,
			requestHeaders = requestHeaders,
		},

		response = response,
		fail = fail,

		headers = {
			status_code = errorCode,
		},
	}

end

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

local function generateCopyXmpSidecarResponse( catalogId, assetId, revisionId, sourceAssetId, sourceRevisionId, errorCode )

	return generateCopyXmpSidecarResponseImp( 'catalogs', catalogId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode )

end

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

local function generateCopyXmpSidecarResponseForInAppContribute( spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, errorCode, optRedactionsHeaderValue )

	local fullRedactionsHeader

	if optRedactionsHeaderValue then
		fullRedactionsHeader = { {
			header = 'X-Redact-Meta',
			value = optRedactionsHeaderValue
		} }
	end

	return generateCopyXmpSidecarResponseImp( 'spaces', spaceId, assetId, revisionId, catalogId, sourceAssetId, sourceRevisionId, fullRedactionsHeader, errorCode )

end

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

local function generateChunkResponse( filePath, chunkNumber, kMasterUrl, resultHeaders, parts, chunkSize, chunkLength, contentRangeStrOverride )

	local fileSize = WFFileUtils.fileAttributes( filePath ).fileSize or 0

	chunkSize = chunkSize or kChunkSize
	chunkLength = chunkLength or chunkSize

	local startOffset = (chunkNumber - 1) * chunkSize
	local endOffset = startOffset + chunkLength - 1

	local contentRangeStr = string.format( "bytes %s-%s/%s", startOffset, endOffset, fileSize)

	contentRangeStr = contentRangeStrOverride or contentRangeStr

	local kDefaultPutArgs = {
		requestHeaders = {
			{
				header = 'X-Generate-Renditions',
				value = 'all',
			},
			{
				header = 'X-Generate-Proxies',
				value = 'all',
			},
			{
				header = 'Accept',
				value = 'application/json',
			},
			{
				header = 'Content-Type',
				value = 'application/octet-stream',
			},
			{
				header = "X-Read-Meta",
				value = "xmp,location",
			},
		},
		dataFormat = 'filePath',
		requestPriority = 3,
		notifyWhenHttpRequestIsInProgress = true,
		requestCost = 2,
	}

	local options = KSTableUtils.deepCopy( kDefaultPutArgs )
	options.length = chunkLength
	options.offset = startOffset + 1
	table.insert( options.requestHeaders, {
		header = 'Content-Range',
		value = contentRangeStr,
	} )

	if parts == nil then
		parts = {}
		for x = 0, chunkNumber - 1 do
			table.insert( parts, { first = x * chunkSize, last = ( x + 1 ) * chunkSize - 1 } )
		end
	end

	local response = KSJsonEncoder.encodeWebSafeJson( {
		parts = parts
	} )

	local headers = nil
	local fail = nil
	if resultHeaders then

		if resultHeaders.status_code >= '4' then
			fail = { 'responseData', resultHeaders }
		else
			headers = resultHeaders
		end

	else
		headers = { status_code = '201' }
	end

	return {
		url = kMasterUrl,
		options = options,
		data = filePath,

		response = response,

		fail = fail,
		headers = headers,
	}

end

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

local function generateLastChunkResponse( filePath, chunkNumber, kMasterUrl, chunkSize, contentRangeStrOverride )

	chunkSize = chunkSize or kChunkSize

	local fileSize = WFFileUtils.fileAttributes( filePath ).fileSize or 0

	local lastChunkLength = ( fileSize - ( chunkNumber - 1 ) * chunkSize )

	return generateChunkResponse( filePath, chunkNumber, kMasterUrl, nil, nil, chunkSize, lastChunkLength, contentRangeStrOverride )

end

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

local function generateErrorResponseMock(url, data, key, err, desc, statusCode, resultCode)

	if type( err ) ~= 'table' then
		err = { err }
	end

	local errorBody = KSJsonEncoder.encodeOzUploadJson({
		errors = {
			[ key ] = { err },
		},
		code = 1003,
		decription = desc,
	})

	local status = { status_code = statusCode }
	if resultCode then
		status['X-Result-Code'] = resultCode
	end

	local errorResponse = {
		url = url,
		fail = { errorBody, status },
		returnHttpErrorsAsData = false,
	}
	if data then
		errorResponse.data = data
	end

	return errorResponse

end

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

local function getAuxDirectUploadPartsUrlForAsset( catalog, assetDoc, sha256, fileSize, type, contentType )

	type = type or 'mask'
	contentType = contentType or 'application/octet-stream'

	local urlPrefix
	if assetDoc._spaceId then
		urlPrefix = "/v2/spaces/" .. assetDoc._spaceId
	else
		urlPrefix = "/v2/catalogs/" .. catalog:ozCatalogId()
	end

	local _, partsUrl = WFURI.replaceFromOzTemplate(
		urlPrefix .. "/assets/{assetId}/sha256/{sha256}/aux/{auxType}/parts" ..
		"?access=put&file_size={file_size}&part_size={part_size}&upload_status={upload_status}&content_type={content_type}", {
		assetId = assetDoc._id,
		sha256 = sha256,
		auxType = type,
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		upload_status = 'all',
		content_type = WFURI.escape( contentType, 'http_query_key' )
	})

	return partsUrl

end

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

-- returns auxes array
local function getAuxDirectUploadMocksForAsset( catalog, assetDoc, mocks, options )

	options = options or {}

	local auxes = { }
	local baseAuxPath = WFCatAssetAux.getBasePath( catalog )

	local completeParams = "?file_size={file_size}"
	if options.atomicDevelopSettings then
		completeParams = completeParams .. "&device={device}&userUpdated={userUpdated}"
	end

	for digest, pathInfo in pairs( assetDoc._localOnly.files.aux ) do

		local auxId = WFCatAssetAux.getAuxIdForDigest( assetDoc, digest )
		local auxInfo = assetDoc.aux[auxId]

		if auxInfo.type == 'mask' or auxInfo.type == 'filter' then
			
			local info = {

				id = auxId,
				digest = digest,
				type = auxInfo.type,
				filePath = KSPathUtils.child( baseAuxPath, pathInfo.relativePath ),
				fileSize = auxInfo.fileSize,
				sha256 =  auxInfo.sha256,
				contentType = 'application/octet-stream',

			}

			table.insert( auxes, info )
			
		end

	end

	local urlPrefix
	if assetDoc._spaceId then
		urlPrefix = "/v2/spaces/" .. assetDoc._spaceId
	else
		urlPrefix = "/v2/catalogs/" .. catalog:ozCatalogId()
	end

	for i, aux in ipairs( auxes ) do

		local partsUrl = getAuxDirectUploadPartsUrlForAsset( catalog, assetDoc, aux.sha256, aux.fileSize, aux.type, aux.contentType )

		local uploadUrl = 'xyz.s3-us-west-2.amazonaws.com/aux-part-' ..  tostring( i )
		local _, completeUrlTemplate = WFURI.replaceFromOzTemplate(
			urlPrefix .. "/assets/{assetId}/sha256/{sha256}/aux/{auxType}/parts" .. completeParams, {
			assetId = assetDoc._id,
			sha256 = aux.sha256,
			auxType = aux.type,
			file_size = tostring( aux.fileSize),
		} )

		local developUserUpdated = KSTableUtils.derefOpt( assetDoc, 'develop', 'userUpdated' ) or kDefaultUserUpdatedUnencoded
		local _, completeUrl = WFURI.replaceFromOzTemplate( completeUrlTemplate, {
			device = WFURI.escape( WFDeviceInfo.deviceDescription(), 'http_query_key' ),
			userUpdated = WFURI.escape( developUserUpdated, 'http_query_key' ),
		} )

		table.insert( mocks, generateDirectPartsMock( partsUrl, uploadUrl, completeUrlTemplate, aux.fileSize, aux.contentType, options ) )

		if not options.fileStatusUploaded then
			table.insert( mocks, generateDirectUploadMock( uploadUrl, aux.filePath,  aux.fileSize, aux.contentType, options ) )
			table.insert( mocks, generateDirectCompleteMock( completeUrl, aux.contentType, options ) )
		end

	end

	return auxes

end

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

local function getXmpDevelopDirectUploadPartsUrlForAsset( catalog, assetDoc, localOnly )

	local sha256 = localOnly.developSettingsSha256
	local filePath = localOnly.xmpDevelopSettingsFilePath
	local fileSize = localOnly.xmpDevelopSettingsFileSize
	local contentType = 'application/rdf+xml'

	local urlPrefix
	if assetDoc._spaceId then
		urlPrefix = "/v2/spaces/" .. assetDoc._spaceId
	else
		urlPrefix = "/v2/catalogs/" .. catalog:ozCatalogId()
	end

	local _, partsUrl = WFURI.replaceFromOzTemplate(
		urlPrefix .. "/assets/{assetId}/sha256/{sha256}/xmp/develop/parts" ..
		"?access=put&file_size={file_size}&part_size={part_size}&upload_status={upload_status}&content_type={content_type}", {
		assetId = assetDoc._id,
		sha256 = sha256,
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		upload_status = "all",
		content_type = WFURI.escape( contentType, 'http_query_key' )
	})

	return partsUrl, sha256, filePath, fileSize, contentType, urlPrefix

end

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

-- returns sha256
local function getXmpDevelopDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks, options )

	options = options or {}

	local partsUrl, sha256, filePath, fileSize, contentType, urlPrefix = getXmpDevelopDirectUploadPartsUrlForAsset( catalog, assetDoc, localOnly )

	local uploadUrl = "xyz.s3-us-west-2.amazonaws.com/xmp-develop-part-1"

	local _, completeUrlTemplate = WFURI.replaceFromOzTemplate(
		urlPrefix .. "/assets/{assetId}/sha256/{sha256}/xmp/develop/parts?file_size={file_size}&device={device}&userUpdated={userUpdated}", {
		assetId = assetDoc._id,
		sha256 = sha256,
		file_size = tostring( fileSize ),
	})

	local developUserUpdated = KSTableUtils.derefOpt( assetDoc, 'develop', 'userUpdated' ) or kDefaultUserUpdatedUnencoded
	local _, completeUrl = WFURI.replaceFromOzTemplate( completeUrlTemplate, {
		device = WFURI.escape( WFDeviceInfo.deviceDescription(), 'http_query_key' ),
		userUpdated = WFURI.escape( developUserUpdated, 'http_query_key' ),
	} )

	table.insert( mocks, generateDirectPartsMock( partsUrl, uploadUrl, completeUrlTemplate, fileSize, contentType, options ) )

	if not options.fileStatusUploaded then
		table.insert( mocks, generateDirectUploadMock( uploadUrl, filePath,  fileSize, contentType, options ) )
		table.insert( mocks, generateDirectCompleteMock( completeUrl, contentType, options ) )
	end

	return sha256

end

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

local function getXmpDevelopDirectUploadPartsUrlForVersion( catalog, versionDoc, localOnly )

	local sha256 = localOnly.developSettingsSha256
	local filePath = localOnly.xmpDevelopSettingsFilePath
	local fileSize = localOnly.xmpDevelopSettingsFileSize
	local contentType = 'application/rdf+xml'

	local urlPrefix
	if versionDoc._spaceId then
		urlPrefix = "/v2/spaces/" .. versionDoc._spaceId
	else
		urlPrefix = "/v2/catalogs/" .. catalog:ozCatalogId()
	end

	local _, partsUrl = WFURI.replaceFromOzTemplate(
		urlPrefix .. "/assets/{assetId}/versions/{versionId}/sha256/{sha256}/xmp/develop/parts" ..
		"?access=put&file_size={file_size}&part_size={part_size}&upload_status={upload_status}&content_type={content_type}", {
		assetId = versionDoc.asset.id,
		versionId = versionDoc._id,
		sha256 = sha256,
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		upload_status = "all",
		content_type = WFURI.escape( contentType, 'http_query_key' )
	})

	return partsUrl, sha256, filePath, fileSize, contentType, urlPrefix

end

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

local function getXmpDevelopDirectUploadMocksForVersion( catalog, versionDoc, localOnly, mocks, options )

	options = options or {}

	local partsUrl, sha256, filePath, fileSize, contentType, urlPrefix = getXmpDevelopDirectUploadPartsUrlForVersion( catalog, versionDoc, localOnly )

	local uploadUrl = "xyz.s3-us-west-2.amazonaws.com/xmp-develop-part-1"
	local _, completeUrl = WFURI.replaceFromOzTemplate(
		urlPrefix .. "/assets/{assetId}/versions/{versionId}/sha256/{sha256}/xmp/develop/parts?file_size={file_size}", {
		assetId = versionDoc.asset.id,
		sha256 = sha256,
		file_size = tostring( fileSize ),
	})

	table.insert( mocks, generateDirectPartsMock( partsUrl, uploadUrl, completeUrl, fileSize, contentType, options ) )
	table.insert( mocks, generateDirectUploadMock( uploadUrl, filePath,  fileSize, contentType, options ) )
	table.insert( mocks, generateDirectCompleteMock( completeUrl, contentType, options ) )

end

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

local function getOriginalDirectUploadMocksForAsset( catalog, assetDoc, localOnly, mocks, revisionId, readMetadataNone )

	local filePath = localOnly.originalFilePath
	local fileSize = localOnly.originalFileSize
	local contentType = 'application/octet-stream'

	local _, partsRequestUrl = WFURI.replaceFromOzTemplate(
		"/v2/catalogs/{catalogId}/assets/{assetId}/revisions/{revisionId}/main/parts" ..
		"?access=put&upload_status={upload_status}&file_size={file_size}&part_size={part_size}&content_type={content_type}", {
		catalogId = catalog:ozCatalogId(),
		assetId = assetDoc._id,
		revisionId = revisionId,
		upload_status = 'pending',
		file_size = tostring( fileSize ),
		part_size = tostring( kOZChunkSize ),
		content_type = WFURI.escape( contentType, 'http_query_key' ),
	})

	local uploadUrl = 'xyz.s3-us-west-2.amazonaws.com/1'
	local completeUrl = 'main/parts?file_size=' .. fileSize

	table.insert( mocks, generateAssetMainPartsMock( partsRequestUrl, uploadUrl, completeUrl, fileSize, contentType, readMetadataNone ) )
	table.insert( mocks, generateAssetUploadMock( uploadUrl, filePath, fileSize, contentType, readMetadataNone ) )
	table.insert( mocks, generateAssetCompleteMock( completeUrl, contentType, readMetadataNone ) )

end

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

local function generateReloadAssetForTemplateLinkMock( catalog, assetId, links, errorResponse )

	-- format the base and make all links relative to it

	local selfLink = KSTableUtils.derefOpt( links, 'self', 'href' )
	local index = string.find( selfLink, '/assets/' )
	local baseUrl = string.sub( selfLink, 5, index )
		-- the 5 is because kOzBase includes /v2/ need to snip that off from the front of baseUrl

	links = KSTableUtils.deepCopy( links )
	for key, value in pairs( links ) do
		if key == '/rels/proxy_create' then
			links[key] = nil
				-- remove the create proxy link, just so we can test that a missing link will be correctly removed
		elseif value.href then
			links[key].href = string.sub( value.href, index + 1 )
		end
	end

	links.foo = { href = '/bar' }
		-- add a dummy link, just so we can test that it will be added correctly

	local status_code = '200'
	local fail
	if errorResponse then
		status_code = '404'
		fail = {
			{
				code = 1000,
				description = "Resource not found",
				errors = {
					revision = {
						"does not exist",
					},
				},
				subtype = "ResourceNotFoundError",
			},
			{
				status_code = '404',
				[ 'X-Result-Code' ] = '1000',
			}
		}
	end

	return {

		url = selfLink,

		response = {
			base = kOZBase .. baseUrl,
			id = assetId,
			links = links,
		},

		fail = fail,

		headers = {
			status_code = status_code,
		},

		options = {
			bypassRateLimiter = true,
		},

	}

end

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

return {

	generateAssetMainPartsMock = generateAssetMainPartsMock,
	generateAssetUploadMock = generateAssetUploadMock,
	generateAssetCompleteMock = generateAssetCompleteMock,

	generateDirectPartsMock = generateDirectPartsMock,
	generateDirectUploadMock = generateDirectUploadMock,
	generateDirectCompleteMock = generateDirectCompleteMock,

	generateAuxCopyMock = generateAuxCopyMock,

	generateCreateMasterResponseWithXReadMeta = generateCreateMasterResponseWithXReadMeta,
	generateCreateMasterResponseForInAppContributeWithXReadMeta = generateCreateMasterResponseForInAppContributeWithXReadMeta,

	generateCopyMasterResponse = generateCopyMasterResponse,
	generateCopyMasterResponseForInAppContribute = generateCopyMasterResponseForInAppContribute,
	generateCopyMasterResponseWithUrlForInAppContribute = generateCopyMasterResponseWithUrlForInAppContribute,
	generateCopyMasterResponseForVideo = generateCopyMasterResponseForVideo,

	generateCopyProxyResponse = generateCopyProxyResponse,
	generateCopyProxyResponseForInAppContribute = generateCopyProxyResponseForInAppContribute,

	generateCreateProxyResponse = generateCreateProxyResponse,
	generateCreateProxyResponseForInAppContribute = generateCreateProxyResponseForInAppContribute,

	generateUploadXmpDevelopFileResponse = generateUploadXmpDevelopFileResponse,
	generateUploadXmpDevelopFileResponseForInAppContribute = generateUploadXmpDevelopFileResponseForInAppContribute,
	generateUploadXmpDevelopFileResponseForVersion = generateUploadXmpDevelopFileResponseForVersion,
	generateUploadXmpDevelop404ResponseForVersion = generateUploadXmpDevelop404ResponseForVersion,

	generate404ResponseForGetVersion = generate404ResponseForGetVersion,

	generateCopyXmpDevelopResponseWithUrl = generateCopyXmpDevelopResponseWithUrl,
	generateCopyXmpDevelopResponse = generateCopyXmpDevelopResponse,
	generateCopyXmpDevelopResponseWithUrlForInAppContribute = generateCopyXmpDevelopResponseWithUrlForInAppContribute,

	generateCreateXmpSidecarResponse = generateCreateXmpSidecarResponse,
	generateCreateXmpSidecarResponseForInAppContribute = generateCreateXmpSidecarResponseForInAppContribute,

	generateCopyXmpSidecarResponse = generateCopyXmpSidecarResponse,
	generateCopyXmpSidecarResponseForInAppContribute = generateCopyXmpSidecarResponseForInAppContribute,

	generateChunkResponse = generateChunkResponse,
	generateLastChunkResponse = generateLastChunkResponse,
	generateErrorResponseMock = generateErrorResponseMock,

	getAuxDirectUploadPartsUrlForAsset = getAuxDirectUploadPartsUrlForAsset,
	getAuxDirectUploadMocksForAsset = getAuxDirectUploadMocksForAsset,

	getXmpDevelopDirectUploadPartsUrlForAsset = getXmpDevelopDirectUploadPartsUrlForAsset,
	getXmpDevelopDirectUploadMocksForAsset = getXmpDevelopDirectUploadMocksForAsset,
	getXmpDevelopDirectUploadMocksForVersion = getXmpDevelopDirectUploadMocksForVersion,

	getOriginalDirectUploadMocksForAsset = getOriginalDirectUploadMocksForAsset,

	generateReloadAssetForTemplateLinkMock = generateReloadAssetForTemplateLinkMock,

}
