@minatojs/tests
Version:
Test Cases for Minato
513 lines (459 loc) • 21.1 kB
text/typescript
import { $, Database } from 'minato'
import { deepEqual, omit } from 'cosmokit'
import { expect } from 'chai'
interface Bar {
id: number
text?: string
num?: number
double?: number
bool?: boolean
list?: string[]
timestamp?: Date
date?: Date
time?: Date
bigtext?: string
binary?: ArrayBuffer
bigint?: bigint
}
interface Baz {
ida: number
idb: string
value?: string
}
interface Tables {
temp2: Bar
temp3: Baz
}
function OrmOperations(database: Database<Tables>) {
database.extend('temp2', {
id: 'unsigned',
text: 'string',
num: 'integer',
double: 'double',
bool: 'boolean',
list: 'list',
timestamp: 'timestamp',
date: 'date',
time: 'time',
bigtext: 'text',
binary: 'binary',
bigint: {
type: 'string',
dump: value => value ? value.toString() : value,
load: value => value ? BigInt(value) : value,
},
}, {
autoInc: true,
indexes: ['text'],
})
database.extend('temp3', {
ida: 'unsigned',
idb: 'string',
value: 'string',
}, {
primary: ['ida', 'idb'],
unique: ['value'],
})
}
namespace OrmOperations {
const merge = <T>(a: T, b: Partial<T>): T => ({ ...a, ...b })
const magicBorn = new Date('1970/08/17')
const toBinary = (source: string) => new TextEncoder().encode(source).buffer
const barTable: Bar[] = [
{ id: 1, bool: true },
{ id: 2, text: 'pku' },
{ id: 3, num: 1989 },
{ id: 4, list: ['1', '1', '4'] },
{ id: 5, timestamp: magicBorn },
{ id: 6, date: magicBorn },
{ id: 7, time: new Date('1970-01-01 12:00:00') },
{ id: 8, binary: toBinary('hello') },
{ id: 9, bigint: BigInt(1e63) },
{ id: 10, text: 'a\b\t\f\n\r\x1a\'\"\\\`b', list: ['a\b\t\f\n\r\x1a\'\"\\\`b'] },
]
const bazTable: Baz[] = [
{ ida: 1, idb: 'a', value: 'a' },
{ ida: 2, idb: 'a', value: 'b' },
{ ida: 1, idb: 'b', value: 'c' },
{ ida: 2, idb: 'b', value: 'd' },
]
async function setup<K extends keyof Tables>(database: Database<Tables>, name: K, table: Tables[K][]) {
await database.remove(name, {})
const result: Tables[K][] = []
for (const item of table) {
result.push(await database.create(name, item as any))
}
return result
}
export const create = function Create(database: Database<Tables>) {
it('auto increment primary key', async () => {
const table = barTable.map(bar => merge(database.tables.temp2.create(), bar))
for (const index in barTable) {
const bar = await database.create('temp2', omit(barTable[index], ['id']))
barTable[index].id = bar.id
expect(bar).to.have.shape(table[index])
}
for (const obj of table) {
await expect(database.get('temp2', { id: obj.id })).to.eventually.have.shape([obj])
}
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
await database.remove('temp2', { id: table.length })
await expect(database.create('temp2', {})).to.eventually.have.shape({ id: table.length + 1 })
})
it('specify primary key', async () => {
for (const obj of bazTable) {
await expect(database.create('temp3', obj)).eventually.shape(obj)
}
for (const obj of bazTable) {
await expect(database.get('temp3', { ida: obj.ida, idb: obj.idb })).eventually.shape([obj])
}
})
it('missing primary key', async () => {
await expect(database.create('temp3', { ida: 1 })).eventually.rejected
})
it('duplicate primary key', async () => {
await expect(database.create('temp2', { id: barTable[0].id })).eventually.rejected
await expect(database.create('temp3', { ida: 1, idb: 'a' })).eventually.rejected
})
it('parallel create', async () => {
await database.remove('temp2', {})
await Promise.all([...Array(5)].map(() => database.create('temp2', {})))
const result = await database.get('temp2', {})
expect(result).length(5)
const ids = result.map(e => e.id).sort((a, b) => a - b)
const min = Math.min(...ids)
expect(ids.map(id => id - min + 1)).shape([1, 2, 3, 4, 5])
await database.remove('temp2', {})
})
it('enormous field', async () => {
const row = { id: 100, bigtext: Array(1000000).fill('a').join('') }
await database.create('temp2', row)
await expect(database.get('temp2', 100)).to.eventually.have.nested.property('0.bigtext', row.bigtext)
})
it('advanced type', async () => {
await setup(database, 'temp2', barTable)
await expect(database.create('temp2', { binary: toBinary('world') })).to.eventually.have.shape({ binary: toBinary('world') })
await expect(database.get('temp2', { binary: { $exists: true } })).to.eventually.have.shape([
{ binary: toBinary('hello') },
{ binary: toBinary('world') },
])
await expect(database.create('temp2', { bigint: 1234567891011121314151617181920n })).to.eventually.have.shape({ bigint: 1234567891011121314151617181920n })
await expect(database.get('temp2', { bigint: { $exists: true } })).to.eventually.have.shape([
{ bigint: BigInt(1e63) },
{ bigint: 1234567891011121314151617181920n },
])
})
}
export const set = function Set(database: Database<Tables>) {
it('basic support', async () => {
const table = await setup(database, 'temp2', barTable)
const data = table.find(bar => bar.timestamp)!
data.list = ['2', '3', '3']
data.text = `$'"%~\``
const magicIds = table.slice(2, 4).map((data) => {
data.list = ['2', '3', '3']
data.text = `$'"%~\``
return data.id
})
await expect(database.set('temp2', {
$or: [
{ id: magicIds },
{ timestamp: magicBorn },
],
}, { list: ['2', '3', '3'], text: `$'"%~\`` })).to.eventually.have.shape({ matched: 3 })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('null override', async () => {
const table = await setup(database, 'temp2', barTable)
const data = table.find(bar => bar.timestamp)!
data.text = null as never
await database.set('temp2', { timestamp: { $exists: true } }, { text: null })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
await expect(database.get('temp2', { text: { $exists: false } })).to.eventually.have.length(1)
})
it('noop', async () => {
const table = await setup(database, 'temp2', barTable)
await database.set('temp2', {}, {})
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
await database.set('temp2', {}, { text: undefined })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('using expressions', async () => {
const table = await setup(database, 'temp2', barTable)
table[1].num = table[1].id * 2
table[2].num = table[2].id * 2
await database.set('temp2', [table[1].id, table[2].id, 99], row => ({
num: $.multiply(2, row.id),
}))
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('using expressions in query', async () => {
const table = await setup(database, 'temp2', barTable)
table[1].num = table[1].id * 2
table[2].num = table[2].id * 2
await database.set('temp2', row => $.in(row.id, [table[1].id, table[2].id, 99]), row => ({
num: $.multiply(2, row.id),
}))
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('enormous field', async () => {
const row = await database.create('temp2', {})
row.bigtext = Array(1000000).fill('a').join('')
await database.set('temp2', row.id, { bigtext: row.bigtext })
await expect(database.get('temp2', row.id)).to.eventually.have.nested.property('0.bigtext', row.bigtext)
})
it('advanced type', async () => {
const table = await setup(database, 'temp2', barTable)
const data1 = table.find(item => item.id === 1)!
data1.binary = toBinary('world')
data1.bigint = 1234567891011121314151617181920n
await database.set('temp2', { id: 1 }, { binary: toBinary('world'), bigint: 1234567891011121314151617181920n })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
}
export const upsert = function Upsert(database: Database<Tables>) {
it('update existing records', async () => {
const table = await setup(database, 'temp2', barTable)
const data = [
{ id: table[0].id, text: 'thu' },
{ id: table[1].id, num: 1911 },
{ id: table[2].id, list: ['2', '3', '3'] },
]
data.forEach(update => {
const index = table.findIndex(obj => obj.id === update.id)
table[index] = merge(table[index], update)
})
await expect(database.upsert('temp2', data.slice(0, 2))).to.eventually.have.shape({ inserted: 0, matched: 2 })
await expect(database.upsert('temp2', data.slice(0, 2))).to.eventually.have.shape({ inserted: 0, matched: 2 })
await expect(database.upsert('temp2', data.slice(2))).to.eventually.have.shape({ inserted: 0, matched: 1 })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('insert new records', async () => {
const table = await setup(database, 'temp2', barTable)
const data = [
{ id: table[table.length - 1].id + 1, text: 'wm"lake' },
{ id: table[table.length - 1].id + 2, text: 'by\'tower' },
{ id: table[table.length - 1].id + 3, text: 'over' },
]
table.push(...data.map(bar => merge(database.tables.temp2.create(), bar)))
await expect(database.upsert('temp2', data.slice(0, 2))).to.eventually.have.shape({ inserted: 2, matched: 0 })
await expect(database.upsert('temp2', data.slice(2))).to.eventually.have.shape({ inserted: 1, matched: 0 })
await expect(database.upsert('temp2', data.slice(2))).to.eventually.have.shape({ inserted: 0, matched: 1 })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('using expressions', async () => {
const table = await setup(database, 'temp2', barTable)
const data2 = table.find(item => item.id === 2)!
const data3 = table.find(item => item.id === 3)!
const data99 = table.find(item => item.id === 99)
data2.num = data2.id * 2
data3.num = data3.num! + 3
expect(data99).to.be.undefined
table.push({ id: 99, num: 999 })
await expect(database.upsert('temp2', row => [
{ id: 2, num: $.multiply(2, row.id) },
{ id: 3, num: $.add(3, row.num) },
{ id: 99, num: 999 },
])).to.eventually.have.shape({ inserted: 1, matched: 2 })
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('using expressions with initial values', async () => {
const table = await setup(database, 'temp3', bazTable)
const data = [
{ ida: 114, idb: '514', value: 'baz' },
]
table.push(...data.map(bar => merge(database.tables.temp3.create(), bar)))
await database.upsert('temp3', row => [
{ ida: 114, idb: '514', value: $.concat(row.value, 'baz') },
])
await expect(database.get('temp3', {})).to.eventually.have.deep.members(table)
})
it('multi condition on composite primary', async () => {
const table = await setup(database, 'temp3', bazTable)
table[1].value = `$'"%~\``
table[2].value = 'cc'
table.push({ ida: 114, idb: '514', value: 'baz' })
await database.upsert('temp3', row => [
{ ida: 2, idb: 'a', value: `$'"%~\`` },
{ ida: 1, idb: 'b', value: 'cc' },
{ ida: 114, idb: '514', value: $.concat(row.value, 'baz') },
])
await expect(database.get('temp3', {})).to.eventually.have.deep.members(table)
})
it('enormous field', async () => {
const row = await database.create('temp2', {})
row.bigtext = Array(1000000).fill('a').join('')
await database.upsert('temp2', [row])
await expect(database.get('temp2', row.id)).to.eventually.have.nested.property('0.bigtext', row.bigtext)
})
it('with unique', async () => {
await setup(database, 'temp3', bazTable)
await expect(database.upsert('temp3', [
{ ida: 10, idb: 'a', value: 'e' },
{ ida: 11, idb: 'b', value: 'f' },
{ ida: 12, idb: 'c', value: 'd' },
], ['value'] as any)).to.eventually.have.shape({ inserted: 2, matched: 1 })
})
it('advanced type', async () => {
const table = await setup(database, 'temp2', barTable)
const data1 = table.find(item => item.id === 1)!
data1.binary = toBinary('world')
data1.bigint = 1234567891011121314151617181920n
table.push({ binary: toBinary('foobar'), bigint: 1234567891011121314151617181920212223n } as any)
await database.upsert('temp2', [
{ id: 1, binary: toBinary('world'), bigint: 1234567891011121314151617181920n },
{ binary: toBinary('foobar'), bigint: 1234567891011121314151617181920212223n },
])
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
}
export const remove = function Remove(database: Database<Tables>) {
it('basic support', async () => {
await setup(database, 'temp3', bazTable)
await expect(database.remove('temp3', { ida: 1, idb: 'a' })).to.eventually.have.shape({ matched: 1 })
await expect(database.get('temp3', {})).eventually.length(3)
await expect(database.remove('temp3', { ida: 1, idb: 'b', value: 'b' })).to.eventually.have.shape({ matched: 0 })
await expect(database.get('temp3', {})).eventually.length(3)
await expect(database.remove('temp3', { idb: 'b' })).to.eventually.have.shape({ matched: 2 })
await expect(database.get('temp3', {})).eventually.length(1)
await expect(database.remove('temp3', {})).to.eventually.have.shape({ matched: 1 })
await expect(database.get('temp3', {})).eventually.length(0)
})
it('advanced query', async () => {
const table = await setup(database, 'temp2', barTable)
await database.remove('temp2', { id: { $gt: table[1].id } })
await expect(database.get('temp2', {})).eventually.length(2)
await database.remove('temp2', { id: { $lte: table[1].id } })
await expect(database.get('temp2', {})).eventually.length(0)
})
}
export const stats = function Stats(database: Database<Tables>) {
it('basic support', async () => {
const stats = await database.stats()
expect(stats.size).to.be.a('number')
expect(stats.tables['temp2'].count).to.be.a('number')
expect(stats.tables['temp2'].size).to.be.a('number')
})
}
export const misc = function Misc(database: Database<Tables>) {
it('date type', async () => {
const table = await setup(database, 'temp2', barTable)
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
await expect(database.eval('temp2', row => $.max(row.timestamp))).to.eventually.deep.eq(table[4].timestamp)
await expect(database.eval('temp2', row => $.max(row.date))).to.eventually.deep.eq(table[5].date)
await expect(database.eval('temp2', row => $.max(row.time))).to.eventually.deep.eq(table[6].time)
table.push(await database.create('temp2', {
text: 'date type',
timestamp: new Date(),
date: new Date(),
time: new Date(),
}))
await expect(database.get('temp2', {})).to.eventually.have.shape(table)
})
it('$.number on date types', async () => {
await setup(database, 'temp2', barTable)
const date = new Date('1970-02-02 12:00:00')
const table = [
{ num: 191, timestamp: date },
{ num: 192, date: date },
{ num: 193, time: date },
]
await database.upsert('temp2', table)
await expect(database.eval('temp2', row => $.array($.number(row.timestamp)), { num: 191 })).to.eventually.deep.equal([+date / 1000])
date.setHours(0, 0, 0, 0)
await expect(database.eval('temp2', row => $.array($.number(row.date)), { num: 192 })).to.eventually.deep.equal([+date / 1000])
await expect(database.eval('temp2', row => $.array($.number(row.time)), { num: 193 })).to.eventually.deep.equal([43200 + date.getTimezoneOffset() * 60])
await expect(database.eval('temp2', row => $.min($.number(row.timestamp)))).to.eventually.deep.equal(0)
})
it('math functions', async () => {
const table = await setup(database, 'temp2', barTable)
table[0].double = 123.45
table[0].num = 6
await database.set('temp2', table[0].id, { double: table[0].double, num: table[0].num })
await expect(database.eval('temp2', row => $.max($.abs($.sub(0, row.double))), table[0].id)).to.eventually.deep.eq(table[0].double)
await expect(database.eval('temp2', row => $.max($.mod(row.double, row.num)), table[0].id)).to.eventually.deep.eq(table[0].double % table[0].num)
await expect(database.eval('temp2', row => $.max($.ceil(row.double)), table[0].id)).to.eventually.deep.eq(Math.ceil(table[0].double))
await expect(database.eval('temp2', row => $.max($.floor(row.double)), table[0].id)).to.eventually.deep.eq(Math.floor(table[0].double))
await expect(database.eval('temp2', row => $.max($.round(row.double)), table[0].id)).to.eventually.deep.eq(Math.round(table[0].double))
await expect(database.eval('temp2', row => $.max($.exp(row.double)), table[0].id)).to.eventually.deep.eq(Math.exp(table[0].double))
await expect(database.eval('temp2', row => $.max($.log(row.double)), table[0].id)).to.eventually.deep.eq(Math.log(table[0].double))
await expect(database.eval('temp2', row => $.max($.floor($.log(row.double, 3))), table[0].id)).to.eventually.deep.eq(Math.floor(Math.log(table[0].double) / Math.log(3)))
await expect(database.eval('temp2', row => $.max($.floor($.pow(row.double, row.num))), table[0].id))
.to.eventually.deep.eq(Math.floor(Math.pow(table[0].double, table[0].num)))
})
it('$.random', async () => {
await setup(database, 'temp2', barTable)
await expect(database.eval('temp2', row => $.max($.random()))).to.eventually.gt(0).lt(1)
})
}
export const index = function Index(database: Database<Tables>) {
it('basic support', async () => {
const driver = Object.values(database.drivers)[0]
const index = {
unique: false,
keys: {
num: 'asc',
timestamp: 'asc',
},
} as const
await driver.createIndex('temp2', index)
let indexes = await driver.getIndexes('temp2')
let added = indexes.find(ind => deepEqual(omit(ind, ['name']), index))
expect(added).to.not.be.undefined
await driver.dropIndex('temp2', added!.name!)
indexes = await driver.getIndexes('temp2')
added = indexes.find(ind => deepEqual(omit(ind, ['name']), index))
expect(added).to.be.undefined
})
it('named', async () => {
const driver = Object.values(database.drivers)[0]
const index = {
name: 'index_unique:temp2:num_asc+timestamp_asc',
unique: true,
keys: {
num: 'asc',
timestamp: 'asc',
},
} as const
await driver.createIndex('temp2', index)
let indexes = await driver.getIndexes('temp2')
let added = indexes.find(ind => deepEqual(ind, index))
expect(added).to.not.be.undefined
await driver.dropIndex('temp2', added!.name!)
indexes = await driver.getIndexes('temp2')
added = indexes.find(ind => deepEqual(ind, index))
expect(added).to.be.undefined
})
it('extend', async () => {
const driver = Object.values(database.drivers)[0]
const index = {
unique: false,
keys: {
text: 'asc',
},
} as const
const indexes = await driver.getIndexes('temp2')
const existed = indexes.find(ind => deepEqual(omit(ind, ['name']), index))
expect(existed).to.not.be.undefined
})
}
export const drop = function Drop(database: Database<Tables>) {
it('make coverage happy', async () => {
// @ts-expect-error
await expect(database.drop('unknown')).to.be.rejected
})
}
export function subquery(database: Database<Tables>) {
it('set query', async () => {
await setup(database, 'temp2', barTable)
await database.set('temp2', row => $.eq(row.text, database.select('temp2', r => $.eq(r.id, 2)).evaluate(r => $.max(r.text))), { text: 'ok' })
await expect(database.get('temp2', 2)).to.eventually.have.shape([{ text: 'ok' }])
})
it('set update', async () => {
const table = await setup(database, 'temp2', barTable)
await database.set('temp2', 1, row => ({ text: database.select('temp2', r => $.eq(r.id, $.add(1, row.id))).evaluate(r => $.max(r.text)) }))
await expect(database.get('temp2', 1)).to.eventually.have.shape([{ text: table[1].text }])
})
}
}
export default OrmOperations