@minatojs/tests
Version:
Test Cases for Minato
1,787 lines (1,612 loc) • 51.3 kB
text/typescript
import { $, Database, Relation } from 'minato'
import { expect } from 'chai'
import { setup } from './utils'
interface User {
id: number
value?: number
profile?: Profile
posts?: Post[]
successor?: Record<string, any> & { id: number }
predecessor?: Record<string, any> & { id: number }
}
interface Profile {
id: number
name?: string
user?: User
}
interface Post {
id2: number
score?: number
author?: User
content?: string
tags?: Tag[]
_tags?: Post2Tag[]
}
interface Tag {
id: number
name: string
posts?: Post[]
_posts?: Post2Tag[]
}
interface Post2Tag {
post?: Post & { id: number }
tag?: Tag & { id: number }
}
interface Login {
id: string
platform: string
name?: string
guilds?: Guild[]
syncs?: GuildSync[]
}
interface Guild {
id: string
platform2: string
name?: string
logins?: Login[]
members?: Member[]
syncs?: GuildSync[]
}
interface Member {
guild: Guild
user: Login
name: string
}
interface GuildSync {
platform: string
syncAt?: number
guild?: Guild
login?: Login
}
interface Tables {
user: User
profile: Profile
post: Post
tag: Tag
post2tag: Post2Tag
guildSync: GuildSync
login: Login
guild: Guild
member: Member
}
function RelationTests(database: Database<Tables>) {
database.extend('user', {
id: 'unsigned',
value: 'integer',
successor: {
type: 'oneToOne',
table: 'user',
target: 'predecessor',
},
}, {
autoInc: true,
})
database.extend('profile', {
id: 'unsigned',
name: 'string',
user: {
type: 'oneToOne',
table: 'user',
target: 'profile',
},
}, {
unique: [['user', 'name']]
})
database.extend('post', {
id2: 'unsigned',
score: 'unsigned',
content: 'string',
author: {
type: 'manyToOne',
table: 'user',
target: 'posts',
},
}, {
autoInc: true,
primary: 'id2',
})
database.extend('tag', {
id: 'unsigned',
name: 'string',
posts: {
type: 'manyToMany',
table: 'post',
target: 'tags',
},
}, {
autoInc: true,
})
database.extend('post2tag', {
'post.id': 'unsigned',
'tag.id': 'unsigned',
post: {
type: 'manyToOne',
table: 'post',
target: '_tags',
},
tag: {
type: 'manyToOne',
table: 'tag',
target: '_posts',
},
}, {
primary: ['post.id', 'tag.id'],
})
database.extend('login', {
id: 'string',
platform: 'string(64)',
name: 'string',
}, {
primary: ['id', 'platform'],
})
database.extend('guild', {
id: 'string',
platform2: 'string(64)',
name: 'string',
logins: {
type: 'manyToMany',
table: 'login',
target: 'guilds',
shared: { platform2: 'platform' },
},
}, {
primary: ['id', 'platform2'],
})
database.extend('guildSync', {
syncAt: 'unsigned',
platform: 'string',
guild: {
type: 'manyToOne',
table: 'guild',
target: 'syncs',
fields: ['guild.id', 'platform'],
},
login: {
type: 'manyToOne',
table: 'login',
target: 'syncs',
fields: ['login.id', 'platform'],
},
}, {
primary: ['guild', 'login'],
})
database.extend('member', {
guild: {
type: 'manyToOne',
table: 'guild',
target: 'members',
},
user: {
type: 'manyToOne',
table: 'login',
},
name: 'string',
}, {
primary: ['user', 'guild'],
})
async function setupAutoInc<S, K extends keyof S & string>(database: Database<S>, name: K, length: number) {
await database.upsert(name, Array(length).fill({}))
await database.remove(name, {})
}
before(async () => {
await setupAutoInc(database, 'user', 3)
await setupAutoInc(database, 'post', 3)
await setupAutoInc(database, 'tag', 3)
})
}
namespace RelationTests {
const userTable: User[] = [
{ id: 1, value: 0 },
{ id: 2, value: 1, successor: { id: 1 } },
{ id: 3, value: 2 },
]
const profileTable: Profile[] = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cat' },
]
const postTable: Post[] = [
{ id2: 1, content: 'A1', author: { id: 1 } },
{ id2: 2, content: 'B2', author: { id: 1 } },
{ id2: 3, content: 'C3', author: { id: 2 } },
]
const tagTable: Tag[] = [
{ id: 1, name: 'X' },
{ id: 2, name: 'Y' },
{ id: 3, name: 'Z' },
]
const post2TagTable: Post2Tag[] = [
{ post: { id: 1 }, tag: { id: 1 } },
{ post: { id: 1 }, tag: { id: 2 } },
{ post: { id: 2 }, tag: { id: 1 } },
{ post: { id: 2 }, tag: { id: 3 } },
{ post: { id: 3 }, tag: { id: 3 } },
] as any
const post2TagTable2: Post2Tag[] = [
{ post: { id2: 1 }, tag: { id: 1 } },
{ post: { id2: 1 }, tag: { id: 2 } },
{ post: { id2: 2 }, tag: { id: 1 } },
{ post: { id2: 2 }, tag: { id: 3 } },
{ post: { id2: 3 }, tag: { id: 3 } },
] as any
export interface RelationOptions {
nullableComparator?: boolean
}
export function select(database: Database<Tables>, options: RelationOptions = {}) {
const { nullableComparator = true } = options
it('basic support', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
await expect(database.get('profile', {}, ['user'])).to.eventually.have.shape(
profiles.map(profile => ({
user: users.find(user => user.id === profile.id),
})),
)
await expect(database.get('user', {}, { include: { profile: true, posts: true } })).to.eventually.have.shape(
users.map(user => ({
...user,
profile: profiles.find(profile => profile.id === user.id),
posts: posts.filter(post => post.author?.id === user.id),
})),
)
await expect(database.select('post', {}, { author: true }).execute()).to.eventually.have.shape(
posts.map(post => ({
...post,
author: users.find(user => user.id === post.author?.id),
})),
)
})
nullableComparator && it('self relation', async () => {
const users = await setup(database, 'user', userTable)
await expect(database.select('user', {}, { successor: true }).execute()).to.eventually.have.shape(
users.map(user => ({
...user,
successor: users.find(successor => successor.id === user.successor?.id) ?? null,
})),
)
})
it('nested reads', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
await expect(database.select('user', {}, { posts: { author: { successor: false } } }).execute()).to.eventually.have.shape(
users.map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id).map(post => ({
...post,
author: users.find(user => user.id === post.author?.id),
})),
})),
)
await expect(database.select('profile', {}, { user: { posts: { author: true } } }).execute()).to.eventually.have.shape(
profiles.map(profile => ({
...profile,
user: {
...(users.find(user => user.id === profile.id)),
posts: posts.filter(post => post.author?.id === profile.id).map(post => ({
...post,
author: users.find(user => user.id === profile.id),
})),
},
})),
)
await expect(database.select('post', {}, { author: { profile: true } }).execute()).to.eventually.have.shape(
posts.map(post => ({
...post,
author: {
...users.find(user => user.id === post.author?.id),
profile: profiles.find(profile => profile.id === post.author?.id),
},
})),
)
})
it('manyToMany', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
const tags = await setup(database, 'tag', tagTable)
const post2tags = await setup(database, 'post2tag', post2TagTable)
const re = await setup(database, Relation.buildAssociationTable('post', 'tag') as any, post2TagTable2)
// explicit manyToMany
await expect(database.select('post', {}, { _tags: { tag: { _posts: { post: true } } } }).execute()).to.eventually.be.fulfilled
await expect(database.select('post', {}, { tags: { posts: true } }).execute()).to.eventually.have.shape(
posts.map(post => ({
...post,
tags: post2tags.filter(p2t => p2t.post?.id === post.id2)
.map(p2t => tags.find(tag => tag.id === p2t.tag?.id))
.filter(tag => tag)
.map(tag => ({
...tag,
posts: post2tags.filter(p2t => p2t.tag?.id === tag!.id).map(p2t => posts.find(post => post.id2 === p2t.post?.id)),
})),
})),
)
})
}
export function query(database: Database<Tables>) {
it('oneToOne / manyToOne', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
await expect(database.get('user', {
profile: {
user: {
id: 1,
},
},
})).to.eventually.have.shape(users.slice(0, 1).map(user => ({
...user,
profile: profiles.find(profile => profile.id === user.id),
})))
await expect(database.get('user', row => $.query(row, {
profile: r => $.eq(r.id, row.id),
}))).to.eventually.have.shape(users.map(user => ({
...user,
profile: profiles.find(profile => profile.id === user.id),
})))
await expect(database.get('user', {
profile: {
user: {
value: 1,
},
},
})).to.eventually.have.shape(users.slice(1, 2).map(user => ({
...user,
profile: profiles.find(profile => profile.id === user.id),
})))
await expect(database.get('post', {
author: {
id: 1,
},
tags: {
$every: {},
},
})).to.eventually.have.shape(posts.map(post => ({
...post,
author: users.find(user => post.author?.id === user.id),
})).filter(post => post.author?.id === 1))
await expect(database.get('post', {
author: {
id: 1,
value: 1,
},
})).to.eventually.have.length(0)
})
it('oneToMany', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
await expect(database.get('user', {
posts: {
$some: {
author: {
id: 1,
},
},
},
})).to.eventually.have.shape(users.slice(0, 1).map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
await expect(database.get('user', {
posts: {
$some: row => $.eq(row.id2, 1),
},
})).to.eventually.have.shape(users.slice(0, 1).map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
await expect(database.get('user', {
posts: {
$none: {
author: {
id: 1,
},
},
},
})).to.eventually.have.shape(users.slice(1).map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
await expect(database.get('user', {
posts: {
$every: {
author: {
id: 1,
},
},
},
})).to.eventually.have.shape([users[0], users[2]].map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
await expect(database.get('user', {
posts: {
$or: [
{
$some: {
author: {
id: 1,
},
},
},
{
$none: {
author: {},
},
},
],
},
})).to.eventually.have.shape([users[0], users[2]].map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
await expect(database.get('user', {
posts: {
$not: {
$some: {
author: {
id: 1,
},
},
},
},
})).to.eventually.have.shape([users[1], users[2]].map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
})
it('manyToMany', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
const tags = await setup(database, 'tag', tagTable)
const post2tags = await setup(database, 'post2tag', post2TagTable)
const re = await setup(database, Relation.buildAssociationTable('post', 'tag') as any, post2TagTable2)
await expect(database.get('post', {
tags: {
$some: {
id: 1,
},
},
})).to.eventually.have.shape(posts.slice(0, 2).map(post => ({
...post,
tags: post2tags.filter(p2t => p2t.post?.id === post.id2)
.map(p2t => tags.find(tag => tag.id === p2t.tag?.id))
.filter(tag => tag),
})))
await expect(database.get('post', {
tags: {
$none: {
id: 1,
},
},
})).to.eventually.have.shape(posts.slice(2).map(post => ({
...post,
tags: post2tags.filter(p2t => p2t.post?.id === post.id2)
.map(p2t => tags.find(tag => tag.id === p2t.tag?.id))
.filter(tag => tag),
})))
await expect(database.get('post', {
tags: {
$every: {
id: 3,
},
},
})).to.eventually.have.shape(posts.slice(2, 3).map(post => ({
...post,
tags: post2tags.filter(p2t => p2t.post?.id === post.id2)
.map(p2t => tags.find(tag => tag.id === p2t.tag?.id))
.filter(tag => tag),
})))
await expect(database.get('post', {
tags: {
$some: 1,
$none: [3],
$every: {},
},
})).to.eventually.have.shape(posts.slice(0, 1).map(post => ({
...post,
tags: post2tags.filter(p2t => p2t.post?.id === post.id2)
.map(p2t => tags.find(tag => tag.id === p2t.tag?.id))
.filter(tag => tag),
})))
})
it('nested query', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
const tags = await setup(database, 'tag', tagTable)
const post2tags = await setup(database, 'post2tag', post2TagTable)
const re = await setup(database, Relation.buildAssociationTable('post', 'tag') as any, post2TagTable2)
await expect(database.get('user', {
posts: {
$some: {
tags: {
$some: {
id: 1,
},
},
},
},
})).to.eventually.have.shape([users[0]].map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id),
})))
await expect(database.get('tag', {
posts: {
$some: {
author: {
id: 2,
},
},
},
})).to.eventually.have.shape([tags[2]].map(tag => ({
...tag,
posts: post2tags.filter(p2t => p2t.tag?.id === tag.id)
.map(p2t => posts.find(post => post.id2 === p2t.post?.id))
.filter(post => post),
})))
})
it('omit query', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
await expect(database.get('user', { id: 2 }, ['id', 'profile'])).to.eventually.have.shape(
[users[1]].map(user => ({
id: user.id,
profile: profiles.find(profile => user.id === profile.id),
})),
)
})
it('existence', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
await setup(database, 'post', postTable)
await expect(database.select('user', { successor: null }, null).execute()).to.eventually.have.shape([
{ id: 1 },
{ id: 3 },
])
await expect(database.select('user', { predecessor: null }, null).execute()).to.eventually.have.shape([
{ id: 2 },
{ id: 3 },
])
await database.set('user', 1, { profile: null })
await expect(database.select('user', { profile: null }, null).execute()).to.eventually.have.shape([
{ id: 1 },
])
await database.set('user', 2, {
posts: {
$disconnect: {
id2: 3,
},
},
})
await expect(database.select('post', { author: null }, null).execute()).to.eventually.have.shape([
{ id2: 3 },
])
await expect(database.select('user', {
posts: {
$every: {
author: null,
},
},
}, null).execute()).to.eventually.have.shape([
{ id: 2 },
{ id: 3 },
])
})
it('manyToOne fallback', async () => {
await setup(database, 'user', [])
await setup(database, 'profile', [])
await setup(database, 'post', [])
await database.create('post', {
id2: 1,
content: 'new post',
author: {
$literal: {
id: 2,
},
},
})
await expect(database.get('post', 1, ['author'])).to.eventually.have.shape([{
author: {
id: 2,
},
}])
await database.create('user', {
id: 2,
value: 123,
})
await expect(database.get('post', 1, ['author'])).to.eventually.have.shape([{
author: {
id: 2,
value: 123,
},
}])
})
}
export function create(database: Database<Tables>, options: RelationOptions = {}) {
const { nullableComparator = true } = options
it('basic support', async () => {
await setup(database, 'user', [])
await setup(database, 'profile', [])
await setup(database, 'post', [])
for (const user of userTable) {
await expect(database.create('user', {
...user,
profile: {
...profileTable.find(profile => profile.id === user.id)!,
},
posts: postTable.filter(post => post.author?.id === user.id),
})).to.eventually.have.shape(user)
}
await expect(database.select('profile', {}, { user: true }).execute()).to.eventually.have.shape(
profileTable.map(profile => ({
...profile,
user: userTable.find(user => user.id === profile.id),
})),
)
await expect(database.select('user', {}, { profile: true, posts: true }).execute()).to.eventually.have.shape(
userTable.map(user => ({
...user,
profile: profileTable.find(profile => profile.id === user.id),
posts: postTable.filter(post => post.author?.id === user.id),
})),
)
})
nullableComparator && it('nullable oneToOne', async () => {
await setup(database, 'user', [])
await database.create('user', {
id: 1,
value: 1,
successor: {
$create: {
id: 2,
value: 2,
},
},
predecessor: {
$create: {
id: 6,
value: 6,
},
},
})
await expect(database.select('user', {}, { successor: true }).orderBy('id').execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: { id: 2, value: 2 } },
{ id: 2, value: 2, successor: null },
{ id: 6, value: 6, successor: { id: 1, value: 1 } },
])
await database.create('user', {
id: 3,
value: 3,
predecessor: {
$upsert: {
id: 4,
value: 4,
},
},
successor: {
$upsert: {
id: 6,
value: 6,
},
},
})
await expect(database.select('user', {}, { successor: true }).orderBy('id').execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: { id: 2, value: 2 } },
{ id: 2, value: 2, successor: null },
{ id: 3, value: 3, successor: { id: 6, value: 6 } },
{ id: 4, value: 4, successor: { id: 3, value: 3 } },
{ id: 6, value: 6 },
])
await database.remove('user', [2, 4])
await database.create('user', {
id: 2,
value: 2,
successor: {
$connect: {
id: 1,
},
},
})
await database.create('user', {
id: 4,
value: 4,
predecessor: {
$connect: {
id: 3,
},
},
})
await database.create('user', {
id: 5,
value: 5,
successor: {
$connect: {
value: 3,
},
},
})
await expect(database.select('user', {}, { successor: true }).orderBy('id').execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: { id: 2, value: 2 } },
{ id: 2, value: 2, successor: { id: 1, value: 1 } },
{ id: 3, value: 3, successor: { id: 4, value: 4 } },
{ id: 4, value: 4, successor: null },
{ id: 5, value: 5, successor: { id: 3, value: 3 } },
{ id: 6, value: 6, successor: { id: 1, value: 1 } },
])
})
it('oneToMany', async () => {
await setup(database, 'user', [])
await setup(database, 'profile', [])
await setup(database, 'post', [])
for (const user of userTable) {
await database.create('user', {
...userTable.find(u => u.id === user.id)!,
posts: postTable.filter(post => post.author?.id === user.id),
})
}
await expect(database.select('user', {}, { posts: true }).execute()).to.eventually.have.shape(
userTable.map(user => ({
...user,
posts: postTable.filter(post => post.author?.id === user.id),
})),
)
})
it('upsert / connect oneToMany / manyToOne', async () => {
await setup(database, 'user', [])
await setup(database, 'profile', [])
await setup(database, 'post', [])
await database.create('user', {
id: 1,
value: 1,
posts: {
$upsert: [
{
id2: 1,
content: 'post1',
},
{
id2: 2,
content: 'post2',
},
],
},
})
await expect(database.select('user', 1, { posts: true }).execute()).to.eventually.have.shape([
{
id: 1,
value: 1,
posts: [
{ id2: 1, content: 'post1' },
{ id2: 2, content: 'post2' },
],
},
])
await database.create('user', {
id: 2,
value: 2,
posts: {
$connect: {
id2: 1,
},
$create: [
{
id2: 3,
content: 'post3',
author: {
$upsert: {
id: 2,
value: 3,
},
},
},
{
id2: 4,
content: 'post4',
author: {
$connect: {
id: 1,
},
},
},
],
},
})
await expect(database.select('user', {}, { posts: true }).execute()).to.eventually.have.shape([
{
id: 1,
value: 1,
posts: [
{ id2: 2, content: 'post2' },
{ id2: 4, content: 'post4' },
],
},
{
id: 2,
value: 3,
posts: [
{ id2: 1, content: 'post1' },
{ id2: 3, content: 'post3' },
],
},
])
})
it('manyToOne', async () => {
const users = await setup(database, 'user', [])
await setup(database, 'post', [])
users.push({ id: 1, value: 2 })
await database.create('post', {
id2: 1,
content: 'post2',
author: {
$create: {
id: 1,
value: 2,
},
},
})
await expect(database.get('user', {})).to.eventually.have.shape(users)
users[0].value = 3
await database.create('post', {
id2: 2,
content: 'post3',
author: {
$create: {
id: 1,
value: 3,
},
},
})
await expect(database.get('user', {})).to.eventually.have.shape(users)
await database.create('post', {
id2: 3,
content: 'post4',
author: {
id: 1,
},
})
await expect(database.get('user', {})).to.eventually.have.shape(users)
await expect(database.get('post', {}, { include: { author: true } })).to.eventually.have.shape([
{ id2: 1, content: 'post2', author: { id: 1, value: 3 } },
{ id2: 2, content: 'post3', author: { id: 1, value: 3 } },
{ id2: 3, content: 'post4', author: { id: 1, value: 3 } },
])
})
it('manyToMany', async () => {
await setup(database, 'user', [])
await setup(database, 'post', [])
await setup(database, 'tag', [])
await setup(database, Relation.buildAssociationTable('post', 'tag') as any, [])
for (const user of userTable) {
await database.create('user', {
...userTable.find(u => u.id === user.id)!,
posts: postTable.filter(post => post.author?.id === user.id).map(post => ({
...post,
tags: {
$upsert: post2TagTable.filter(p2t => p2t.post?.id === post.id2).map(p2t => tagTable.find(tag => tag.id === p2t.tag?.id)).filter(x => !!x),
},
})),
})
}
await expect(database.select('user', {}, { posts: { tags: true } }).execute()).to.eventually.have.shape(
userTable.map(user => ({
...user,
posts: postTable.filter(post => post.author?.id === user.id).map(post => ({
...post,
tags: post2TagTable.filter(p2t => p2t.post?.id === post.id2).map(p2t => tagTable.find(tag => tag.id === p2t.tag?.id)),
})),
})),
)
})
it('manyToMany expr', async () => {
await setup(database, 'user', [])
await setup(database, 'post', [])
await setup(database, 'tag', [])
await setup(database, Relation.buildAssociationTable('post', 'tag') as any, [])
await database.create('post', {
id2: 1,
content: 'post1',
author: {
$create: {
id: 1,
value: 1,
},
},
tags: {
$create: [
{
name: 'tag1',
},
{
name: 'tag2',
},
],
},
})
await database.create('post', {
id2: 2,
content: 'post2',
author: {
$connect: {
id: 1,
},
},
tags: {
$connect: {
name: 'tag1',
},
},
})
await expect(database.select('user', {}, { posts: { tags: true } }).execute()).to.eventually.have.shape([
{
id: 1,
value: 1,
posts: [
{
id2: 1,
content: 'post1',
tags: [
{ name: 'tag1' },
{ name: 'tag2' },
],
},
{
id2: 2,
content: 'post2',
tags: [
{ name: 'tag1' },
],
},
],
},
])
})
it('explicit manyToMany', async () => {
await setup(database, 'login', [])
await setup(database, 'guild', [])
await setup(database, 'guildSync', [])
await database.create('login', {
id: '1',
platform: 'sandbox',
name: 'Bot1',
syncs: {
$create: [
{
syncAt: 123,
guild: {
$upsert: { id: '1', platform2: 'sandbox', name: 'Guild1' },
},
},
],
},
})
await database.upsert('guild', [
{ id: '2', platform2: 'sandbox', name: 'Guild2' },
{ id: '3', platform2: 'sandbox', name: 'Guild3' },
])
await database.create('login', {
id: '2',
platform: 'sandbox',
name: 'Bot2',
syncs: {
$create: [
{
syncAt: 123,
guild: {
$connect: { id: '2' },
},
},
],
},
})
await expect(database.get('login', {
platform: 'sandbox',
}, {
include: { syncs: { guild: true } },
})).to.eventually.have.shape([
{
id: '1',
platform: 'sandbox',
name: 'Bot1',
syncs: [
{
syncAt: 123,
guild: { id: '1', platform2: 'sandbox', name: 'Guild1' },
},
],
},
{
id: '2',
platform: 'sandbox',
name: 'Bot2',
syncs: [
{
syncAt: 123,
guild: { id: '2', platform2: 'sandbox', name: 'Guild2' },
},
],
},
])
})
}
export function modify(database: Database<Tables>, options: RelationOptions = {}) {
const { nullableComparator = true } = options
it('oneToOne / manyToOne', async () => {
const users = await setup(database, 'user', userTable)
const profiles = await setup(database, 'profile', profileTable)
await setup(database, 'post', postTable)
profiles.splice(2, 1)
await database.set('user', 3, {
profile: null,
})
await expect(database.get('profile', {})).to.eventually.have.deep.members(profiles)
profiles.push(database.tables['profile'].create({ id: 3, name: 'Reborn' }))
await database.set('user', 3, {
profile: {
name: 'Reborn',
},
})
await expect(database.get('profile', {})).to.eventually.have.deep.members(profiles)
users[0].value = 99
await database.set('post', 1, {
author: {
value: 99,
},
})
await expect(database.get('user', {})).to.eventually.have.deep.members(users)
profiles.splice(2, 1)
await database.set('user', 3, {
profile: null,
})
await expect(database.get('profile', {})).to.eventually.have.deep.members(profiles)
users.push({ id: 100, value: 200, successor: { id: undefined } } as any)
await database.set('post', 1, {
author: {
id: 100,
value: 200,
},
})
await expect(database.get('user', {})).to.eventually.have.deep.members(users)
})
it('oneToOne expr', async () => {
await setup(database, 'user', [])
await setup(database, 'profile', [])
await setup(database, 'post', [])
await database.create('user', {
id: 1,
value: 0,
})
await database.set('user', 1, {
profile: {
$create: {
name: 'Apple',
},
},
})
await expect(database.select('user', {}, { profile: true }).execute()).to.eventually.have.shape(
[{ id: 1, value: 0, profile: { name: 'Apple' } }],
)
await database.set('user', 1, {
profile: {
$upsert: [{
name: 'Apple2',
}],
},
})
await expect(database.select('user', {}, { profile: true }).execute()).to.eventually.have.shape(
[{ id: 1, value: 0, profile: { name: 'Apple2' } }],
)
await database.set('user', 1, {
profile: {
$set: r => ({
name: $.concat(r.name, '3'),
}),
},
})
await expect(database.select('user', {}, { profile: true }).execute()).to.eventually.have.shape(
[{ id: 1, value: 0, profile: { name: 'Apple23' } }],
)
})
nullableComparator && it('nullable oneToOne', async () => {
await setup(database, 'user', [])
await database.upsert('user', [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
])
await database.set('user', 1, {
successor: {
$upsert: {
id: 2,
},
},
predecessor: {
$upsert: {
id: 2,
},
},
})
await database.set('user', 3, {
successor: {
$create: {
id: 4,
value: 4,
},
},
predecessor: {
$connect: {
id: 4,
},
},
})
await expect(database.select('user', {}, { successor: true }).execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: { id: 2, value: 2 } },
{ id: 2, value: 2, successor: { id: 1, value: 1 } },
{ id: 3, value: 3, successor: { id: 4, value: 4 } },
{ id: 4, value: 4, successor: { id: 3, value: 3 } },
])
await database.set('user', [1, 2], {
successor: null,
})
await expect(database.select('user', {}, { successor: true }).execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: null },
{ id: 2, value: 2, successor: null },
{ id: 3, value: 3, successor: { id: 4, value: 4 } },
{ id: 4, value: 4, successor: { id: 3, value: 3 } },
])
await database.set('user', 3, {
predecessor: {
$disconnect: {},
},
successor: {
$disconnect: {},
},
})
await expect(database.select('user', {}, { successor: true }).execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: null },
{ id: 2, value: 2, successor: null },
{ id: 3, value: 3, successor: null },
{ id: 4, value: 4, successor: null },
])
await database.set('user', 2, {
predecessor: {
$connect: {
id: 3,
},
},
successor: {
$connect: {
id: 1
},
},
})
await expect(database.select('user', {}, { successor: true }).execute()).to.eventually.have.shape([
{ id: 1, value: 1, successor: null },
{ id: 2, value: 2, successor: { id: 1, value: 1 } },
{ id: 3, value: 3, successor: { id: 2, value: 2 } },
{ id: 4, value: 4, successor: null },
])
})
nullableComparator && it('set null on oneToOne', async () => {
await setup(database, 'user', [])
for (const user of [
{ id: 1, value: 1, profile: { name: 'A' } },
{ id: 2, value: 2, profile: { name: 'B' } },
{ id: 3, value: 3, profile: { name: 'B' } },
]) {
await database.create('user', user)
}
await database.set('user', 1, {
profile: null,
})
await expect(database.select('user', {}, { profile: true }).execute()).to.eventually.have.shape([
{ id: 1, value: 1, profile: null },
{ id: 2, value: 2, profile: { name: 'B' } },
{ id: 3, value: 3, profile: { name: 'B' } },
])
await expect(database.set('profile', 3, {
user: null,
})).to.be.eventually.rejected
})
nullableComparator && it('manyToOne expr', async () => {
await setup(database, 'user', [])
await setup(database, 'profile', [])
await setup(database, 'post', [])
await database.create('post', {
id2: 1,
content: 'Post1',
})
await database.set('post', 1, {
author: {
$upsert: {
id: 1,
value: 0,
},
},
})
await database.set('post', 1, {
author: {
$set: _ => ({
profile: {
name: 'Apple',
},
}),
},
})
await expect(database.select('user', {}, { posts: true, profile: true }).execute()).to.eventually.have.shape(
[{ id: 1, value: 0, profile: { name: 'Apple' }, posts: [{ id2: 1, content: 'Post1' }] }],
)
await database.set('post', 1, {
author: {
$set: r => ({
value: 123,
}),
},
})
await expect(database.select('user', {}, { posts: true, profile: true }).execute()).to.eventually.have.shape(
[{ id: 1, value: 123, profile: { name: 'Apple' }, posts: [{ id2: 1, content: 'Post1' }] }],
)
await database.set('post', 1, {
author: null,
})
await expect(database.select('user', {}, { posts: true, profile: true }).execute()).to.eventually.have.shape(
[{ id: 1, value: 123, profile: { name: 'Apple' }, posts: [] }],
)
await database.set('post', 1, {
author: {
value: 999,
profile: { name: 'Banana' },
},
})
await expect(database.select('user', {}, { posts: true, profile: true }).execute()).to.eventually.have.shape([
{ id: 1, value: 123, profile: { name: 'Apple' }, posts: [] },
{ value: 999, profile: { name: 'Banana' }, posts: [{ id2: 1, content: 'Post1' }] },
])
})
it('create oneToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
posts.push(database.tables['post'].create({ id2: 4, author: { id: 2 }, content: 'post1' }))
posts.push(database.tables['post'].create({ id2: 5, author: { id: 2 }, content: 'post2' }))
posts.push(database.tables['post'].create({ id2: 6, author: { id: 2 }, content: 'post1' }))
posts.push(database.tables['post'].create({ id2: 7, author: { id: 2 }, content: 'post2' }))
await database.set('user', 2, {
posts: {
$create: [
{ id2: 4, content: 'post1' },
{ id2: 5, content: 'post2' },
],
$upsert: [
{ id2: 6, content: 'post1' },
{ id2: 7, content: 'post2' },
],
},
})
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
posts.push(database.tables['post'].create({ id2: 101, author: { id: 1 }, content: 'post101' }))
await database.set('user', 1, row => ({
value: $.add(row.id, 98),
posts: {
$create: { id2: 101, content: 'post101' },
},
}))
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
})
it('set oneToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
posts[0].score = 2
posts[1].score = 3
await database.set('user', 1, row => ({
posts: {
$set: r => ({
score: $.add(row.id, r.id2),
}),
},
}))
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
posts[0].score = 12
posts[1].score = 13
await database.set('user', 1, row => ({
posts: {
$set: [
{
where: { score: { $gt: 2 } },
update: r => ({ score: $.add(r.score, 10) }),
},
{
where: r => $.eq(r.score, 2),
update: r => ({ score: $.add(r.score, 10) }),
},
],
},
}))
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
})
nullableComparator && it('delete oneToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
posts.splice(0, 1)
await database.set('user', {}, row => ({
posts: {
$remove: r => $.eq(r.id2, row.id),
},
}))
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
await database.set('post', 2, {
author: {
$remove: {},
$connect: { id: 2 },
},
})
await database.set('post', 3, {
author: {
$disconnect: {},
},
})
await expect(database.get('user', {}, { include: { posts: true } })).to.eventually.have.shape([
{
id: 2,
posts: [
{ id2: 2 },
],
},
{
id: 3,
posts: [],
},
])
})
it('override oneToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
const posts = await setup(database, 'post', postTable)
posts[0].score = 2
posts[1].score = 3
await database.set('user', 1, row => ({
posts: posts.slice(0, 2),
}))
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
posts[0].score = 4
posts[1].score = 5
await database.set('user', 1, {
posts: posts.slice(0, 2),
})
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
await database.set('user', 1, {
posts: [],
})
await expect(database.get('post', {})).to.eventually.have.length(posts.length - 2)
})
nullableComparator && it('connect / disconnect oneToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
await setup(database, 'post', postTable)
await database.set('user', 1, {
posts: {
$disconnect: {},
$connect: { id2: 3 },
},
})
await expect(database.get('user', 1, ['posts'])).to.eventually.have.shape([{
posts: [
{ id2: 3 },
],
}])
})
it('modify manyToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
await setup(database, 'post', postTable)
await setup(database, 'tag', [])
await setup(database, Relation.buildAssociationTable('post', 'tag') as any, [])
await database.set('post', 2, {
tags: {
$create: {
id: 1,
name: 'Tag1',
},
$upsert: [
{
id: 2,
name: 'Tag2',
},
{
id: 3,
name: 'Tag3',
},
],
},
})
await expect(database.get('post', 2, ['tags'])).to.eventually.have.nested.property('[0].tags').with.shape([
{ id: 1, name: 'Tag1' },
{ id: 2, name: 'Tag2' },
{ id: 3, name: 'Tag3' },
])
await database.set('post', 2, row => ({
tags: {
$set: r => ({
name: $.concat(r.name, row.content, '2'),
}),
$remove: {
id: 3,
},
},
}))
await expect(database.get('post', 2, ['tags'])).to.eventually.have.nested.property('[0].tags').with.shape([
{ id: 1, name: 'Tag1B22' },
{ id: 2, name: 'Tag2B22' },
])
await database.set('post', 2, {
tags: [
{ id: 1, name: 'Tag1' },
{ id: 2, name: 'Tag2' },
{ id: 3, name: 'Tag3' },
],
})
await expect(database.get('post', 2, ['tags'])).to.eventually.have.nested.property('[0].tags').with.shape([
{ id: 1, name: 'Tag1' },
{ id: 2, name: 'Tag2' },
{ id: 3, name: 'Tag3' },
])
await database.set('post', 2, row => ({
tags: {
$set: [
{
where: { id: 1 },
update: { name: 'Set1' },
},
{
where: r => $.query(r, { id: 2 }),
update: { name: 'Set2' },
},
{
where: r => $.eq(r.id, 3),
update: _ => ({ name: 'Set3' }),
},
],
},
}))
await expect(database.get('post', 2, ['tags'])).to.eventually.have.nested.property('[0].tags').with.shape([
{ id: 1, name: 'Set1' },
{ id: 2, name: 'Set2' },
{ id: 3, name: 'Set3' },
])
})
it('connect / disconnect manyToMany', async () => {
await setup(database, 'user', userTable)
await setup(database, 'profile', profileTable)
await setup(database, 'post', postTable)
await setup(database, 'tag', tagTable)
await setup(database, Relation.buildAssociationTable('post', 'tag') as any, post2TagTable2)
await database.set('post', 2, {
tags: {
$disconnect: {},
},
})
await expect(database.get('post', 2, ['tags'])).to.eventually.have.nested.property('[0].tags').deep.equal([])
await database.set('post', 2, row => ({
tags: {
$connect: r => $.eq(r.id, row.id2),
},
}))
await expect(database.get('post', 2, ['tags'])).to.eventually.have.nested.property('[0].tags').with.shape([{
id: 2,
}])
})
it('query relation', async () => {
await setup(database, 'user', userTable)
const posts = await setup(database, 'post', postTable)
await setup(database, 'tag', tagTable)
await setup(database, Relation.buildAssociationTable('post', 'tag') as any, post2TagTable2)
posts.filter(post => post2TagTable.some(p2t => p2t.post?.id === post.id2 && p2t.tag?.id === 1)).forEach(post => post.score! += 10)
await database.set('post', {
tags: {
$some: {
id: 1,
},
},
}, row => ({
score: $.add(row.score, 10),
}))
await expect(database.get('post', {})).to.eventually.have.deep.members(posts)
})
it('nested modify', async () => {
await setup(database, 'user', userTable)
await setup(database, 'post', postTable)
const profiles = await setup(database, 'profile', profileTable)
profiles[0].name = 'Evil'
await database.set('user', 1, {
posts: {
$set: {
where: { id2: { $gt: 1 } },
update: {
author: {
$set: _ => ({
profile: {
$set: _ => ({
name: 'Evil',
}),
},
}),
},
},
},
},
})
await expect(database.get('profile', {})).to.eventually.have.deep.members(profiles)
})
it('shared manyToMany', async () => {
await setup(database, 'login', [
{ id: '1', platform: 'sandbox', name: 'Bot1' },
{ id: '2', platform: 'sandbox', name: 'Bot2' },
{ id: '3', platform: 'sandbox', name: 'Bot3' },
{ id: '1', platform: 'whitebox', name: 'Bot1' },
])
await setup(database, 'guild', [
{ id: '1', platform2: 'sandbox', name: 'Guild1' },
{ id: '2', platform2: 'sandbox', name: 'Guild2' },
{ id: '3', platform2: 'sandbox', name: 'Guild3' },
{ id: '1', platform2: 'whitebox', name: 'Guild1' },
])
await setup(database, Relation.buildAssociationTable('login', 'guild') as any, [])
await database.set('login', {
id: '1',
platform: 'sandbox',
}, {
guilds: {
$connect: {
id: {
$or: ['1', '2'],
},
},
},
})
await expect(database.get('login', {
id: '1',
platform: 'sandbox',
}, ['guilds'])).to.eventually.have.nested.property('[0].guilds').with.length(2)
await database.set('login', {
id: '1',
platform: 'sandbox',
}, {
guilds: {
$disconnect: {
id: '2',
},
},
})
await expect(database.get('login', {
id: '1',
platform: 'sandbox',
}, ['guilds'])).to.eventually.have.nested.property('[0].guilds').with.length(1)
await database.create('guild', {
id: '4',
platform2: 'sandbox',
name: 'Guild4',
logins: {
$upsert: [
{ id: '1' },
{ id: '2' },
],
},
})
await expect(database.get('login', { platform: 'sandbox' }, ['id', 'guilds'])).to.eventually.have.shape([
{ id: '1', guilds: [{ id: '1' }, { id: '4' }] },
{ id: '2', guilds: [{ id: '4' }] },
{ id: '3', guilds: [] },
])
await expect(database.get('guild', { platform2: 'sandbox' }, ['id', 'logins'])).to.eventually.have.shape([
{ id: '1', logins: [{ id: '1' }] },
{ id: '2', logins: [] },
{ id: '3', logins: [] },
{ id: '4', logins: [{ id: '1' }, { id: '2' }] },
])
})
it('explicit manyToMany', async () => {