UNPKG

synctos

Version:

The Syncmaker. A tool to build comprehensive sync functions for Couchbase Sync Gateway.

656 lines (565 loc) 34.4 kB
// DEPRECATION NOTICE: This module has been deprecated in favour of the test-fixture-maker module /* istanbul ignore file */ const util = require('util'); /** * Initializes the module with the sync function at the specified file path. * * @param {string} filePath The path to the sync function to load */ exports.initSyncFunction = util.deprecate( initSyncFunction, 'The test-helper module is deprecated. Use test-fixture-maker instead.'); /** * Initializes the test helper module with the document definitions at the specified file path. * * @param {string} filePath The path to the document definitions to load */ exports.initDocumentDefinitions = util.deprecate( initDocumentDefinitions, 'The test-helper module is deprecated. Use test-fixture-maker instead.'); /** * An object that contains functions that are used to format expected validation error messages in specifications. Documentation can be * found in the "validation-error-formatter" module. */ exports.validationErrorFormatter = require('./validation-error-formatter'); /** * Attempts to write the specified doc and then verifies that it completed successfully with the expected channels. * * @param {Object} doc The document to write. May include property "_deleted=true" to simulate a delete operation. * @param {Object} oldDoc The document to replace or delete. May be null or undefined or include property "_deleted=true" to simulate a * create operation. * @param {(Object|string[])} expectedAuthorization Either an object that specifies the separate channels/roles/users or a list of channels * that are authorized to perform the operation. If it is an object, the following fields are * available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized * @param {Object[]} [expectedAccessAssignments] An optional list of expected user and role channel assignments. Each entry is an object * that contains the following fields: * - expectedType: an optional string that indicates whether this is a "channel" (default) or "role" assignment * - expectedChannels: an optional list of channels to assign to the users and roles * - expectedUsers: an optional list of users to which to assign the channels * - expectedRoles: an optional list of roles to which to assign the channels */ exports.verifyDocumentAccepted = verifyDocumentAccepted; /** * Attempts to create the specified doc and then verifies that it completed successfully with the expected channels. * * @param {Object} doc The new document * @param {(Object|string[])} [expectedAuthorization] Either an optional object that specifies the channels/roles/users or an optional list * of channels that are authorized to perform the operation. If omitted, then the channel * "write" is assumed. If it is an object, the following fields are available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized * @param {Object[]} [expectedAccessAssignments] An optional list of expected user and role channel assignments. Each entry is an object * that contains the following fields: * - expectedType: an optional string that indicates whether this is a "channel" (default) or "role" assignment * - expectedChannels: an optional list of channels to assign to the users and roles * - expectedUsers: an optional list of users to which to assign the channels * - expectedRoles: an optional list of roles to which to assign the channels */ exports.verifyDocumentCreated = verifyDocumentCreated; /** * Attempts to replace the specified doc and then verifies that it completed successfully with the expected channels. * * @param {Object} doc The updated document * @param {Object} oldDoc The document to replace * @param {(Object|string[])} [expectedAuthorization] Either an optional object that specifies the channels/roles/users or an optional list * of channels that are authorized to perform the operation. If omitted, then the channel * "write" is assumed. If it is an object, the following fields are available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized * @param {Object[]} [expectedAccessAssignments] An optional list of expected user and role channel assignments. Each entry is an object * that contains the following fields: * - expectedType: an optional string that indicates whether this is a "channel" (default) or "role" assignment * - expectedChannels: an optional list of channels to assign to the users and roles * - expectedUsers: an optional list of users to which to assign the channels * - expectedRoles: an optional list of roles to which to assign the channels */ exports.verifyDocumentReplaced = verifyDocumentReplaced; /** * Attempts to delete the specified doc and then verifies that it completed successfully with the expected channels. * * @param {Object} oldDoc The document to delete * @param {(Object|string[])} [expectedAuthorization] Either an optional object that specifies the channels/roles/users or an optional list * of channels that are authorized to perform the operation. If omitted, then the channel * "write" is assumed. If it is an object, the following fields are available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized */ exports.verifyDocumentDeleted = verifyDocumentDeleted; /** * Attempts to write the specified doc and then verifies that it failed validation with the expected channels. * * @param {Object} doc The document to write. May include property "_deleted=true" to simulate a delete operation. * @param {Object} oldDoc The document to replace or delete. May be null or undefined or include property "_deleted=true" to simulate a * create operation. * @param {string} docType The document's type as specified in the document definition * @param {string[]} expectedErrorMessages The list of validation error messages that should be generated by the operation. May be a string * if only one validation error is expected. * @param {(Object|string[])} expectedAuthorization Either an object that specifies the separate channels/roles/users or a list of channels * that are authorized to perform the operation. If it is an object, the following fields are * available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized */ exports.verifyDocumentRejected = verifyDocumentRejected; /** * Attempts to create the specified doc and then verifies that it failed validation with the expected channels. * * @param {Object} doc The new document * @param {string} docType The document's type as specified in the document definition * @param {string[]} expectedErrorMessages The list of validation error messages that should be generated by the operation. May be a string * if only one validation error is expected. * @param {(Object|string[])} [expectedAuthorization] Either an optional object that specifies the channels/roles/users or an optional list * of channels that are authorized to perform the operation. If omitted, then the channel * "write" is assumed. If it is an object, the following fields are available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized */ exports.verifyDocumentNotCreated = verifyDocumentNotCreated; /** * Attempts to replace the specified doc and then verifies that it failed validation with the expected channels. * * @param {Object} doc The updated document * @param {Object} oldDoc The document to replace * @param {string} docType The document's type as specified in the document definition * @param {string[]} expectedErrorMessages The list of validation error messages that should be generated by the operation. May be a string * if only one validation error is expected. * @param {(Object|string[])} [expectedAuthorization] Either an optional object that specifies the channels/roles/users or an optional list * of channels that are authorized to perform the operation. If omitted, then the channel * "write" is assumed. If it is an object, the following fields are available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized */ exports.verifyDocumentNotReplaced = verifyDocumentNotReplaced; /** * Attempts to delete the specified doc and then verifies that it failed validation with the expected channels. * * @param {Object} oldDoc The document to delete * @param {string} docType The document's type as specified in the document definition * @param {string[]} expectedErrorMessages The list of validation error messages that should be generated by the operation. May be a string * if only one validation error is expected. * @param {(Object|string[])} [expectedAuthorization] Either an optional object that specifies the channels/roles/users or an optional list * of channels that are authorized to perform the operation. If omitted, then the channel * "write" is assumed. If it is an object, the following fields are available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized */ exports.verifyDocumentNotDeleted = verifyDocumentNotDeleted; /** * Verifies that the given exception result of a document write operation includes the specified validation error messages. * * @param {Object} docType The document's type as specified in the document definition * @param {string[]} expectedErrorMessages The list of validation error messages that should be contained in the exception. May be a string * if only one validation error is expected. * @param {Object} exception The exception that was thrown by the sync function. Should include a "forbidden" property of type string. */ exports.verifyValidationErrors = verifyValidationErrors; /** * Verifies that the specified document that was created, replaced or deleted required the specified channels for access. * * @param {string[]} expectedChannels The list of all channels that are authorized to perform the operation. May be a string if only one channel * is expected. */ exports.verifyRequireAccess = verifyRequireAccess; /** * Verifies that the specified document that was created, replaced or deleted required the specified roles for access. * * @param {string[]} expectedRoles The list of all roles that are authorized to perform the operation. May be a string if only one role is * expected. */ exports.verifyRequireRole = verifyRequireRole; /** * Verifies that the specified document that was created, replaced or deleted required the specified users for access. * * @param {string[]} expectedUsers The list of all users that are authorized to perform the operation. May be a string if only one user is * expected. */ exports.verifyRequireUser = verifyRequireUser; /** * Verifies that the specified channels were all assigned to a document that was created, replaced or deleted. * * @param {string[]} expectedChannels The list of channels that should have been assigned to the document. May be a string if only one * channel is expected. */ exports.verifyChannelAssignment = verifyChannelAssignment; /** * Verifies that the sync function throws an error when authorization is denied to create/replace/delete a document. * * @param {Object} doc The document to attempt to write. May include property "_deleted=true" to simulate a delete operation. * @param {Object} oldDoc The document to replace or delete. May be null or undefined or include property "_deleted=true" to simulate a * create operation. * @param {(Object|string[])} expectedAuthorization Either an object that specifies the separate channels/roles/users or a list of channels * that are authorized to perform the operation. If it is an object, the following fields are * available: * - expectedChannels: an optional list of channels that are authorized * - expectedRoles: an optional list of roles that are authorized * - expectedUsers: an optional list of users that are authorized */ exports.verifyAccessDenied = verifyAccessDenied; /** * Verifies that the given document's type is unknown/invalid. * * @param {Object} doc The document to attempt to write. May include property "_deleted=true" to simulate a delete operation. * @param {Object} oldDoc The document to replace or delete. May be null or undefined or include property "_deleted=true" to simulate a * create operation. */ exports.verifyUnknownDocumentType = verifyUnknownDocumentType; // Implementation begins here const assert = require('assert'); const fs = require('fs'); const syncFunctionLoader = require('../loading/sync-function-loader'); const testEnvironmentMaker = require('./test-environment-maker'); const defaultWriteChannel = 'write'; function initSyncFunction(filePath) { const rawSyncFunction = fs.readFileSync(filePath, 'utf8').toString(); init(rawSyncFunction, filePath, true); } function initDocumentDefinitions(filePath) { const rawSyncFunction = syncFunctionLoader.load(filePath); init(rawSyncFunction); } function init(rawSyncFunction, syncFunctionFile, unescapeBackticks) { const testHelperEnvironment = testEnvironmentMaker.init(rawSyncFunction, syncFunctionFile, unescapeBackticks); exports.requireAccess = testHelperEnvironment.requireAccess; exports.requireRole = testHelperEnvironment.requireRole; exports.requireUser = testHelperEnvironment.requireUser; exports.channel = testHelperEnvironment.channel; exports.access = testHelperEnvironment.access; exports.role = testHelperEnvironment.role; exports.syncFunction = testHelperEnvironment.syncFunction; } function verifyRequireAccess(expectedChannels) { assert.ok(exports.requireAccess.callCount > 0, `Document does not specify required channels. Expected: ${expectedChannels}`); checkAuthorizations(expectedChannels, exports.requireAccess.calls[0].arg, 'channel'); } function verifyRequireRole(expectedRoles) { assert.ok(exports.requireRole.callCount > 0, `Document does not specify required roles. Expected: ${expectedRoles}`); checkAuthorizations(expectedRoles, exports.requireRole.calls[0].arg, 'role'); } function verifyRequireUser(expectedUsers) { assert.ok(exports.requireUser.callCount > 0, `Document does not specify required users. Expected: ${expectedUsers}`); checkAuthorizations(expectedUsers, exports.requireUser.calls[0].arg, 'user'); } function verifyChannelAssignment(expectedChannels) { assert.equal(exports.channel.callCount, 1, `Document was not assigned to any channels. Expected: ${expectedChannels}`); checkAuthorizations(expectedChannels, exports.channel.calls[0].arg, 'channel'); } function checkAuthorizations(expectedAuthorizations, actualAuthorizations, authorizationType) { if (!Array.isArray(expectedAuthorizations)) { expectedAuthorizations = [ expectedAuthorizations ]; } // Rather than compare the sizes of the two lists, which leads to an obtuse error message on failure (e.g. "expected 2 to be 3"), ensure // that neither list of channels/roles/users contains an element that does not exist in the other expectedAuthorizations.forEach((expectedAuth) => { if (!actualAuthorizations.includes(expectedAuth)) { assert.fail(`Expected ${authorizationType} was not encountered: ${expectedAuth}. Actual ${authorizationType}s: ${actualAuthorizations}`); } }); actualAuthorizations.forEach((actualAuth) => { if (!expectedAuthorizations.includes(actualAuth)) { assert.fail(`Unexpected ${authorizationType} encountered: ${actualAuth}. Expected ${authorizationType}s: ${expectedAuthorizations}`); } }); } function areUnorderedListsEqual(list1, list2) { return list1.length === list2.length && list1.every((element) => list2.includes(element)) && list2.every((element) => list1.includes(element)); } function accessAssignmentCallExists(accessFunction, expectedAssignees, expectedPermissions) { // Try to find an actual channel/role access assignment call that matches the expected call return accessFunction.calls.some((accessCall) => { return areUnorderedListsEqual(accessCall.args[0], expectedAssignees) && areUnorderedListsEqual(accessCall.args[1], expectedPermissions); }); } function prefixRoleName(role) { return `role:${role}`; } function verifyChannelAccessAssignment(expectedAssignment) { const expectedUsersAndRoles = [ ]; if (expectedAssignment.expectedUsers) { if (Array.isArray(expectedAssignment.expectedUsers)) { expectedUsersAndRoles.push(...expectedAssignment.expectedUsers); } else { expectedUsersAndRoles.push(expectedAssignment.expectedUsers); } } if (expectedAssignment.expectedRoles) { // The prefix "role:" must be applied to roles when calling the access function, as specified by // http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/sync-function-api-guide/index.html#access-username-channelname if (Array.isArray(expectedAssignment.expectedRoles)) { expectedAssignment.expectedRoles.forEach((expectedRole) => { expectedUsersAndRoles.push(prefixRoleName(expectedRole)); }); } else { expectedUsersAndRoles.push(prefixRoleName(expectedAssignment.expectedRoles)); } } const expectedChannels = [ ]; if (expectedAssignment.expectedChannels) { if (Array.isArray(expectedAssignment.expectedChannels)) { expectedChannels.push(...expectedAssignment.expectedChannels); } else { expectedChannels.push(expectedAssignment.expectedChannels); } } if (!accessAssignmentCallExists(exports.access, expectedUsersAndRoles, expectedChannels)) { assert.fail(`Missing expected call to assign channel access (${JSON.stringify(expectedChannels)}) to users and roles (${JSON.stringify(expectedUsersAndRoles)})`); } } function verifyRoleAccessAssignment(expectedAssignment) { const expectedUsers = [ ]; if (expectedAssignment.expectedUsers) { if (Array.isArray(expectedAssignment.expectedUsers)) { expectedUsers.push(...expectedAssignment.expectedUsers); } else { expectedUsers.push(expectedAssignment.expectedUsers); } } const expectedRoles = [ ]; if (expectedAssignment.expectedRoles) { // The prefix "role:" must be applied to roles when calling the role function, as specified by // http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/sync-function-api-guide/index.html#role-username-rolename if (Array.isArray(expectedAssignment.expectedRoles)) { expectedAssignment.expectedRoles.forEach((expectedRole) => { expectedRoles.push(prefixRoleName(expectedRole)); }); } else { expectedRoles.push(prefixRoleName(expectedAssignment.expectedRoles)); } } if (!accessAssignmentCallExists(exports.role, expectedUsers, expectedRoles)) { assert.fail(`Missing expected call to assign role access (${JSON.stringify(expectedRoles)}) to users (${JSON.stringify(expectedUsers)})`); } } function verifyAccessAssignments(expectedAccessAssignments) { let expectedAccessCalls = 0; let expectedRoleCalls = 0; expectedAccessAssignments.forEach((expectedAssignment) => { if (expectedAssignment.expectedType === 'role') { verifyRoleAccessAssignment(expectedAssignment); expectedRoleCalls++; } else if (expectedAssignment.expectedType === 'channel' || !expectedAssignment.expectedType) { verifyChannelAccessAssignment(expectedAssignment); expectedAccessCalls++; } else { assert.fail(`Unrecognized expected access assignment type ("${expectedAssignment.expectedType}")`); } }); if (exports.access.callCount !== expectedAccessCalls) { assert.fail(`Number of calls to assign channel access (${exports.access.callCount}) does not match expected (${expectedAccessCalls})`); } if (exports.role.callCount !== expectedRoleCalls) { assert.fail(`Number of calls to assign role access (${exports.role.callCount}) does not match expected (${expectedRoleCalls})`); } } function verifyOperationChannelsAssigned(doc, expectedChannels) { if (exports.channel.callCount !== 1) { assert.fail('Document channels were not assigned'); } const actualChannels = exports.channel.calls[0].arg; if (Array.isArray(expectedChannels)) { expectedChannels.forEach((expectedChannel) => { assert.ok( actualChannels.includes(expectedChannel), `Document was not assigned to expected channel: ${expectedChannel}. Actual: ${actualChannels}`); }); } else { assert.ok( actualChannels.includes(expectedChannels), `Document was not assigned to expected channel: "${expectedChannels}. Actual: ${actualChannels}`); } } function verifyAuthorization(expectedAuthorization) { let expectedOperationChannels = [ ]; if (typeof expectedAuthorization === 'string' || Array.isArray(expectedAuthorization)) { // For backward compatibility, if the authorization parameter is not an object, treat it as the collection of channels that are required // for authorization expectedOperationChannels = expectedAuthorization; verifyRequireAccess(expectedAuthorization); assert.equal(exports.requireRole.callCount, 0, `Unexpected document roles assigned: ${JSON.stringify(exports.requireRole.calls)}`); assert.equal(exports.requireUser.callCount, 0, `Unexpected document users assigned: ${JSON.stringify(exports.requireUser.calls)}`); } else { if (expectedAuthorization.expectedChannels) { expectedOperationChannels = expectedAuthorization.expectedChannels; verifyRequireAccess(expectedAuthorization.expectedChannels); } if (expectedAuthorization.expectedRoles) { verifyRequireRole(expectedAuthorization.expectedRoles); } else { assert.equal(exports.requireRole.callCount, 0, `Unexpected document roles assigned: ${JSON.stringify(exports.requireRole.calls)}`); } if (expectedAuthorization.expectedUsers) { verifyRequireUser(expectedAuthorization.expectedUsers); } else { assert.equal(exports.requireUser.callCount, 0, `Unexpected document users assigned: ${JSON.stringify(exports.requireUser.calls)}`); } if (!expectedAuthorization.expectedChannels && !expectedAuthorization.expectedRoles && !expectedAuthorization.expectedUsers) { verifyRequireAccess([ ]); } } return expectedOperationChannels; } function verifyDocumentAccepted(doc, oldDoc, expectedAuthorization, expectedAccessAssignments) { exports.syncFunction(doc, oldDoc || null); if (expectedAccessAssignments) { verifyAccessAssignments(expectedAccessAssignments); } const expectedOperationChannels = verifyAuthorization(expectedAuthorization); verifyOperationChannelsAssigned(doc, expectedOperationChannels); } function verifyDocumentCreated(doc, expectedAuthorization, expectedAccessAssignments) { verifyDocumentAccepted(doc, null, expectedAuthorization || defaultWriteChannel, expectedAccessAssignments); } function verifyDocumentReplaced(doc, oldDoc, expectedAuthorization, expectedAccessAssignments) { verifyDocumentAccepted(doc, oldDoc, expectedAuthorization || defaultWriteChannel, expectedAccessAssignments); } function verifyDocumentDeleted(oldDoc, expectedAuthorization) { verifyDocumentAccepted({ _id: oldDoc._id, _deleted: true }, oldDoc, expectedAuthorization || defaultWriteChannel); } function verifyDocumentRejected(doc, oldDoc, docType, expectedErrorMessages, expectedAuthorization) { let syncFuncError = null; try { exports.syncFunction(doc, oldDoc || null); } catch (ex) { syncFuncError = ex; } if (syncFuncError) { verifyValidationErrors(docType, expectedErrorMessages, syncFuncError); verifyAuthorization(expectedAuthorization); assert.equal(exports.channel.callCount, 0, `Document was erroneously assigned to channels: ${JSON.stringify(exports.channel.calls)}`); } else { assert.fail('Document validation succeeded when it was expected to fail'); } } function verifyDocumentNotCreated(doc, docType, expectedErrorMessages, expectedAuthorization) { verifyDocumentRejected(doc, null, docType, expectedErrorMessages, expectedAuthorization || defaultWriteChannel); } function verifyDocumentNotReplaced(doc, oldDoc, docType, expectedErrorMessages, expectedAuthorization) { verifyDocumentRejected(doc, oldDoc, docType, expectedErrorMessages, expectedAuthorization || defaultWriteChannel); } function verifyDocumentNotDeleted(oldDoc, docType, expectedErrorMessages, expectedAuthorization) { verifyDocumentRejected({ _id: oldDoc._id, _deleted: true }, oldDoc, docType, expectedErrorMessages, expectedAuthorization || defaultWriteChannel); } function verifyValidationErrors(docType, expectedErrorMessages, exception) { if (!Array.isArray(expectedErrorMessages)) { expectedErrorMessages = [ expectedErrorMessages ]; } // Used to split the leading component (e.g. "Invalid foobar document") from the validation error messages, which are separated by a colon const validationErrorRegex = /^([^:]+):\s*(.+)$/; const exceptionMessageMatches = validationErrorRegex.exec(exception.forbidden); let actualErrorMessages; if (exceptionMessageMatches) { assert.equal(exceptionMessageMatches.length, 3, `Unrecognized document validation error message format: "${exception.forbidden}"`); const invalidDocMessage = exceptionMessageMatches[1].trim(); assert.equal( invalidDocMessage, `Invalid ${docType} document`, `Unrecognized document validation error message format: "${exception.forbidden}"`); actualErrorMessages = exceptionMessageMatches[2].trim().split(/;\s*/); } else { actualErrorMessages = [ exception.forbidden ]; } // Rather than compare the sizes of the two lists, which leads to an obtuse error message on failure (e.g. "expected 2 to be 3"), verify // that neither list of validation errors contains an element that does not exist in the other expectedErrorMessages.forEach((expectedErrorMsg) => { assert.ok( actualErrorMessages.includes(expectedErrorMsg), `Document validation errors do not include expected error message: "${expectedErrorMsg}". Actual error: ${exception.forbidden}`); }); actualErrorMessages.forEach((errorMessage) => { if (!expectedErrorMessages.includes(errorMessage)) { assert.fail(`Unexpected document validation error: "${errorMessage}". Expected error: Invalid ${docType} document: ${expectedErrorMessages.join('; ')}`); } }); } function countAuthorizationTypes(expectedAuthorization) { let count = 0; if (expectedAuthorization.expectedChannels) { count++; } if (expectedAuthorization.expectedRoles) { count++; } if (expectedAuthorization.expectedUsers) { count++; } return count; } function verifyAccessDenied(doc, oldDoc, expectedAuthorization) { const channelAccessDeniedError = new Error('Channel access denied!'); const roleAccessDeniedError = new Error('Role access denied!'); const userAccessDeniedError = new Error('User access denied!'); const generalAuthFailedMessage = 'missing channel access'; exports.requireAccess.throwWith(channelAccessDeniedError); exports.requireRole.throwWith(roleAccessDeniedError); exports.requireUser.throwWith(userAccessDeniedError); let syncFuncError = null; try { exports.syncFunction(doc, oldDoc || null); } catch (ex) { syncFuncError = ex; } if (syncFuncError) { if (typeof expectedAuthorization === 'string' || Array.isArray(expectedAuthorization)) { assert.equal( syncFuncError, channelAccessDeniedError, `Document authorization error does not indicate channel access was denied. Actual: ${JSON.stringify(syncFuncError)}`); } else if (countAuthorizationTypes(expectedAuthorization) === 0) { verifyRequireAccess([ ]); } else if (countAuthorizationTypes(expectedAuthorization) > 1) { assert.equal( syncFuncError.forbidden, generalAuthFailedMessage, `Document authorization error does not indicate that channel, role and user access were all denied. Actual: ${JSON.stringify(syncFuncError)}`); } else if (expectedAuthorization.expectedChannels) { assert.equal( syncFuncError, channelAccessDeniedError, `Document authorization error does not indicate channel access was denied. Actual: ${JSON.stringify(syncFuncError)}`); } else if (expectedAuthorization.expectedRoles) { assert.equal( syncFuncError, roleAccessDeniedError, `Document authorization error does not indicate role access was denied. Actual: ${JSON.stringify(syncFuncError)}`); } else { assert.ok( syncFuncError, userAccessDeniedError, `Document authorization error does not indicate user access was denied. Actual: ${JSON.stringify(syncFuncError)}`); } verifyAuthorization(expectedAuthorization); } else { assert.fail('Document authorization succeeded when it was expected to fail'); } } function verifyUnknownDocumentType(doc, oldDoc) { let syncFuncError = null; try { exports.syncFunction(doc, oldDoc || null); } catch (ex) { syncFuncError = ex; } if (syncFuncError) { assert.equal( syncFuncError.forbidden, 'Unknown document type', `Document validation error does not indicate the document type is unrecognized. Actual: ${JSON.stringify(syncFuncError)}`); assert.equal(exports.channel.callCount, 0, `Document was erroneously assigned to channels: ${JSON.stringify(exports.channel.calls)}`); assert.equal(exports.requireAccess.callCount, 0, `Unexpected attempt to specify required channels: ${JSON.stringify(exports.requireAccess.calls)}`); assert.equal(exports.requireRole.callCount, 0, `Unexpected attempt to specify required roles: ${JSON.stringify(exports.requireRole.calls)}`); assert.equal(exports.requireUser.callCount, 0, `Unexpected attempt to specify required users: ${JSON.stringify(exports.requireUser.calls)}`); } else { assert.fail('Document type was successfully identified when it was expected to be unknown'); } }