340 lines
11 KiB
TypeScript
340 lines
11 KiB
TypeScript
import { TestContext } from "../Context";
|
|
import { EntityDict } from "../test-app-domain";
|
|
import { v4 } from 'uuid';
|
|
import assert from 'assert';
|
|
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
|
import { describe, it, before, after } from 'mocha';
|
|
import { DbStore } from "../../lib/types/dbStore";
|
|
|
|
export default (storeGetter: () => DbStore<EntityDict, TestContext>) => {
|
|
|
|
// ==================== 复合表达式测试 ====================
|
|
|
|
it('[8.1]nested math expressions', async () => {
|
|
const store = storeGetter();
|
|
const context = new TestContext(store);
|
|
await context.begin();
|
|
|
|
const id1 = v4();
|
|
await store.operate('house', {
|
|
id: v4(),
|
|
action: 'create',
|
|
data: {
|
|
id: id1,
|
|
areaId: v4(),
|
|
ownerId: v4(),
|
|
district: '杭州',
|
|
size: 100.0,
|
|
}
|
|
}, context, {});
|
|
await context.commit();
|
|
|
|
// 测试嵌套表达式: size * 2 + 50 / 5 = 210
|
|
const r1 = await context.select('house', {
|
|
data: { id: 1, size: 1 },
|
|
filter: {
|
|
id: id1,
|
|
$expr: {
|
|
$eq: [
|
|
{
|
|
$divide: [
|
|
{
|
|
$add: [
|
|
{ $multiply: [{ '#attr': 'size' }, 2] },
|
|
50
|
|
]
|
|
},
|
|
5
|
|
]
|
|
},
|
|
210
|
|
]
|
|
}
|
|
}
|
|
}, {});
|
|
assert(r1.length === 1, `Nested math expression failed`);
|
|
});
|
|
|
|
it('[8.2]complex logic with date and math', async () => {
|
|
const store = storeGetter();
|
|
const context = new TestContext(store);
|
|
await context.begin();
|
|
|
|
const now = Date.now();
|
|
const twoDaysAgo = now - 2 * 24 * 3600 * 1000;
|
|
const fiveDaysAgo = now - 5 * 24 * 3600 * 1000;
|
|
|
|
const id1 = v4();
|
|
const id2 = v4();
|
|
await store.operate('token', {
|
|
id: v4(),
|
|
action: 'create',
|
|
data: [
|
|
{
|
|
id: id1,
|
|
env: { type: 'web' } as any,
|
|
applicationId: v4(),
|
|
userId: v4(),
|
|
playerId: v4(),
|
|
refreshedAt: twoDaysAgo,
|
|
value: v4(),
|
|
},
|
|
{
|
|
id: id2,
|
|
env: { type: 'web' } as any,
|
|
applicationId: v4(),
|
|
userId: v4(),
|
|
playerId: v4(),
|
|
refreshedAt: fiveDaysAgo,
|
|
value: v4(),
|
|
}
|
|
]
|
|
}, context, {});
|
|
await context.commit();
|
|
|
|
// 查询: 刷新时间在3天内 OR 刷新时间超过4天
|
|
const r1 = await context.select('token', {
|
|
data: { id: 1 },
|
|
filter: {
|
|
id: { $in: [id1, id2] },
|
|
$expr: {
|
|
$or: [
|
|
{
|
|
$lt: [
|
|
{ $dateDiff: [now, { '#attr': 'refreshedAt' }, 'd'] },
|
|
3
|
|
]
|
|
},
|
|
{
|
|
$gt: [
|
|
{ $dateDiff: [now, { '#attr': 'refreshedAt' }, 'd'] },
|
|
4
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}, {});
|
|
assert(r1.length === 2, `Complex logic failed, expected 2, got ${r1.length}`);
|
|
|
|
// 查询: 刷新时间在3天到4天之间
|
|
const r2 = await context.select('token', {
|
|
data: { id: 1 },
|
|
filter: {
|
|
id: { $in: [id1, id2] },
|
|
$expr: {
|
|
$and: [
|
|
{
|
|
$gte: [
|
|
{ $dateDiff: [now, { '#attr': 'refreshedAt' }, 'd'] },
|
|
3
|
|
]
|
|
},
|
|
{
|
|
$lte: [
|
|
{ $dateDiff: [now, { '#attr': 'refreshedAt' }, 'd'] },
|
|
4
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}, {});
|
|
assert(r2.length === 0, `Complex logic failed, expected 0, got ${r2.length}`);
|
|
});
|
|
|
|
it('[8.3]expression with cross-entity reference', async () => {
|
|
const store = storeGetter();
|
|
const context = new TestContext(store);
|
|
await context.begin();
|
|
|
|
const systemId = v4();
|
|
const appId1 = v4();
|
|
const appId2 = v4();
|
|
|
|
await store.operate('system', {
|
|
id: v4(),
|
|
action: 'create',
|
|
data: {
|
|
id: systemId,
|
|
name: 'parent_system',
|
|
description: 'test',
|
|
folder: '/test',
|
|
application$system: [
|
|
{
|
|
id: v4(),
|
|
action: 'create',
|
|
data: {
|
|
id: appId1,
|
|
name: 'parent_system_app1',
|
|
description: 't1',
|
|
type: 'web',
|
|
}
|
|
},
|
|
{
|
|
id: v4(),
|
|
action: 'create',
|
|
data: {
|
|
id: appId2,
|
|
name: 'child_app',
|
|
description: 't2',
|
|
type: 'web',
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} as EntityDict['system']['CreateSingle'], context, {});
|
|
await context.commit();
|
|
|
|
// // 查询: application.name 以 system.name 的前缀开头
|
|
const r1 = await context.select('application', {
|
|
data: {
|
|
id: 1,
|
|
name: 1,
|
|
system: { id: 1, name: 1 }
|
|
},
|
|
filter: {
|
|
systemId,
|
|
$expr: {
|
|
$startsWith: [
|
|
{ '#attr': 'name' },
|
|
{
|
|
'#refAttr': 'name',
|
|
'#refId': 'node-1'
|
|
}
|
|
]
|
|
},
|
|
system: {
|
|
'#id': 'node-1'
|
|
}
|
|
}
|
|
}, {});
|
|
|
|
assert(r1.length === 1, `Cross-entity expression failed, expected 1, got ${r1.length}`);
|
|
assert(r1[0].id === appId1, `Expected appId1, got ${r1[0].id}`);
|
|
});
|
|
|
|
// ==================== $mod 运算符测试 ====================
|
|
|
|
it('[10.1]math expression $mod', async () => {
|
|
const store = storeGetter();
|
|
const context = new TestContext(store);
|
|
await context.begin();
|
|
|
|
const id1 = v4();
|
|
const id2 = v4();
|
|
const id3 = v4();
|
|
|
|
await store.operate('user', {
|
|
id: v4(),
|
|
action: 'create',
|
|
data: [
|
|
{ id: id1, name: 'user1', nickname: 'n1', idState: 'unverified', userState: 'normal' },
|
|
{ id: id2, name: 'user2', nickname: 'n2', idState: 'unverified', userState: 'normal' },
|
|
{ id: id3, name: 'user3', nickname: 'n3', idState: 'unverified', userState: 'normal' },
|
|
]
|
|
}, context, {});
|
|
await context.commit();
|
|
|
|
// 测试 $mod: $$seq$$ % 2 = 0 (偶数序列号)
|
|
const r1 = await context.select('user', {
|
|
data: { id: 1, $$seq$$: 1 },
|
|
filter: {
|
|
id: { $in: [id1, id2, id3] },
|
|
$expr: {
|
|
$eq: [
|
|
{ $mod: [{ '#attr': '$$seq$$' }, 2] },
|
|
0
|
|
]
|
|
}
|
|
}
|
|
}, {});
|
|
// 偶数序列号的数量取决于插入顺序,这里只检查查询能正常执行
|
|
assert(r1.length >= 0, `$mod expression failed`);
|
|
|
|
// 测试 $mod: $$seq$$ % 3 = 1
|
|
const r2 = await context.select('user', {
|
|
data: { id: 1, $$seq$$: 1 },
|
|
filter: {
|
|
id: { $in: [id1, id2, id3] },
|
|
$expr: {
|
|
$eq: [
|
|
{ $mod: [{ '#attr': '$$seq$$' }, 3] },
|
|
1
|
|
]
|
|
}
|
|
}
|
|
}, {});
|
|
assert(r2.length >= 0, `$mod with 3 failed`);
|
|
});
|
|
|
|
|
|
// ==================== 聚合与普通查询组合测试 ====================
|
|
|
|
it('[20.2]select with nested aggregation', async () => {
|
|
const store = storeGetter();
|
|
const context = new TestContext(store);
|
|
await context.begin();
|
|
|
|
const systemId1 = v4();
|
|
const systemId2 = v4();
|
|
|
|
await store.operate('system', {
|
|
id: v4(),
|
|
action: 'create',
|
|
data: [
|
|
{
|
|
id: systemId1,
|
|
name: 'agg_system1',
|
|
description: 'test',
|
|
folder: '/test1',
|
|
platformId: 'p1',
|
|
application$system: [
|
|
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1_1', description: 't', type: 'web' } },
|
|
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1_2', description: 't', type: 'web' } },
|
|
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1_3', description: 't', type: 'wechatMp' } },
|
|
]
|
|
},
|
|
{
|
|
id: systemId2,
|
|
name: 'agg_system2',
|
|
description: 'test',
|
|
folder: '/test2',
|
|
platformId: 'p1',
|
|
application$system: [
|
|
{ id: v4(), action: 'create', data: { id: v4(), name: 'app2_1', description: 't', type: 'web' } },
|
|
]
|
|
}
|
|
]
|
|
} as EntityDict['system']['CreateMulti'], context, {});
|
|
await context.commit();
|
|
|
|
// 查询 system 并附带 application 聚合统计
|
|
const r1 = await context.select('system', {
|
|
data: {
|
|
id: 1,
|
|
name: 1,
|
|
application$system$$aggr: {
|
|
$entity: 'application',
|
|
data: {
|
|
'#aggr': { type: 1 },
|
|
'#count-1': { id: 1 }
|
|
}
|
|
}
|
|
},
|
|
filter: {
|
|
id: { $in: [systemId1, systemId2] }
|
|
}
|
|
}, {});
|
|
|
|
assert(r1.length === 2, `Nested aggregation query failed`);
|
|
|
|
const sys1 = r1.find(s => s.id === systemId1);
|
|
assert(sys1, `System1 not found`);
|
|
assert(sys1.application$system$$aggr && sys1.application$system$$aggr.length === 2, `System1 should have 2 type groups`);
|
|
|
|
const webCount = sys1.application$system$$aggr.find((a: any) => a['#data']?.type === 'web');
|
|
assert(webCount && webCount['#count-1'] === 2, `System1 web count should be 2`);
|
|
});
|
|
|
|
} |