import { NodePrivacyLevel, PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_HIDDEN, PRIVACY_ATTR_VALUE_MASK, PRIVACY_ATTR_VALUE_MASK_USER_INPUT, } from './privacyConstants' import { getNodeSelfPrivacyLevel, reducePrivacyLevel, getNodePrivacyLevel, shouldMaskNode, maskDisallowedTextContent, } from './privacy' import { ACTION_NAME_MASK } from './action/actionNameConstants' describe('getNodePrivacyLevel', () => { it('returns the element privacy mode if it has one', () => { const node = document.createElement('div') node.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.MASK) }) it('fallbacks to the default privacy mode if the element has none', () => { const node = document.createElement('div') expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.ALLOW) expect(getNodePrivacyLevel(node, NodePrivacyLevel.IGNORE)).toBe(NodePrivacyLevel.IGNORE) expect(getNodePrivacyLevel(node, NodePrivacyLevel.MASK)).toBe(NodePrivacyLevel.MASK) expect(getNodePrivacyLevel(node, NodePrivacyLevel.MASK_USER_INPUT)).toBe(NodePrivacyLevel.MASK_USER_INPUT) expect(getNodePrivacyLevel(node, NodePrivacyLevel.HIDDEN)).toBe(NodePrivacyLevel.HIDDEN) expect(getNodePrivacyLevel(node, NodePrivacyLevel.MASK_UNLESS_ALLOWLISTED)).toBe( NodePrivacyLevel.MASK_UNLESS_ALLOWLISTED ) }) describe('inheritance', () => { it('returns an ancestor privacy mode if the element has none', () => { const ancestor = document.createElement('div') const node = document.createElement('div') ancestor.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) ancestor.appendChild(node) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.MASK) }) it('fallbacks to the default privacy mode if no ancestor has one', () => { const ancestor = document.createElement('div') const node = document.createElement('div') ancestor.appendChild(node) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.ALLOW) }) it('overrides the ancestor privacy mode', () => { const ancestor = document.createElement('div') ancestor.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) const node = document.createElement('div') node.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT) ancestor.appendChild(node) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.MASK_USER_INPUT) }) it('does not override the ancestor privacy mode if it is HIDDEN', () => { const ancestor = document.createElement('div') ancestor.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_HIDDEN) const node = document.createElement('div') node.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT) ancestor.appendChild(node) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.HIDDEN) }) it('overrides the ancestor privacy mode if the element should be IGNORE', () => { const ancestor = document.createElement('div') ancestor.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) const node = document.createElement('script') ancestor.appendChild(node) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.IGNORE) }) it('returns an ancestor privacy mode if the element has none and cross shadow DOM', () => { const ancestor = document.createElement('div') ancestor.attachShadow({ mode: 'open' }) const node = document.createElement('div') ancestor.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) ancestor.shadowRoot!.appendChild(node) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW)).toBe(NodePrivacyLevel.MASK) }) }) describe('cache', () => { it('fills the cache', () => { const ancestor = document.createElement('div') const node = document.createElement('div') ancestor.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) ancestor.appendChild(node) const cache = new Map() getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW, cache) expect(cache.get(node)).toBe(NodePrivacyLevel.MASK) }) it('uses the cache', () => { const ancestor = document.createElement('div') const node = document.createElement('div') ancestor.appendChild(node) const cache = new Map() cache.set(node, NodePrivacyLevel.MASK_USER_INPUT) expect(getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW, cache)).toBe(NodePrivacyLevel.MASK_USER_INPUT) }) it('does not recurse on ancestors if the node is already in the cache', () => { const ancestor = document.createElement('div') const node = document.createElement('div') ancestor.appendChild(node) const parentNodeGetterSpy = spyOnProperty(node, 'parentNode').and.returnValue(ancestor) const cache = new Map() cache.set(node, NodePrivacyLevel.MASK_USER_INPUT) getNodePrivacyLevel(node, NodePrivacyLevel.ALLOW, cache) expect(parentNodeGetterSpy).not.toHaveBeenCalled() }) }) }) describe('getNodeSelfPrivacyLevel', () => { ;[ { msg: 'is not an element', html: 'foo', expected: undefined, }, // Overrules { msg: 'has no privacy attribute or class', html: '', expected: undefined, }, { msg: 'is a "base" element (forced override)', html: '', expected: NodePrivacyLevel.ALLOW, }, { msg: 'is an "input" element of type "password" (forced override)', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'is an "input" element of type "tel" (forced override)', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'is an "input" element of type "email" (forced override)', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'is an "input" element of type "hidden" (forced override)', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'is an "input" element and has an autocomplete attribute starting with "cc-" (forced override)', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'is an "input" element and has an autocomplete attribute ending with "-password" (forced override)', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'is an "input" element and has an autocomplete attribute not starting with "cc-"', html: '', expected: undefined, }, // Class { msg: 'has a dd-privacy-allow class', html: '', expected: NodePrivacyLevel.ALLOW, }, { msg: 'has a dd-privacy-hidden class', html: '', expected: NodePrivacyLevel.HIDDEN, }, { msg: 'has a dd-privacy-mask class', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'has a dd-privacy-mask-user-input class', html: '', expected: NodePrivacyLevel.MASK_USER_INPUT, }, { msg: 'has an unknown class starting with dd-privacy-', html: '', expected: undefined, }, // Attributes { msg: 'has a data-dd-privacy="allow" attribute', html: '', expected: NodePrivacyLevel.ALLOW, }, { msg: 'has a data-dd-privacy="hidden" attribute', html: '', expected: NodePrivacyLevel.HIDDEN, }, { msg: 'has a data-dd-privacy="mask" attribute', html: '', expected: NodePrivacyLevel.MASK, }, { msg: 'has a data-dd-privacy="mask-user-input" attribute', html: '', expected: NodePrivacyLevel.MASK_USER_INPUT, }, { msg: 'has an unknown data-dd-privacy attribute value', html: '', expected: undefined, }, // Ignored elements { msg: 'should be ignored', html: '