UNPKG

@minatojs/tests

Version:
513 lines (459 loc) 21.1 kB
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