fix(tracker): fix for dying tests (added tabid to writer, refactored other tests)
This commit is contained in:
parent
3da035188e
commit
00ee001539
8 changed files with 452 additions and 16 deletions
2
.github/workflows/ui-tests.js.yml
vendored
2
.github/workflows/ui-tests.js.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
- name: Jest tests
|
||||
run: |
|
||||
cd tracker/tracker
|
||||
yarn test
|
||||
yarn test:ci
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
"build": "npm run clean && npm run tscRun && npm run rollup && npm run compile",
|
||||
"prepare": "cd ../../ && husky install tracker/.husky/",
|
||||
"lint-front": "lint-staged",
|
||||
"test": "jest"
|
||||
"test": "jest --coverage=false",
|
||||
"test:ci": "jest --coverage=true"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.2",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ export interface Options {
|
|||
domSanitizer?: (node: Element) => SanitizeLevel
|
||||
}
|
||||
|
||||
export const stringWiper = (input: string) =>
|
||||
input
|
||||
.trim()
|
||||
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█')
|
||||
|
||||
export default class Sanitizer {
|
||||
private readonly obscured: Set<number> = new Set()
|
||||
private readonly hidden: Set<number> = new Set()
|
||||
|
|
@ -59,10 +64,9 @@ export default class Sanitizer {
|
|||
sanitize(id: number, data: string): string {
|
||||
if (this.obscured.has(id)) {
|
||||
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
||||
return data
|
||||
.trim()
|
||||
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█')
|
||||
return stringWiper(data)
|
||||
}
|
||||
|
||||
if (this.options.obscureTextNumbers) {
|
||||
data = data.replace(/\d/g, '0')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,14 +82,6 @@ export function hasOpenreplayAttribute(e: Element, attr: string): boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
export function isIframeCrossdomain(e: HTMLIFrameElement): boolean {
|
||||
try {
|
||||
return e.contentWindow?.location.href !== window.location.href
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if iframe is accessible
|
||||
**/
|
||||
|
|
@ -105,7 +97,7 @@ function dec2hex(dec: number) {
|
|||
return dec.toString(16).padStart(2, '0')
|
||||
}
|
||||
|
||||
export function generateRandomId(len: number) {
|
||||
export function generateRandomId(len?: number) {
|
||||
const arr: Uint8Array = new Uint8Array((len || 40) / 2)
|
||||
// msCrypto = IE11
|
||||
// @ts-ignore
|
||||
|
|
|
|||
113
tracker/tracker/src/tests/guards.unit.test.ts
Normal file
113
tracker/tracker/src/tests/guards.unit.test.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { describe, expect, test } from '@jest/globals'
|
||||
import {
|
||||
isNode,
|
||||
isSVGElement,
|
||||
isElementNode,
|
||||
isCommentNode,
|
||||
isTextNode,
|
||||
isDocument,
|
||||
isRootNode,
|
||||
hasTag,
|
||||
} from '../main/app/guards.js'
|
||||
|
||||
describe('isNode', () => {
|
||||
test('returns true for a valid Node object', () => {
|
||||
const node = document.createElement('div')
|
||||
expect(isNode(node)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-Node object', () => {
|
||||
const obj = { foo: 'bar' }
|
||||
expect(isNode(obj)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSVGElement', () => {
|
||||
test('returns true for an SVGElement object', () => {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
expect(isSVGElement(svg)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-SVGElement object', () => {
|
||||
const div = document.createElement('div')
|
||||
expect(isSVGElement(div)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isElementNode', () => {
|
||||
test('returns true for an Element object', () => {
|
||||
const element = document.createElement('div')
|
||||
expect(isElementNode(element)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-Element object', () => {
|
||||
const textNode = document.createTextNode('Hello')
|
||||
expect(isElementNode(textNode)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isCommentNode', () => {
|
||||
test('returns true for a Comment object', () => {
|
||||
const comment = document.createComment('This is a comment')
|
||||
expect(isCommentNode(comment)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-Comment object', () => {
|
||||
const div = document.createElement('div')
|
||||
expect(isCommentNode(div)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isTextNode', () => {
|
||||
test('returns true for a Text object', () => {
|
||||
const textNode = document.createTextNode('Hello')
|
||||
expect(isTextNode(textNode)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-Text object', () => {
|
||||
const div = document.createElement('div')
|
||||
expect(isTextNode(div)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isDocument', () => {
|
||||
test('returns true for a Document object', () => {
|
||||
const documentObj = document.implementation.createHTMLDocument('Test')
|
||||
expect(isDocument(documentObj)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-Document object', () => {
|
||||
const div = document.createElement('div')
|
||||
expect(isDocument(div)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isRootNode', () => {
|
||||
test('returns true for a Document object', () => {
|
||||
const documentObj = document.implementation.createHTMLDocument('Test')
|
||||
expect(isRootNode(documentObj)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for a DocumentFragment object', () => {
|
||||
const fragment = document.createDocumentFragment()
|
||||
expect(isRootNode(fragment)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a non-root Node object', () => {
|
||||
const div = document.createElement('div')
|
||||
expect(isRootNode(div)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasTag', () => {
|
||||
test('returns true if the element has the specified tag name', () => {
|
||||
const element = document.createElement('input')
|
||||
expect(hasTag(element, 'input')).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false if the element does not have the specified tag name', () => {
|
||||
const element = document.createElement('div')
|
||||
// @ts-expect-error
|
||||
expect(hasTag(element, 'span')).toBe(false)
|
||||
})
|
||||
})
|
||||
135
tracker/tracker/src/tests/sanitizer.unit.test.ts
Normal file
135
tracker/tracker/src/tests/sanitizer.unit.test.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { describe, expect, jest, afterEach, beforeEach, test } from '@jest/globals'
|
||||
import Sanitizer, { SanitizeLevel, Options, stringWiper } from '../main/app/sanitizer.js'
|
||||
|
||||
describe('stringWiper', () => {
|
||||
test('should replace all characters with █', () => {
|
||||
expect(stringWiper('Sensitive Data')).toBe('██████████████')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sanitizer', () => {
|
||||
let sanitizer: Sanitizer
|
||||
|
||||
beforeEach(() => {
|
||||
const options: Options = {
|
||||
obscureTextEmails: true,
|
||||
obscureTextNumbers: false,
|
||||
domSanitizer: undefined,
|
||||
}
|
||||
const app = {
|
||||
nodes: {
|
||||
getID: (el: { mockId: number }) => el.mockId,
|
||||
},
|
||||
}
|
||||
// @ts-expect-error
|
||||
sanitizer = new Sanitizer(app, options)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sanitizer.clear()
|
||||
})
|
||||
|
||||
test('should handle node and mark it as obscured if parent is obscured', () => {
|
||||
sanitizer['obscured'].add(2)
|
||||
sanitizer.handleNode(1, 2, document.createElement('div'))
|
||||
expect(sanitizer.isObscured(1)).toBe(true)
|
||||
})
|
||||
|
||||
test('should handle node and mark it as obscured if it has "masked" or "obscured" attribute', () => {
|
||||
const node = document.createElement('div')
|
||||
node.setAttribute('data-openreplay-obscured', '')
|
||||
sanitizer.handleNode(1, 2, node)
|
||||
expect(sanitizer.isObscured(1)).toBe(true)
|
||||
})
|
||||
|
||||
test('should handle node and mark it as hidden if parent is hidden', () => {
|
||||
sanitizer['hidden'].add(2)
|
||||
sanitizer.handleNode(1, 2, document.createElement('div'))
|
||||
expect(sanitizer.isHidden(1)).toBe(true)
|
||||
})
|
||||
|
||||
test('should handle node and mark it as hidden if it has "htmlmasked" or "hidden" attribute', () => {
|
||||
const node = document.createElement('div')
|
||||
node.setAttribute('data-openreplay-hidden', '')
|
||||
sanitizer.handleNode(1, 2, node)
|
||||
expect(sanitizer.isHidden(1)).toBe(true)
|
||||
})
|
||||
|
||||
test('should handle node and sanitize based on custom domSanitizer function', () => {
|
||||
const domSanitizer = (node: Element): SanitizeLevel => {
|
||||
if (node.tagName === 'SPAN') {
|
||||
return SanitizeLevel.Obscured
|
||||
}
|
||||
if (node.tagName === 'DIV') {
|
||||
return SanitizeLevel.Hidden
|
||||
}
|
||||
return SanitizeLevel.Plain
|
||||
}
|
||||
|
||||
const options: Options = {
|
||||
obscureTextEmails: true,
|
||||
obscureTextNumbers: false,
|
||||
domSanitizer,
|
||||
}
|
||||
const app = {
|
||||
nodes: {
|
||||
getID: jest.fn(),
|
||||
},
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
sanitizer = new Sanitizer(app, options)
|
||||
|
||||
const spanNode = document.createElement('span')
|
||||
const divNode = document.createElement('div')
|
||||
const plainNode = document.createElement('p')
|
||||
|
||||
sanitizer.handleNode(1, 2, spanNode)
|
||||
sanitizer.handleNode(3, 4, divNode)
|
||||
sanitizer.handleNode(5, 6, plainNode)
|
||||
|
||||
expect(sanitizer.isObscured(1)).toBe(true)
|
||||
expect(sanitizer.isHidden(3)).toBe(true)
|
||||
expect(sanitizer.isObscured(5)).toBe(false)
|
||||
expect(sanitizer.isHidden(5)).toBe(false)
|
||||
})
|
||||
|
||||
test('should sanitize data as obscured if node is marked as obscured', () => {
|
||||
sanitizer['obscured'].add(1)
|
||||
const data = 'Sensitive Data'
|
||||
|
||||
const sanitizedData = sanitizer.sanitize(1, data)
|
||||
expect(sanitizedData).toEqual(stringWiper(data))
|
||||
})
|
||||
|
||||
test('should sanitize data by obscuring text numbers if enabled', () => {
|
||||
sanitizer['options'].obscureTextNumbers = true
|
||||
const data = 'Phone: 123-456-7890'
|
||||
const sanitizedData = sanitizer.sanitize(1, data)
|
||||
expect(sanitizedData).toEqual('Phone: 000-000-0000')
|
||||
})
|
||||
|
||||
test('should sanitize data by obscuring text emails if enabled', () => {
|
||||
sanitizer['options'].obscureTextEmails = true
|
||||
const data = 'john.doe@example.com'
|
||||
const sanitizedData = sanitizer.sanitize(1, data)
|
||||
expect(sanitizedData).toEqual('********@*******.***')
|
||||
})
|
||||
|
||||
test('should return inner text of an element securely by sanitizing it', () => {
|
||||
const element = document.createElement('div')
|
||||
sanitizer['obscured'].add(1)
|
||||
// @ts-expect-error
|
||||
element.mockId = 1
|
||||
element.innerText = 'Sensitive Data'
|
||||
const sanitizedText = sanitizer.getInnerTextSecure(element)
|
||||
expect(sanitizedText).toEqual('██████████████')
|
||||
})
|
||||
|
||||
test('should return empty string if node element does not exist', () => {
|
||||
const element = document.createElement('div')
|
||||
element.innerText = 'Sensitive Data'
|
||||
const sanitizedText = sanitizer.getInnerTextSecure(element)
|
||||
expect(sanitizedText).toEqual('')
|
||||
})
|
||||
})
|
||||
186
tracker/tracker/src/tests/utils.unit.test.ts
Normal file
186
tracker/tracker/src/tests/utils.unit.test.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import { describe, expect, test, jest, afterEach, beforeEach } from '@jest/globals'
|
||||
import {
|
||||
adjustTimeOrigin,
|
||||
getTimeOrigin,
|
||||
now,
|
||||
stars,
|
||||
normSpaces,
|
||||
isURL,
|
||||
deprecationWarn,
|
||||
getLabelAttribute,
|
||||
hasOpenreplayAttribute,
|
||||
canAccessIframe,
|
||||
generateRandomId,
|
||||
} from '../main/utils.js'
|
||||
|
||||
describe('adjustTimeOrigin', () => {
|
||||
test('adjusts the time origin based on performance.now', () => {
|
||||
jest.spyOn(Date, 'now').mockReturnValue(1000)
|
||||
jest.spyOn(performance, 'now').mockReturnValue(1000)
|
||||
adjustTimeOrigin()
|
||||
|
||||
expect(getTimeOrigin()).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('now', () => {
|
||||
test('returns the current timestamp in milliseconds', () => {
|
||||
jest.spyOn(Date, 'now').mockReturnValue(2550)
|
||||
jest.spyOn(performance, 'now').mockReturnValue(2550)
|
||||
|
||||
adjustTimeOrigin()
|
||||
|
||||
expect(now()).toBe(2550)
|
||||
})
|
||||
})
|
||||
|
||||
describe('stars', () => {
|
||||
test('returns a string of asterisks with the same length as the input string', () => {
|
||||
expect(stars('hello')).toBe('*****')
|
||||
})
|
||||
|
||||
test('returns an empty string if the input string is empty', () => {
|
||||
expect(stars('')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('normSpaces', () => {
|
||||
test('trims the string and replaces multiple spaces with a single space', () => {
|
||||
expect(normSpaces(' hello world ')).toBe('hello world')
|
||||
})
|
||||
|
||||
test('returns an empty string if the input string is empty', () => {
|
||||
expect(normSpaces('')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isURL', () => {
|
||||
test('returns true for a valid URL starting with "https://"', () => {
|
||||
expect(isURL('https://example.com')).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for a valid URL starting with "http://"', () => {
|
||||
expect(isURL('http://example.com')).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for a URL without a valid protocol', () => {
|
||||
expect(isURL('example.com')).toBe(false)
|
||||
})
|
||||
|
||||
test('returns false for an empty string', () => {
|
||||
expect(isURL('')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deprecationWarn', () => {
|
||||
let consoleWarnSpy: jest.SpiedFunction<(args: any) => void>
|
||||
|
||||
beforeEach(() => {
|
||||
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation((args) => args)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
consoleWarnSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('prints a warning message for a deprecated feature', () => {
|
||||
deprecationWarn('oldFeature', 'newFeature')
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'OpenReplay: oldFeature is deprecated. Please, use newFeature instead. Visit https://docs.openreplay.com/ for more information.',
|
||||
)
|
||||
})
|
||||
|
||||
test('does not print a warning message for a deprecated feature that has already been warned', () => {
|
||||
deprecationWarn('oldFeature2', 'newFeature')
|
||||
deprecationWarn('oldFeature2', 'newFeature')
|
||||
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getLabelAttribute', () => {
|
||||
test('returns the value of "data-openreplay-label" attribute if present', () => {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('data-openreplay-label', 'Label')
|
||||
expect(getLabelAttribute(element)).toBe('Label')
|
||||
})
|
||||
|
||||
test('returns the value of "data-asayer-label" attribute if "data-openreplay-label" is not present (with deprecation warning)', () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation((args) => args)
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('data-asayer-label', 'Label')
|
||||
expect(getLabelAttribute(element)).toBe('Label')
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'OpenReplay: "data-asayer-label" attribute is deprecated. Please, use "data-openreplay-label" attribute instead. Visit https://docs.openreplay.com/ for more information.',
|
||||
)
|
||||
consoleWarnSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('returns null if neither "data-openreplay-label" nor "data-asayer-label" are present', () => {
|
||||
const element = document.createElement('div')
|
||||
expect(getLabelAttribute(element)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasOpenreplayAttribute', () => {
|
||||
let consoleWarnSpy: jest.SpiedFunction<(args: any) => void>
|
||||
|
||||
beforeEach(() => {
|
||||
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation((args) => args)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
consoleWarnSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('returns true and prints a deprecation warning for a deprecated openreplay attribute', () => {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('data-openreplay-htmlmasked', 'true')
|
||||
const result = hasOpenreplayAttribute(element, 'htmlmasked')
|
||||
expect(result).toBe(true)
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'OpenReplay: "data-openreplay-htmlmasked" attribute is deprecated. Please, use "hidden" attribute instead. Visit https://docs.openreplay.com/installation/sanitize-data for more information.',
|
||||
)
|
||||
})
|
||||
|
||||
test('returns false for a non-existent openreplay attribute', () => {
|
||||
const element = document.createElement('div')
|
||||
const result = hasOpenreplayAttribute(element, 'nonexistent')
|
||||
expect(result).toBe(false)
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('canAccessIframe', () => {
|
||||
test('returns true if the iframe has a contentDocument', () => {
|
||||
const iframe = document.createElement('iframe')
|
||||
Object.defineProperty(iframe, 'contentDocument', {
|
||||
get: () => document.createElement('div'),
|
||||
})
|
||||
expect(canAccessIframe(iframe)).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false if the iframe does not have a contentDocument', () => {
|
||||
const iframe = document.createElement('iframe')
|
||||
// Mock iframe.contentDocument to throw an error
|
||||
Object.defineProperty(iframe, 'contentDocument', {
|
||||
get: () => {
|
||||
throw new Error('securityError')
|
||||
},
|
||||
})
|
||||
expect(canAccessIframe(iframe)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateRandomId', () => {
|
||||
test('generates a random ID with the specified length', () => {
|
||||
const id = generateRandomId(10)
|
||||
expect(id).toHaveLength(10)
|
||||
expect(/^[0-9a-f]+$/.test(id)).toBe(true)
|
||||
})
|
||||
|
||||
test('generates a random ID with the default length if no length is specified', () => {
|
||||
const id = generateRandomId()
|
||||
expect(id).toHaveLength(40)
|
||||
expect(/^[0-9a-f]+$/.test(id)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -9,7 +9,7 @@ describe('BatchWriter', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
onBatchMock = jest.fn()
|
||||
batchWriter = new BatchWriter(1, 123456789, 'example.com', onBatchMock)
|
||||
batchWriter = new BatchWriter(1, 123456789, 'example.com', onBatchMock, '123')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -21,7 +21,8 @@ describe('BatchWriter', () => {
|
|||
expect(batchWriter['timestamp']).toBe(123456789)
|
||||
expect(batchWriter['url']).toBe('example.com')
|
||||
expect(batchWriter['onBatch']).toBe(onBatchMock)
|
||||
expect(batchWriter['nextIndex']).toBe(0)
|
||||
// we add tab id as first in the batch
|
||||
expect(batchWriter['nextIndex']).toBe(1)
|
||||
expect(batchWriter['beaconSize']).toBe(200000)
|
||||
expect(batchWriter['encoder']).toBeDefined()
|
||||
expect(batchWriter['strDict']).toBeDefined()
|
||||
|
|
@ -30,12 +31,14 @@ describe('BatchWriter', () => {
|
|||
})
|
||||
|
||||
test('writeType writes the type of the message', () => {
|
||||
// @ts-ignore
|
||||
const message = [Messages.Type.BatchMetadata, 1, 2, 3, 4, 'example.com']
|
||||
const result = batchWriter['writeType'](message as Message)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test('writeFields encodes the message fields', () => {
|
||||
// @ts-ignore
|
||||
const message = [Messages.Type.BatchMetadata, 1, 2, 3, 4, 'example.com']
|
||||
const result = batchWriter['writeFields'](message as Message)
|
||||
expect(result).toBe(true)
|
||||
|
|
@ -52,6 +55,7 @@ describe('BatchWriter', () => {
|
|||
})
|
||||
|
||||
test('writeWithSize writes the message with its size', () => {
|
||||
// @ts-ignore
|
||||
const message = [Messages.Type.BatchMetadata, 1, 2, 3, 4, 'example.com']
|
||||
const result = batchWriter['writeWithSize'](message as Message)
|
||||
expect(result).toBe(true)
|
||||
|
|
@ -72,6 +76,7 @@ describe('BatchWriter', () => {
|
|||
})
|
||||
|
||||
test('writeMessage writes the given message', () => {
|
||||
// @ts-ignore
|
||||
const message = [Messages.Type.Timestamp, 987654321]
|
||||
// @ts-ignore
|
||||
batchWriter['writeWithSize'] = jest.fn().mockReturnValue(true)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue