mongochangestream
Version:
Sync MongoDB collections via change streams into any database.
303 lines (292 loc) • 8.3 kB
text/typescript
import { Collection } from 'mongodb'
import assert from 'node:assert'
import { describe, test } from 'vitest'
import {
castError,
docToChangeStreamInsert,
generatePipelineFromOmit,
omitFieldsForUpdate,
removeUnusedFields,
safeRetry,
} from './util.js'
type ErrorWithCause = Error & { cause: unknown }
describe('util', () => {
describe('generatePipelineFromOmit', () => {
test('should generate pipeline from omit with no dotted fields', () => {
const pipeline = generatePipelineFromOmit(['documents', 'createdAt'])
assert.deepEqual(pipeline, [
{
$unset: [
'fullDocument.documents',
'updateDescription.updatedFields.documents',
'fullDocument.createdAt',
'updateDescription.updatedFields.createdAt',
],
},
])
})
})
describe('removeUnusedFields', () => {
test('should remove all unneeded fields', () => {
const schema = {
bsonType: 'object',
required: ['a', 'b'],
additionalProperties: false,
properties: {
a: {
title: 'An array of strings',
bsonType: 'array',
items: {
bsonType: 'string',
title: 'A string',
},
},
b: {
description: 'foo or bar',
bsonType: 'string',
enum: ['foo', 'bar'],
oneOf: [
{ bsonType: 'string', const: 'foo' },
{ bsonType: 'string', const: 'bar' },
],
},
c: {
bsonType: 'object',
additionalProperties: true,
properties: {
d: {
bsonType: 'number',
description: 'A number',
},
},
},
},
}
removeUnusedFields(schema)
assert.deepEqual(schema, {
bsonType: 'object',
additionalProperties: false,
properties: {
a: {
bsonType: 'array',
items: {
bsonType: 'string',
},
},
b: {
bsonType: 'string',
enum: ['foo', 'bar'],
},
c: {
bsonType: 'object',
additionalProperties: true,
properties: {
d: {
bsonType: 'number',
},
},
},
},
})
})
})
describe('omitFieldsForUpdate', () => {
test('should remove omitted fields from removedFields - exact', () => {
const event: any = {
updateDescription: {
updatedFields: {},
removedFields: ['address.geo.long'],
truncatedArrays: [],
},
}
const expected = {
updateDescription: {
updatedFields: {},
removedFields: [],
truncatedArrays: [],
},
}
omitFieldsForUpdate(['address.geo.long'], event)
assert.deepEqual(event, expected)
})
test('should remove omitted fields from removedFields - prefix', () => {
const event: any = {
updateDescription: {
updatedFields: {},
removedFields: ['address.geo.long'],
truncatedArrays: [],
},
}
const expected = {
updateDescription: {
updatedFields: {},
removedFields: [],
truncatedArrays: [],
},
}
omitFieldsForUpdate(['address.geo'], event)
assert.deepEqual(event, expected)
})
test('should remove omitted fields from updatedFields - exact', () => {
const event: any = {
updateDescription: {
updatedFields: {
name: 'unknown',
'address.city': 'San Diego',
},
removedFields: [],
truncatedArrays: [],
},
}
const expected = {
updateDescription: {
updatedFields: {
name: 'unknown',
},
removedFields: [],
truncatedArrays: [],
},
}
omitFieldsForUpdate(['address.city'], event)
assert.deepEqual(event, expected)
})
test('should remove omitted fields from updatedFields - prefix', () => {
const event: any = {
updateDescription: {
updatedFields: {
name: 'unknown',
'address.geo.lat': 24,
},
removedFields: [],
truncatedArrays: [],
},
}
const expected = {
updateDescription: {
updatedFields: {
name: 'unknown',
},
removedFields: [],
truncatedArrays: [],
},
}
omitFieldsForUpdate(['address.geo'], event)
assert.deepEqual(event, expected)
})
test('should remove omitted fields from updatedFields - nested', () => {
const event: any = {
updateDescription: {
updatedFields: {
name: 'unknown',
'address.geo': { lat: 24, long: 25 },
},
removedFields: [],
truncatedArrays: [],
},
}
const expected = {
updateDescription: {
updatedFields: {
name: 'unknown',
'address.geo': { long: 25 },
},
removedFields: [],
truncatedArrays: [],
},
}
omitFieldsForUpdate(['address.geo.lat'], event)
assert.deepEqual(event, expected)
})
})
describe('docToChangeStreamInsert', () => {
test('should produce a correct change stream "insert" event', () => {
const collection = {
dbName: 'testdb',
collectionName: 'testcoll',
} as Collection
const doc = {
_id: '123',
name: 'Joe',
}
const result = docToChangeStreamInsert(collection)(doc)
assert.deepEqual(result, {
fullDocument: { _id: '123', name: 'Joe' },
operationType: 'insert',
ns: { db: 'testdb', coll: 'testcoll' },
documentKey: { _id: '123' },
})
})
})
})
describe('castError', () => {
test('should return the same Error if input is already an Error', () => {
const originalError = new Error('test error')
const result = castError(originalError)
assert.strictEqual(result, originalError)
})
test('should convert string to Error', () => {
const result = castError('test error')
assert.ok(result instanceof Error)
assert.strictEqual(result.message, 'test error')
})
test('should handle unknown error types', () => {
const unknownError = { foo: 'bar' }
const result = castError(unknownError) as ErrorWithCause
assert.ok(result instanceof Error)
assert.strictEqual(result.message, 'Unknown error')
assert.strictEqual(result.cause, unknownError)
})
})
describe('safeRetry', () => {
test('should successfully return resolved promise value', async () => {
const result = await safeRetry(() => Promise.resolve(42))
assert.strictEqual(result, 42)
})
test('should wrap string errors in Error object', async () => {
let thrownError: Error | undefined
try {
await safeRetry(
() => {
throw 'custom error'
},
{ retries: 0 }
)
} catch (err) {
thrownError = err as Error
}
assert.ok(thrownError instanceof Error)
assert.strictEqual(thrownError?.message, 'custom error')
})
test('should wrap unknown error types in Error object', async () => {
const customError = { code: 500 }
let thrownError: ErrorWithCause | undefined
try {
await safeRetry(
() => {
throw customError
},
{ retries: 0 }
)
} catch (err) {
thrownError = err as ErrorWithCause
}
assert.ok(thrownError instanceof Error)
assert.strictEqual(thrownError?.message, 'Unknown error')
assert.strictEqual(thrownError?.cause, customError)
})
test('should pass through Error objects unchanged', async () => {
const originalError = new Error('test error')
let thrownError: Error | undefined
try {
await safeRetry(
() => {
throw originalError
},
{ retries: 0 }
)
} catch (err) {
thrownError = err as Error
}
assert.strictEqual(thrownError, originalError)
})
})