dyngoose
Version:
Elegant DynamoDB object modeling for Typescript
267 lines (216 loc) • 8.93 kB
text/typescript
import { expect } from 'chai'
import * as Dyngoose from '.'
import { TestableTable } from './setup-tests.spec'
describe('Table', () => {
it('should create primaryKey', () => {
expect(TestableTable.primaryKey).to.be.instanceof(Dyngoose.Query.PrimaryKey)
})
it('should have attributes properties', async () => {
const card = new TestableTable()
card.id = 10
card.title = '100'
await card.save()
const reloadedCard = await TestableTable.primaryKey.get(10, '100')
expect(reloadedCard).to.be.instanceof(TestableTable)
if (reloadedCard != null) {
expect(reloadedCard.id).to.eq(10)
expect(reloadedCard.get('id')).to.eq(10)
expect(reloadedCard.title).to.eq('100')
expect(reloadedCard.getUpdatedAttributes().length).to.eq(0)
}
})
describe('.remove', () => {
it('should allow attributes to be removed', async () => {
const card = TestableTable.new({
testNumberSet: [1, 2, 3],
})
card.id = 101
card.title = '101'
card.generic = 'something'
card.remove('generic')
card.remove('testString')
card.testString = 'value is set'
await card.save()
const reloadedCard = (await TestableTable.primaryKey.get(101, '101'))!
expect(reloadedCard).to.be.instanceof(TestableTable)
expect(reloadedCard.id).to.eq(101)
expect(reloadedCard.get('id')).to.eq(101)
expect(reloadedCard.title).to.eq('101')
expect(reloadedCard.generic).to.eq(null)
expect(reloadedCard.defaultedString).to.eq('SomeDefault')
expect(reloadedCard.testString).to.eq('value is set')
expect(reloadedCard.testNumberSet).to.deep.eq(new Set([1, 2, 3]))
expect(reloadedCard.getUpdatedAttributes().length).to.eq(0)
reloadedCard.generic = 'should be considered an update'
expect(reloadedCard.getUpdatedAttributes()).to.deep.eq(['generic'])
expect(reloadedCard.getSaveOperation()).to.eq('update')
})
})
it('should support update operators', async () => {
const card = TestableTable.new({
id: 98,
title: '98',
testString: 'some value',
testNumber: 11,
testNumberSet: new Set([1, 2, 3]),
testStringSet: ['1', '2', '3'],
testAttributeNaming: 'test',
})
await card.save()
expect(card.testNumber).to.eq(11, 'num eq 11')
card.set('testNumber', 5, { operator: 'decrement' })
expect(card.getUpdatedAttributes()).to.deep.eq(['testNumber'])
await card.save()
const reloadedCard = await TestableTable.primaryKey.get(card)
expect(reloadedCard).to.be.instanceof(TestableTable)
if (reloadedCard != null) {
expect(reloadedCard.testNumber).to.eq(11 - 5, 'decrement worked')
}
})
it('should allow an attribute to be emptied', async () => {
const card = new TestableTable()
card.id = 10
card.title = '100'
card.testString = 'some value'
await card.save()
expect(card.testString).to.eq('some value', 'initial card created')
card.testString = ''
expect(card.testString).to.eq(null, 'cleared strings become null, because DynamoDB does not allow empty string values')
expect(card.getUpdatedAttributes()).to.deep.eq([])
expect(card.getRemovedAttributes()).to.deep.eq(['testString'])
await card.save()
const reloadedCard = await TestableTable.primaryKey.get(10, '100')
expect(reloadedCard).to.be.instanceof(TestableTable)
if (reloadedCard != null) {
expect(reloadedCard.testString).to.eq(null, 'reloaded testString value compared')
}
})
it('should work with TTL', async () => {
const card = new TestableTable()
card.id = 10
card.title = '100'
card.expiresAt = new Date(Date.now() + 5000) // 5 secs away
await card.save()
// Wait 15 seconds
await new Promise((resolve) => setTimeout(resolve, 15000))
const reloaded = await TestableTable.primaryKey.get(10, '100', { consistent: true })
expect(reloaded).to.eq(undefined)
})
it('should be able to query by property names', async () => {
const results = await TestableTable.primaryKey.scan({
testAttributeNaming: 'test',
})
expect(results.length).to.eq(1)
})
describe('saving should support conditions', () => {
context('when condition check was failed', () => {
it('should throw error', async () => {
const record = TestableTable.new({ id: 22, title: 'something new' })
await record.save()
let error: Error | undefined
try {
record.generic = 'something blue'
await record.save({ conditions: { generic: 'fail' } })
} catch (ex) {
error = ex
}
expect(error).to.be.instanceOf(Error)
.with.property('name', 'ConditionalCheckFailedException')
expect(error).to.have.property('message', 'The conditional request failed')
})
})
context('when condition check was passed', () => {
it('should put item as per provided condition', async () => {
const record = TestableTable.new({ id: 22, title: 'bar' })
// save a new record, and confirm the id does not exist… useful to
// confirm you are adding a new record and not unintentionally updating an existing one
await record.save({ conditions: { id: ['not exists'] } })
const reloaded = await TestableTable.primaryKey.get({ id: 22, title: 'bar' }, { consistent: true })
expect(reloaded).to.be.instanceOf(TestableTable)
})
})
})
describe('saving should support returnValue', () => {
it('should parse the returned values', async () => {
const newRecord = TestableTable.new({
id: 99,
title: 'new record',
generic: 'before update',
unixTimestamp: new Date(),
})
await newRecord.save()
// load the record we just created
const record = TestableTable.primaryKey.fromKey({
id: 99,
title: 'new record',
})
record.generic = 'after update'
const output = await record.save({ returnOutput: true, operator: 'update', returnValues: 'ALL_OLD' })
expect(output.Attributes).to.not.be.a('undefined')
if (output.Attributes != null) {
const oldRecord = TestableTable.fromDynamo(output.Attributes)
expect(oldRecord.generic).to.eq('before update')
}
})
})
describe('deleting should support conditions', () => {
context('when condition check was failed', () => {
it('should throw error', async () => {
const record = TestableTable.new({ id: 23, title: 'something new' })
await record.save()
let error: Error | undefined
try {
await record.delete({ conditions: { id: 24 } })
} catch (ex) {
error = ex
}
expect(error).to.be.instanceOf(Error)
.with.property('name', 'ConditionalCheckFailedException')
expect(error).to.have.property('message', 'The conditional request failed')
})
})
context('when condition check was passed', () => {
it('should delete item as per provided condition', async () => {
const record = TestableTable.new({ id: 24, title: 'bar' })
// save a new record, and confirm the id does not exist… useful to
// confirm you are adding a new record and not unintentionally updating an existing one
await record.save()
await record.delete({ conditions: { id: 24 } })
const reloaded = await TestableTable.primaryKey.get(record, { consistent: true })
expect(reloaded).not.to.be.instanceOf(TestableTable)
})
})
})
it('should apply default values', () => {
const record = TestableTable.new()
expect(record.id).to.eq(1)
expect(record.defaultedString).to.eq('SomeDefault')
expect(Array.from(record.testNumberSetWithDefaults)).to.deep.eq([42, 420])
})
it('should apply default values even when undefined is given', () => {
const record = TestableTable.new({ defaultedString: undefined })
expect(record.id).to.eq(1)
expect(record.defaultedString).to.eq('SomeDefault')
expect(Array.from(record.testNumberSetWithDefaults)).to.deep.eq([42, 420])
})
it('should not apply defaults when the record is loaded from DynamoDB', () => {
const record = TestableTable.fromDynamo({})
expect(record.id).to.eq(null)
})
describe('#toJSON', () => {
it('should export to an object', () => {
const record = TestableTable.new()
expect(record.toJSON()).to.deep.eq({
id: 1,
defaultedString: 'SomeDefault',
testNumberSetWithDefaults: [42, 420],
createdAt: record.createdAt.toISOString(),
updatedAt: record.updatedAt.toISOString(),
})
})
it('should not apply defaults when the record is loaded from DynamoDB', () => {
const record = TestableTable.fromDynamo({})
expect(record.toJSON()).to.deep.eq({})
})
})
})