5117 lines
158 KiB
TypeScript
5117 lines
158 KiB
TypeScript
import { MysqlStore } from "../../lib/MySQL/store";
|
||
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 { WebConfig } from '../test-app-domain/Application/Schema';
|
||
import { describe, it, before, after } from '../utils/test';
|
||
import { DbStore } from "../../lib/types/dbStore";
|
||
|
||
export const tests = (storeGetter: () => DbStore<EntityDict, TestContext>) => {
|
||
|
||
it('test insert', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: v4(),
|
||
name: 'xc',
|
||
nickname: 'xc',
|
||
},
|
||
{
|
||
id: v4(),
|
||
name: 'zz',
|
||
nickname: 'zzz',
|
||
}
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
});
|
||
|
||
it('test cascade insert', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'xxxc',
|
||
nickname: 'ddd',
|
||
token$player: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
env: {
|
||
type: 'web',
|
||
},
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: Date.now(),
|
||
value: v4(),
|
||
}
|
||
}]
|
||
} as EntityDict['user']['CreateSingle']['data']
|
||
}, context, {});
|
||
});
|
||
|
||
it('test update', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
const tokenId = v4();
|
||
await context.begin();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'xxxc',
|
||
nickname: 'ddd',
|
||
token$player: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: tokenId,
|
||
env: {
|
||
type: 'web',
|
||
},
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: Date.now(),
|
||
value: v4(),
|
||
}
|
||
}]
|
||
}
|
||
} as EntityDict['user']['CreateSingle'], context, {});
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'update',
|
||
filter: {
|
||
id: tokenId,
|
||
},
|
||
data: {
|
||
player: {
|
||
id: v4(),
|
||
action: 'activate',
|
||
data: {
|
||
name: 'xcxcxc0903'
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
});
|
||
|
||
it('test cascade update', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
const userId = v4();
|
||
const tokenId1 = v4();
|
||
const tokenId2 = v4();
|
||
await context.begin();
|
||
await context.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: userId,
|
||
name: 'xxxc',
|
||
nickname: 'ddd',
|
||
token$player: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: tokenId1,
|
||
env: {
|
||
type: 'web',
|
||
},
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: Date.now(),
|
||
value: v4(),
|
||
}
|
||
}]
|
||
}
|
||
} as EntityDict['user']['CreateSingle'], {});
|
||
|
||
await context.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: tokenId2,
|
||
env: {
|
||
type: 'web',
|
||
},
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: Date.now(),
|
||
value: v4(),
|
||
} as EntityDict['token']['CreateSingle']['data'],
|
||
}, {});
|
||
|
||
await context.commit();
|
||
|
||
// cascade update token of userId
|
||
await context.operate('user', {
|
||
id: v4(),
|
||
action: 'update',
|
||
data: {
|
||
name: 'xc',
|
||
token$player: [{
|
||
id: v4(),
|
||
action: 'update',
|
||
data: {
|
||
entity: 'email',
|
||
}
|
||
}]
|
||
},
|
||
filter: {
|
||
id: userId,
|
||
},
|
||
}, {});
|
||
|
||
const [row] = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
},
|
||
filter: {
|
||
id: tokenId2,
|
||
}
|
||
}, {});
|
||
|
||
assert(row.entity === 'mobile');
|
||
});
|
||
|
||
it('test delete', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
const tokenId = v4();
|
||
await context.begin();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'xxxc',
|
||
nickname: 'ddd',
|
||
token$player: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: tokenId,
|
||
env: {
|
||
type: 'server',
|
||
},
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: Date.now(),
|
||
value: v4(),
|
||
}
|
||
}]
|
||
}
|
||
}, context, {});
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'remove',
|
||
filter: {
|
||
id: tokenId,
|
||
},
|
||
data: {
|
||
player: {
|
||
id: v4(),
|
||
action: 'update',
|
||
data: {
|
||
name: 'xcxcxc0902'
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
});
|
||
|
||
it('test delete2', async () => {
|
||
const store = storeGetter();
|
||
// 这个例子暂在mysql上过不去,先放着吧
|
||
const context = new TestContext(store);
|
||
const tokenId = v4();
|
||
await context.begin();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'xxxc',
|
||
nickname: 'ddd',
|
||
token$player: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: tokenId,
|
||
env: {
|
||
type: 'server',
|
||
},
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: Date.now(),
|
||
value: v4(),
|
||
}
|
||
}]
|
||
}
|
||
}, context, {});
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'remove',
|
||
filter: {
|
||
id: tokenId,
|
||
},
|
||
data: {
|
||
ref: {
|
||
id: await generateNewIdAsync(),
|
||
action: 'remove',
|
||
data: {},
|
||
}
|
||
},
|
||
}, context, {});
|
||
await context.commit();
|
||
});
|
||
|
||
it('test decimal', async () => {
|
||
const store = storeGetter();
|
||
const id = v4();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id,
|
||
areaId: 'xc',
|
||
ownerId: 'xc',
|
||
district: '杭州',
|
||
size: 77.5,
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
const [house] = await store.select('house', {
|
||
data: {
|
||
id: 1,
|
||
size: 1,
|
||
},
|
||
filter: {
|
||
id,
|
||
},
|
||
}, context, {});
|
||
assert(typeof house.size === 'number');
|
||
});
|
||
|
||
|
||
it('[1.1]子查询', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'xc',
|
||
nickname: 'xc',
|
||
}
|
||
}, context, {});
|
||
|
||
process.env.NODE_ENV = 'development';
|
||
const rows = await store.select('user', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
},
|
||
filter: {
|
||
token$user: {
|
||
}
|
||
},
|
||
}, context, {});
|
||
process.env.NODE_ENV = undefined;
|
||
// console.log(rows);
|
||
assert(rows.length === 0);
|
||
await context.commit();
|
||
});
|
||
|
||
it('[1.2]行内属性上的表达式', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
const id = v4();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
name: 'xc',
|
||
nickname: 'xc',
|
||
}
|
||
}, context, {});
|
||
|
||
const id2 = v4();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id2,
|
||
name: 'xzw',
|
||
nickname: 'xzw22',
|
||
}
|
||
}, context, {});
|
||
|
||
process.env.NODE_ENV = 'development';
|
||
const users = await store.select('user', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [id, id2],
|
||
},
|
||
$expr: {
|
||
$eq: [{
|
||
'#attr': 'name',
|
||
}, {
|
||
"#attr": 'nickname',
|
||
}]
|
||
},
|
||
},
|
||
}, context, {});
|
||
const users2 = await store.select('user', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [id, id2],
|
||
},
|
||
$expr: {
|
||
$eq: [{
|
||
$mod: [
|
||
{
|
||
'#attr': '$$seq$$',
|
||
},
|
||
2,
|
||
],
|
||
}, 0]
|
||
},
|
||
},
|
||
}, context, {});
|
||
process.env.NODE_ENV = undefined;
|
||
|
||
assert(users.length === 1);
|
||
assert(users2.length === 1);
|
||
await context.commit();
|
||
});
|
||
|
||
it('[1.3]跨filter结点的表达式', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await context.begin();
|
||
await store.operate('application', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [{
|
||
id: id1,
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
system: {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'bbb',
|
||
name: 'systest',
|
||
description: 'aaaaa',
|
||
config: {},
|
||
folder: '/systest',
|
||
platformId: 'platform-111',
|
||
} as EntityDict['system']['CreateSingle']['data']
|
||
},
|
||
}, {
|
||
id: id2,
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
system: {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'ccc',
|
||
name: 'test2',
|
||
description: 'aaaaa2',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
}
|
||
},
|
||
}]
|
||
}, context, {});
|
||
|
||
const applications = await store.select('application', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
systemId: 1,
|
||
system: {
|
||
id: 1,
|
||
name: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
$expr: {
|
||
$startsWith: [
|
||
{
|
||
"#refAttr": 'name',
|
||
"#refId": 'node-1',
|
||
},
|
||
{
|
||
"#attr": 'name',
|
||
}
|
||
]
|
||
},
|
||
system: {
|
||
"#id": 'node-1',
|
||
},
|
||
id: id2,
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
system: {
|
||
name: 1,
|
||
}
|
||
},
|
||
$direction: 'asc',
|
||
}
|
||
]
|
||
}, context, {});
|
||
console.log(applications);
|
||
assert(applications.length === 1 && applications[0].id === id2);
|
||
await context.commit();
|
||
});
|
||
|
||
|
||
it('[1.4]跨filter子查询的表达式', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
await store.operate('application', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [{
|
||
id: 'aaaa',
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
system: {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'bbbb',
|
||
name: 'systest',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/systest',
|
||
platformId: 'platform-111',
|
||
}
|
||
},
|
||
}, {
|
||
id: 'aaaa2',
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
system: {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'cccc',
|
||
name: 'test2',
|
||
description: 'aaaaa2',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
}
|
||
},
|
||
}]
|
||
}, context, {});
|
||
|
||
process.env.NODE_ENV = 'development';
|
||
let systems = await store.select('system', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
},
|
||
filter: {
|
||
"#id": 'node-1',
|
||
$and: [
|
||
{
|
||
application$system: {
|
||
'#sqp': 'not in',
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
"#attr": 'name',
|
||
},
|
||
{
|
||
'#refId': 'node-1',
|
||
"#refAttr": 'name',
|
||
}
|
||
]
|
||
},
|
||
'#id': 'node-2',
|
||
}
|
||
}, {
|
||
id: {
|
||
$in: ['bbbb', 'cccc'],
|
||
}
|
||
}
|
||
]
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
name: 1,
|
||
},
|
||
$direction: 'asc',
|
||
}
|
||
]
|
||
}, context, {});
|
||
assert(systems.length === 1 && systems[0].id === 'bbbb');
|
||
systems = await store.select('system', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
},
|
||
filter: {
|
||
"#id": 'node-1',
|
||
$and: [
|
||
{
|
||
application$system: {
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
"#attr": 'name',
|
||
},
|
||
{
|
||
'#refId': 'node-1',
|
||
"#refAttr": 'name',
|
||
}
|
||
]
|
||
},
|
||
},
|
||
},
|
||
{
|
||
id: {
|
||
$in: ['bbbb', 'cccc'],
|
||
},
|
||
}
|
||
]
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
name: 1,
|
||
},
|
||
$direction: 'asc',
|
||
}
|
||
]
|
||
}, context, {});
|
||
process.env.NODE_ENV = undefined;
|
||
assert(systems.length === 1 && systems[0].id === 'cccc');
|
||
await context.commit();
|
||
});
|
||
|
||
it('[1.5]projection中的跨结点表达式', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
await store.operate('application', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [{
|
||
id: 'aaa5',
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
system: {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'bbb5',
|
||
name: 'systest',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/systest',
|
||
platformId: 'platform-111',
|
||
}
|
||
},
|
||
}, {
|
||
id: 'aaa5-2',
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
system: {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'ccc5',
|
||
name: 'test2',
|
||
description: 'aaaaa2',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
} as EntityDict['system']['CreateSingle']['data'],
|
||
},
|
||
}]
|
||
}, context, {});
|
||
|
||
let applications = await store.select('application', {
|
||
data: {
|
||
"#id": 'node-1',
|
||
id: 1,
|
||
name: 1,
|
||
system: {
|
||
id: 1,
|
||
name: 1,
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
"#attr": 'name',
|
||
},
|
||
{
|
||
'#refId': 'node-1',
|
||
"#refAttr": 'name',
|
||
}
|
||
]
|
||
},
|
||
}
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: ['aaa5', 'aaa5-2'],
|
||
},
|
||
},
|
||
}, context, {});
|
||
// console.log(applications);
|
||
assert(applications.length === 2);
|
||
applications.forEach(
|
||
(app) => {
|
||
assert(app.id === 'aaa5' && !(app.system!.$expr)
|
||
|| app.id === 'aaa5-2' && !!(app.system!.$expr));
|
||
}
|
||
);
|
||
|
||
const applications2 = await store.select('application', {
|
||
data: {
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
"#attr": 'name',
|
||
},
|
||
{
|
||
'#refId': 'node-1',
|
||
"#refAttr": 'name',
|
||
}
|
||
]
|
||
},
|
||
id: 1,
|
||
name: 1,
|
||
system: {
|
||
"#id": 'node-1',
|
||
id: 1,
|
||
name: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: ['aaa5', 'aaa5-2'],
|
||
},
|
||
},
|
||
}, context, {});
|
||
// console.log(applications2);
|
||
// assert(applications.length === 2);
|
||
applications2.forEach(
|
||
(app) => {
|
||
assert(app.id === 'aaa5' && !(app.$expr)
|
||
|| app.id === 'aaa5-2' && !!(app.$expr));
|
||
}
|
||
);
|
||
await context.commit();
|
||
});
|
||
|
||
// 这个貌似目前支持不了 by Xc
|
||
it('[1.6]projection中的一对多跨结点表达式', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
await store.operate('system', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'bbb6',
|
||
name: 'test2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'aaa6',
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'aaa6-2',
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}]
|
||
}
|
||
} as EntityDict['system']['CreateSingle'], context, {});
|
||
|
||
const systems = await store.select('system', {
|
||
data: {
|
||
"#id": 'node-1',
|
||
id: 1,
|
||
name: 1,
|
||
application$system: {
|
||
$entity: 'application',
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
"#attr": 'name',
|
||
},
|
||
{
|
||
'#refId': 'node-1',
|
||
"#refAttr": 'name',
|
||
}
|
||
]
|
||
},
|
||
$expr2: {
|
||
'#refId': 'node-1',
|
||
"#refAttr": 'id',
|
||
}
|
||
}
|
||
},
|
||
},
|
||
}, context, {});
|
||
// console.log(systems);
|
||
assert(systems.length === 1);
|
||
const [system] = systems;
|
||
const { application$system: applications } = system;
|
||
assert(applications!.length === 2);
|
||
applications!.forEach(
|
||
(ele) => {
|
||
assert(ele.id === 'aaa' && ele.$expr === false && ele.$expr2 === 'bbb'
|
||
|| ele.id === 'aaa2' && ele.$expr === true && ele.$expr2 === 'bbb');
|
||
}
|
||
);
|
||
});
|
||
|
||
it('[1.7]事务性测试', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
|
||
await context.begin();
|
||
const systemId = v4();
|
||
await store.operate('system', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: systemId,
|
||
name: 'test2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'aaa7',
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}, {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: 'aaa7-2',
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}]
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
await context.begin();
|
||
const systems = await store.select('system', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
application$system: {
|
||
$entity: 'application',
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
systemId: 1,
|
||
}
|
||
},
|
||
},
|
||
filter: {
|
||
id: systemId,
|
||
},
|
||
}, context, {});
|
||
assert(systems.length === 1 && systems[0].application$system!.length === 2);
|
||
|
||
await store.operate('application', {
|
||
id: v4(),
|
||
action: 'remove',
|
||
data: {},
|
||
filter: {
|
||
id: 'aaa7',
|
||
}
|
||
}, context, {});
|
||
|
||
const systems2 = await store.select('system', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
application$system: {
|
||
$entity: 'application',
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
systemId: 1,
|
||
}
|
||
},
|
||
},
|
||
filter: {
|
||
id: systemId,
|
||
},
|
||
}, context, {});
|
||
assert(systems2.length === 1 && systems2[0].application$system!.length === 1);
|
||
await context.rollback();
|
||
|
||
const systems3 = await store.select('system', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
application$system: {
|
||
$entity: 'application',
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
systemId: 1,
|
||
}
|
||
},
|
||
},
|
||
filter: {
|
||
id: systemId,
|
||
},
|
||
}, context, {});
|
||
assert(systems3.length === 1 && systems3[0].application$system!.length === 2);
|
||
});
|
||
|
||
it('[1.8]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: 'test2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
},
|
||
}
|
||
}]
|
||
},
|
||
{
|
||
id: systemId2,
|
||
name: 'test2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
},
|
||
}
|
||
}]
|
||
}
|
||
]
|
||
} as EntityDict['system']['CreateMulti'], context, {});
|
||
await context.commit();
|
||
|
||
await context.begin();
|
||
const result = await store.aggregate('application', {
|
||
data: {
|
||
'#aggr': {
|
||
system: {
|
||
id: 1,
|
||
},
|
||
},
|
||
'#count-1': {
|
||
id: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
systemId: {
|
||
$in: [systemId1, systemId2],
|
||
},
|
||
},
|
||
}, context, {});
|
||
await context.commit();
|
||
// console.log(result);
|
||
assert(result.length === 2);
|
||
result.forEach(
|
||
(row) => assert(row['#count-1'] === 2)
|
||
);
|
||
});
|
||
|
||
it('[1.9]test + 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: 'test2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}]
|
||
},
|
||
{
|
||
id: systemId2,
|
||
name: 'test2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}]
|
||
}
|
||
]
|
||
} as EntityDict['system']['CreateMulti'], context, {});
|
||
await context.commit();
|
||
|
||
await context.begin();
|
||
const result = await store.select('system', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
application$system$$aggr: {
|
||
$entity: 'application',
|
||
data: {
|
||
'#aggr': {
|
||
systemId: 1,
|
||
},
|
||
'#count-1': {
|
||
id: 1,
|
||
}
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [systemId1, systemId2],
|
||
},
|
||
},
|
||
}, context, {});
|
||
await context.commit();
|
||
// console.log(result);
|
||
assert(result.length === 2);
|
||
result.forEach(
|
||
(row) => assert(row.application$system$$aggr?.length === 1 && row.application$system$$aggr[0]['#count-1'] === 2)
|
||
);
|
||
});
|
||
|
||
it('[1.10]json insert/select', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
let id = await generateNewIdAsync();
|
||
await context.operate('application', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
name: 'xuchang',
|
||
description: 'tt',
|
||
type: 'web',
|
||
systemId: 'system',
|
||
config: {
|
||
type: 'web',
|
||
passport: ['email', 'mobile'],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}, {});
|
||
|
||
let result = await context.select('application', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
config: {
|
||
passport: [undefined, 1],
|
||
wechat: {
|
||
appId: 1,
|
||
}
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
},
|
||
}, {});
|
||
// console.log(JSON.stringify(result));
|
||
|
||
id = await generateNewIdAsync();
|
||
await context.operate('application', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
name: 'xuchang',
|
||
description: 'tt',
|
||
type: 'web',
|
||
systemId: 'system',
|
||
config: {
|
||
type: 'web',
|
||
wechat: {
|
||
appId: 'aaaa\\nddddd',
|
||
appSecret: '',
|
||
},
|
||
passport: ['email', 'mobile'],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}, {});
|
||
|
||
result = await context.select('application', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
config: {
|
||
passport: [undefined, 1],
|
||
wechat: {
|
||
appId: 1,
|
||
}
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
},
|
||
}, {});
|
||
console.log((result[0].config as WebConfig)!.wechat!.appId);
|
||
});
|
||
|
||
it('[1.11]json as filter', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id = await generateNewIdAsync();
|
||
await store.operate('oper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
action: 'test',
|
||
data: {
|
||
name: 'xc',
|
||
books: [{
|
||
title: 'mathmatics',
|
||
price: 1,
|
||
}, {
|
||
title: 'english',
|
||
price: 2,
|
||
}],
|
||
},
|
||
targetEntity: 'bbb',
|
||
bornAt: 111,
|
||
}
|
||
}, context, {});
|
||
|
||
const row = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
books: [undefined, {
|
||
title: 1,
|
||
price: 1,
|
||
}],
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
data: {
|
||
name: 'xc',
|
||
}
|
||
}
|
||
}, context, {});
|
||
const row2 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
books: [undefined, {
|
||
title: 1,
|
||
price: 1,
|
||
}],
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
data: {
|
||
name: 'xc2',
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
// console.log(JSON.stringify(row));
|
||
assert(row.length === 1, JSON.stringify(row));
|
||
assert(row2.length === 0, JSON.stringify(row2));
|
||
});
|
||
|
||
|
||
it('[1.11.2]json filter on top level', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id = await generateNewIdAsync();
|
||
await store.operate('actionAuth', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
pathId: await generateNewIdAsync(),
|
||
deActions: ['1.12'],
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
|
||
const row = await store.select('actionAuth', {
|
||
data: {
|
||
id: 1,
|
||
deActions: 1,
|
||
},
|
||
filter: {
|
||
id,
|
||
deActions: {
|
||
$overlaps: '1.12',
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
|
||
const row2 = await store.select('actionAuth', {
|
||
data: {
|
||
id: 1,
|
||
deActions: 1,
|
||
},
|
||
filter: {
|
||
id,
|
||
deActions: {
|
||
$contains: ['1.13333'],
|
||
}
|
||
}
|
||
}, context, {});
|
||
// console.log(JSON.stringify(row));
|
||
assert(row.length === 1, JSON.stringify(row));
|
||
console.log(JSON.stringify(row));
|
||
assert(row2.length === 0, JSON.stringify(row2));
|
||
|
||
const row3 = await store.select('actionAuth', {
|
||
data: {
|
||
id: 1,
|
||
deActions: 1,
|
||
},
|
||
filter: {
|
||
id,
|
||
deActions: {
|
||
$exists: true,
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
assert(row3.length === 1);
|
||
const row4 = await store.select('actionAuth', {
|
||
data: {
|
||
id: 1,
|
||
deActions: 1,
|
||
},
|
||
filter: {
|
||
id,
|
||
deActions: {
|
||
$exists: false,
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
assert(row4.length === 0);
|
||
});
|
||
|
||
it('[1.11.3]json escape', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id = await generateNewIdAsync();
|
||
await store.operate('oper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
action: 'test',
|
||
data: {
|
||
$or: [{
|
||
name: 'xc',
|
||
}, {
|
||
name: {
|
||
$includes: 'xc',
|
||
}
|
||
}],
|
||
},
|
||
targetEntity: 'bbb',
|
||
bornAt: 123,
|
||
}
|
||
}, context, {});
|
||
|
||
const row = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
data: {
|
||
'.$or': {
|
||
$contains: {
|
||
name: 'xc',
|
||
},
|
||
},
|
||
},
|
||
}
|
||
}, context, {});
|
||
|
||
process.env.NODE_ENV = 'development';
|
||
const row2 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
data: {
|
||
'.$or': [
|
||
{
|
||
name: 'xc',
|
||
},
|
||
{
|
||
name: {
|
||
'.$includes': 'xc',
|
||
}
|
||
}
|
||
],
|
||
},
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
assert(row.length === 1);
|
||
assert(row2.length === 1);
|
||
});
|
||
|
||
it('[1.12]complicated json filter', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id = await generateNewIdAsync();
|
||
await store.operate('oper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
action: 'test',
|
||
data: {
|
||
name: 'xcc',
|
||
price: [100, 400, 1000],
|
||
},
|
||
targetEntity: 'bbb',
|
||
bornAt: 123,
|
||
}
|
||
}, context, {});
|
||
|
||
const row = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: [undefined, 400],
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row2 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: [undefined, 200],
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row3 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: [undefined, {
|
||
$gt: 300,
|
||
}],
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row4 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: {
|
||
$contains: [200, 500],
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row5 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: {
|
||
$contains: [100, 400],
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row6 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: {
|
||
$contains: ['xc'],
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row7 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
name: {
|
||
$includes: 'xc',
|
||
},
|
||
price: {
|
||
$overlaps: [200, 400, 800],
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
/**
|
||
* 带$or的查询
|
||
*/
|
||
process.env.NODE_ENV = 'development';
|
||
const row8 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
$or: [
|
||
{
|
||
name: {
|
||
$includes: 'xc',
|
||
},
|
||
},
|
||
{
|
||
name: {
|
||
$includes: 'xzw',
|
||
}
|
||
}
|
||
],
|
||
price: {
|
||
$overlaps: [200, 400, 800],
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
const row9 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: {
|
||
$length: 3,
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
const row10 = await store.select('oper', {
|
||
data: {
|
||
id: 1,
|
||
data: {
|
||
name: 1,
|
||
price: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
data: {
|
||
price: {
|
||
$length: {
|
||
$gt: 3,
|
||
},
|
||
},
|
||
}
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
assert(row.length === 1);
|
||
assert(row2.length === 0);
|
||
assert(row3.length === 1);
|
||
assert(row4.length === 0);
|
||
assert(row5.length === 1);
|
||
assert(row6.length === 0);
|
||
assert(row7.length === 1);
|
||
assert(row8.length === 1);
|
||
assert(row9.length === 1);
|
||
assert(row10.length === 0);
|
||
// console.log(JSON.stringify(row7));
|
||
});
|
||
|
||
it('[1.13]sub query predicate', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
const id1 = await generateNewIdAsync();
|
||
const id2 = await generateNewIdAsync();
|
||
const id3 = await generateNewIdAsync();
|
||
const systems: EntityDict['system']['CreateSingle']['data'][] = [
|
||
{
|
||
id: id1,
|
||
name: 'test1.13-1',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test1.13-1-1',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test1.13-1-2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}]
|
||
},
|
||
{
|
||
id: id2,
|
||
name: 'test1.13-2',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
application$system: [{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test1.13-2-1',
|
||
description: 'ttttt',
|
||
type: 'web',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: v4(),
|
||
name: 'test1.13-2-2',
|
||
description: 'ttttt2',
|
||
type: 'wechatMp',
|
||
config: {
|
||
type: 'web',
|
||
passport: [],
|
||
location: {
|
||
protocol: "http:",
|
||
hostname: '',
|
||
port: '',
|
||
},
|
||
},
|
||
}
|
||
}]
|
||
},
|
||
{
|
||
|
||
id: id3,
|
||
name: 'test1.13-3',
|
||
description: 'aaaaa',
|
||
config: {
|
||
App: {},
|
||
},
|
||
folder: '/test2',
|
||
platformId: 'platform-111',
|
||
}
|
||
];
|
||
|
||
await context.operate('system', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: systems,
|
||
}, {});
|
||
|
||
await context.commit();
|
||
|
||
const r1 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1);
|
||
|
||
const r2 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
'#sqp': 'not in',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r2.length === 0);
|
||
|
||
const r22 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
name: {
|
||
$startsWith: 'test1.13-2',
|
||
},
|
||
'#sqp': 'not in',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r22.length === 1);
|
||
|
||
const r23 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
name: {
|
||
$startsWith: 'test1.13-2',
|
||
},
|
||
'#sqp': 'all',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r23.length === 0);
|
||
|
||
const r24 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
name: {
|
||
$startsWith: 'test1.13-2',
|
||
},
|
||
'#sqp': 'not all',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r24.length === 1);
|
||
|
||
const r3 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
'#sqp': 'all',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r3.length === 0);
|
||
|
||
|
||
const r4 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
application$system: {
|
||
'#sqp': 'not all',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r4.length === 1);
|
||
|
||
const r5 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [id1, id2, id3],
|
||
},
|
||
application$system: {
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r5.length === 2);
|
||
assert(r5.map(ele => ele.id).includes(id1));
|
||
assert(r5.map(ele => ele.id).includes(id2));
|
||
|
||
const r6 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [id1, id2, id3],
|
||
},
|
||
application$system: {
|
||
'#sqp': 'not in',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r6.length === 1);
|
||
assert(r6.map(ele => ele.id).includes(id3));
|
||
|
||
const r7 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [id1, id2, id3],
|
||
},
|
||
application$system: {
|
||
'#sqp': 'all',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r7.length === 0);
|
||
|
||
const r8 = await context.select('system', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: {
|
||
$in: [id1, id2, id3],
|
||
},
|
||
application$system: {
|
||
'#sqp': 'not all',
|
||
},
|
||
},
|
||
}, {});
|
||
assert(r8.length === 3);
|
||
assert(r8.map(ele => ele.id).includes(id1));
|
||
assert(r8.map(ele => ele.id).includes(id2));
|
||
assert(r8.map(ele => ele.id).includes(id3));
|
||
});
|
||
|
||
it('[1.14]$dateDiff expression', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const now = Date.now();
|
||
const oneHourAgo = now - 3600 * 1000; // 1小时前
|
||
const oneDayAgo = now - 24 * 3600 * 1000; // 1天前
|
||
const oneMonthAgo = now - 30 * 24 * 3600 * 1000; // 约30天前
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
const id3 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: oneHourAgo,
|
||
value: v4(),
|
||
},
|
||
{
|
||
id: id2,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: oneDayAgo,
|
||
value: v4(),
|
||
},
|
||
{
|
||
id: id3,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: oneMonthAgo,
|
||
value: v4(),
|
||
}
|
||
]
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
|
||
// 测试秒级差异
|
||
const r1 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateDiff: [
|
||
now,
|
||
{ '#attr': 'refreshedAt' },
|
||
's'
|
||
]
|
||
},
|
||
3500 // 约1小时 = 3600秒,给一些误差
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row for seconds diff, got ${r1.length}`);
|
||
|
||
// 测试分钟级差异
|
||
const r2 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateDiff: [
|
||
now,
|
||
{ '#attr': 'refreshedAt' },
|
||
'm'
|
||
]
|
||
},
|
||
55 // 约1小时 = 60分钟
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `Expected 1 row for minutes diff, got ${r2.length}`);
|
||
|
||
// 测试小时级差异
|
||
const r3 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id2,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateDiff: [
|
||
now,
|
||
{ '#attr': 'refreshedAt' },
|
||
'h'
|
||
]
|
||
},
|
||
23 // 约1天 = 24小时
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r3.length === 1, `Expected 1 row for hours diff, got ${r3.length}`);
|
||
|
||
// 测试天级差异
|
||
const r4 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id3,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateDiff: [
|
||
now,
|
||
{ '#attr': 'refreshedAt' },
|
||
'd'
|
||
]
|
||
},
|
||
25 // 约30天
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r4.length === 1, `Expected 1 row for days diff, got ${r4.length}`);
|
||
});
|
||
|
||
it('[1.15]$dateFloor expression', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 创建一个特定时间的记录: 2024-06-15 14:30:45
|
||
const specificTime = new Date('2024-06-15T14:30:45.123Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificTime,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
|
||
// 测试向下取整到分钟 - 应该得到 14:30:00
|
||
const startOfMinute = new Date('2024-06-15T14:30:00.000Z').valueOf();
|
||
const endOfMinute = new Date('2024-06-15T14:31:00.000Z').valueOf();
|
||
|
||
const r1 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$and: [
|
||
{
|
||
$gte: [
|
||
{
|
||
$dateFloor: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'm'
|
||
]
|
||
},
|
||
startOfMinute
|
||
]
|
||
},
|
||
{
|
||
$lt: [
|
||
{
|
||
$dateFloor: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'm'
|
||
]
|
||
},
|
||
endOfMinute
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row for minute floor, got ${r1.length}`);
|
||
|
||
// 测试向下取整到小时 - 应该得到 14:00:00
|
||
const startOfHour = new Date('2024-06-15T14:00:00.000Z').valueOf();
|
||
const endOfHour = new Date('2024-06-15T15:00:00.000Z').valueOf();
|
||
|
||
const r2 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$and: [
|
||
{
|
||
$gte: [
|
||
{
|
||
$dateFloor: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'h'
|
||
]
|
||
},
|
||
startOfHour
|
||
]
|
||
},
|
||
{
|
||
$lt: [
|
||
{
|
||
$dateFloor: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'h'
|
||
]
|
||
},
|
||
endOfHour
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `Expected 1 row for hour floor, got ${r2.length}`);
|
||
|
||
// 测试向下取整到天 - 应该得到 2024-06-15 00:00:00
|
||
const startOfDay = new Date('2024-06-15T00:00:00.000Z').valueOf();
|
||
const endOfDay = new Date('2024-06-16T00:00:00.000Z').valueOf();
|
||
|
||
const r3 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$and: [
|
||
{
|
||
$gte: [
|
||
{
|
||
$dateFloor: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'd'
|
||
]
|
||
},
|
||
startOfDay
|
||
]
|
||
},
|
||
{
|
||
$lt: [
|
||
{
|
||
$dateFloor: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'd'
|
||
]
|
||
},
|
||
endOfDay
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r3.length === 1, `Expected 1 row for day floor, got ${r3.length}`);
|
||
});
|
||
|
||
it('[1.16]$dateCeil expression', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 创建一个特定时间的记录: 2024-06-15 14:30:45
|
||
const specificTime = new Date('2024-06-15T14:30:45.123Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificTime,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
|
||
// 测试向上取整到分钟 - 应该得到 14:31:00
|
||
const ceilMinute = new Date('2024-06-15T14:31:00.000Z').valueOf();
|
||
|
||
const r1 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateCeil: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'm'
|
||
]
|
||
},
|
||
ceilMinute
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row for minute ceil, got ${r1.length}`);
|
||
|
||
// 测试向上取整到小时 - 应该得到 15:00:00
|
||
const ceilHour = new Date('2024-06-15T15:00:00.000Z').valueOf();
|
||
|
||
const r2 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateCeil: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'h'
|
||
]
|
||
},
|
||
ceilHour
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `Expected 1 row for hour ceil, got ${r2.length}`);
|
||
|
||
// 测试向上取整到天 - 应该得到 2024-06-16 00:00:00
|
||
const ceilDay = new Date('2024-06-16T00:00:00.000Z').valueOf();
|
||
|
||
const r3 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
refreshedAt: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$dateCeil: [
|
||
{ '#attr': 'refreshedAt' },
|
||
'd'
|
||
]
|
||
},
|
||
ceilDay
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r3.length === 1, `Expected 1 row for day ceil, got ${r3.length}`);
|
||
});
|
||
|
||
it('[1.17]aggregation functions $$sum, $$max, $$min, $$avg', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const areaId = v4();
|
||
const ownerId = v4();
|
||
|
||
// 创建多个房屋记录用于测试聚合函数
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: v4(),
|
||
areaId,
|
||
ownerId,
|
||
district: '杭州',
|
||
size: 80.0,
|
||
},
|
||
{
|
||
id: v4(),
|
||
areaId,
|
||
ownerId,
|
||
district: '杭州',
|
||
size: 100.0,
|
||
},
|
||
{
|
||
id: v4(),
|
||
areaId,
|
||
ownerId,
|
||
district: '杭州',
|
||
size: 120.0,
|
||
},
|
||
{
|
||
id: v4(),
|
||
areaId,
|
||
ownerId,
|
||
district: '上海',
|
||
size: 150.0,
|
||
},
|
||
]
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
|
||
// 测试 $$count
|
||
const countResult = await store.aggregate('house', {
|
||
data: {
|
||
'#aggr': {
|
||
district: 1,
|
||
},
|
||
'#count-1': {
|
||
id: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
areaId,
|
||
},
|
||
}, context, {});
|
||
|
||
assert(countResult.length === 2, `Expected 2 groups, got ${countResult.length}`);
|
||
const hangzhouCount = countResult.find(r => r['#data']?.district === '杭州');
|
||
const shanghaiCount = countResult.find(r => r['#data']?.district === '上海');
|
||
assert(hangzhouCount && hangzhouCount['#count-1'] === 3, `Expected 3 houses in 杭州, got ${hangzhouCount?.['#count-1']}`);
|
||
assert(shanghaiCount && shanghaiCount['#count-1'] === 1, `Expected 1 house in 上海, got ${shanghaiCount?.['#count-1']}`);
|
||
|
||
// TODO: 下面的测试用例暂不支持,先注释掉
|
||
// // 测试 $$sum
|
||
// const sumResult = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': {
|
||
// district: 1,
|
||
// },
|
||
// '#sum-1': {
|
||
// $$sum: {
|
||
// '#attr': 'size',
|
||
// },
|
||
// }
|
||
// },
|
||
// filter: {
|
||
// areaId,
|
||
// },
|
||
// }, context, {});
|
||
|
||
// const hangzhouSum = sumResult.find(r => r['#data']?.district === '杭州');
|
||
// assert(hangzhouSum && hangzhouSum['#sum-1'] === 300, `Expected sum 300 for 杭州, got ${hangzhouSum?.['#sum-1']}`);
|
||
|
||
// // 测试 $$max
|
||
// const maxResult = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': {
|
||
// district: 1,
|
||
// },
|
||
// '#max-1': {
|
||
// $$max: {
|
||
// '#attr': 'size',
|
||
// },
|
||
// }
|
||
// },
|
||
// filter: {
|
||
// areaId,
|
||
// },
|
||
// }, context, {});
|
||
|
||
// const hangzhouMax = maxResult.find(r => r['#data']?.district === '杭州');
|
||
// assert(hangzhouMax && hangzhouMax['#max-1'] === 120, `Expected max 120 for 杭州, got ${hangzhouMax?.['#max-1']}`);
|
||
|
||
// // 测试 $$min
|
||
// const minResult = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': {
|
||
// district: 1,
|
||
// },
|
||
// '#min-1': {
|
||
// $$min: {
|
||
// '#attr': 'size',
|
||
// },
|
||
// }
|
||
// },
|
||
// filter: {
|
||
// areaId,
|
||
// },
|
||
// }, context, {});
|
||
|
||
// const hangzhouMin = minResult.find(r => r['#data']?.district === '杭州');
|
||
// assert(hangzhouMin && hangzhouMin['#min-1'] === 80, `Expected min 80 for 杭州, got ${hangzhouMin?.['#min-1']}`);
|
||
|
||
// // 测试 $$avg
|
||
// const avgResult = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': {
|
||
// district: 1,
|
||
// },
|
||
// '#avg-1': {
|
||
// $$avg: {
|
||
// '#attr': 'size',
|
||
// },
|
||
// }
|
||
// },
|
||
// filter: {
|
||
// areaId,
|
||
// },
|
||
// }, context, {});
|
||
|
||
// const hangzhouAvg = avgResult.find(r => r['#data']?.district === '杭州');
|
||
// assert(hangzhouAvg && hangzhouAvg['#avg-1'] === 100, `Expected avg 100 for 杭州, got ${hangzhouAvg?.['#avg-1']}`);
|
||
});
|
||
|
||
it('[1.18]date expression with constants', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const now = Date.now();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: now,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
|
||
await context.commit();
|
||
|
||
// 测试 $dateDiff 使用常量日期
|
||
const yesterday = now - 24 * 3600 * 1000;
|
||
|
||
const r1 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
$dateDiff: [
|
||
now,
|
||
yesterday,
|
||
'd'
|
||
]
|
||
},
|
||
1
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row for constant date diff, got ${r1.length}`);
|
||
|
||
// 测试 $dateDiff 混合使用属性和常量
|
||
const r2 = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$lt: [
|
||
{
|
||
$dateDiff: [
|
||
{ '#attr': 'refreshedAt' },
|
||
yesterday,
|
||
'h'
|
||
]
|
||
},
|
||
48 // 应该在48小时以内
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `Expected 1 row for mixed date diff, got ${r2.length}`);
|
||
});
|
||
|
||
// ==================== 数学运算符测试 ====================
|
||
|
||
it('[2.1]math expression $add', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '杭州',
|
||
size: 80.5,
|
||
},
|
||
{
|
||
id: id2,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '上海',
|
||
size: 100.0,
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $add: size + 20 > 100
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$gt: [
|
||
{
|
||
$add: [{ '#attr': 'size' }, 20]
|
||
},
|
||
100
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 2, `Expected 2 rows, got ${r1.length}`);
|
||
|
||
// 测试多参数 $add: size + 10 + 20 > 120
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$gt: [
|
||
{
|
||
$add: [{ '#attr': 'size' }, 10, 20]
|
||
},
|
||
120
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].id === id2, `Expected id2, got ${r2.map(r => r.id)}`);
|
||
});
|
||
|
||
it('[2.2]math expression $subtract', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '杭州',
|
||
size: 80.0,
|
||
},
|
||
{
|
||
id: id2,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '上海',
|
||
size: 120.0,
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $subtract: size - 50 > 50
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$gt: [
|
||
{
|
||
$subtract: [{ '#attr': 'size' }, 50]
|
||
},
|
||
50
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id2, `Expected id2, got ${r1.map(r => r.id)}`);
|
||
});
|
||
|
||
it('[2.3]math expression $multiply', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '杭州',
|
||
size: 50.0,
|
||
},
|
||
{
|
||
id: id2,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '上海',
|
||
size: 100.0,
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $multiply: size * 2 >= 200
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$gte: [
|
||
{
|
||
$multiply: [{ '#attr': 'size' }, 2]
|
||
},
|
||
200
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id2, `Expected id2, got ${r1.map(r => r.id)}`);
|
||
|
||
// 测试多参数 $multiply: size * 2 * 3 > 500
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$gt: [
|
||
{
|
||
$multiply: [{ '#attr': 'size' }, 2, 3]
|
||
},
|
||
500
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].id === id2, `Expected id2, got ${r2.map(r => r.id)}`);
|
||
});
|
||
|
||
it('[2.4]math expression $divide', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '杭州',
|
||
size: 80.0,
|
||
},
|
||
{
|
||
id: id2,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '上海',
|
||
size: 200.0,
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $divide: size / 2 > 50
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$gt: [
|
||
{
|
||
$divide: [{ '#attr': 'size' }, 2]
|
||
},
|
||
50
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id2, `Expected id2, got ${r1.map(r => r.id)}`);
|
||
});
|
||
|
||
it('[2.5]math expression $abs', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '杭州',
|
||
size: 80.0,
|
||
},
|
||
{
|
||
id: id2,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: '上海',
|
||
size: 120.0,
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $abs: abs(size - 100) <= 20
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$lte: [
|
||
{
|
||
$abs: {
|
||
$subtract: [{ '#attr': 'size' }, 100]
|
||
}
|
||
},
|
||
20
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 2, `Expected 2 rows, got ${r1.length}`);
|
||
});
|
||
|
||
it('[2.6]math expression $round', 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: 80.567,
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $round: round(size, 1) = 80.6
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
$round: [{ '#attr': 'size' }, 1]
|
||
},
|
||
80.6
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row, got ${r1.length}`);
|
||
|
||
// 测试 $round: round(size, 0) = 81
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{
|
||
$round: [{ '#attr': 'size' }, 0]
|
||
},
|
||
81
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `Expected 1 row, got ${r2.length}`);
|
||
});
|
||
|
||
it('[2.7]math expression $ceil and $floor', 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: 80.3,
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $ceil: ceil(size) = 81
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{ $ceil: { '#attr': 'size' } },
|
||
81
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row for ceil, got ${r1.length}`);
|
||
|
||
// 测试 $floor: floor(size) = 80
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{ $floor: { '#attr': 'size' } },
|
||
80
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `Expected 1 row for floor, got ${r2.length}`);
|
||
});
|
||
|
||
it('[2.8]math expression $pow', 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: 10.0,
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $pow: pow(size, 2) = 100
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{ $pow: [{ '#attr': 'size' }, 2] },
|
||
100
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row, got ${r1.length}`);
|
||
});
|
||
|
||
// ==================== 字符串操作测试 ====================
|
||
|
||
it('[3.1]string expression $endsWith', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
name: 'test_user',
|
||
nickname: 'test',
|
||
},
|
||
{
|
||
id: id2,
|
||
name: 'admin_role',
|
||
nickname: 'admin',
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $endsWith: name ends with '_user'
|
||
const r1 = await context.select('user', {
|
||
data: { id: 1, name: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$endsWith: [{ '#attr': 'name' }, '_user']
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id1, `Expected id1, got ${r1.map(r => r.id)}`);
|
||
});
|
||
|
||
it('[3.2]string expression $includes', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
name: 'test_admin_user',
|
||
nickname: 'test',
|
||
},
|
||
{
|
||
id: id2,
|
||
name: 'guest_role',
|
||
nickname: 'guest',
|
||
},
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $includes: name includes 'admin'
|
||
const r1 = await context.select('user', {
|
||
data: { id: 1, name: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$includes: [{ '#attr': 'name' }, 'admin']
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id1, `Expected id1, got ${r1.map(r => r.id)}`);
|
||
});
|
||
|
||
it('[3.3]string expression $concat', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
name: 'hello',
|
||
nickname: 'world',
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $concat: concat(name, '_', nickname) = 'hello_world'
|
||
const r1 = await context.select('user', {
|
||
data: { id: 1, name: 1, nickname: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$eq: [
|
||
{ $concat: [{ '#attr': 'name' }, '_', { '#attr': 'nickname' }] },
|
||
'hello_world'
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `Expected 1 row, got ${r1.length}`);
|
||
});
|
||
|
||
// ==================== 比较运算符测试 ====================
|
||
|
||
it('[4.1]comparison expressions $gt $gte $lt $lte $ne', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
const id3 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{ id: id1, areaId: v4(), ownerId: v4(), district: '杭州', size: 50.0 },
|
||
{ id: id2, areaId: v4(), ownerId: v4(), district: '上海', size: 100.0 },
|
||
{ id: id3, areaId: v4(), ownerId: v4(), district: '北京', size: 150.0 },
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
const ids = [id1, id2, id3];
|
||
|
||
// $gt: size > 100
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: { $gt: [{ '#attr': 'size' }, 100] }
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id3, `$gt failed`);
|
||
|
||
// $gte: size >= 100
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: { $gte: [{ '#attr': 'size' }, 100] }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 2, `$gte failed, expected 2, got ${r2.length}`);
|
||
|
||
// $lt: size < 100
|
||
const r3 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: { $lt: [{ '#attr': 'size' }, 100] }
|
||
}
|
||
}, {});
|
||
assert(r3.length === 1 && r3[0].id === id1, `$lt failed`);
|
||
|
||
// $lte: size <= 100
|
||
const r4 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: { $lte: [{ '#attr': 'size' }, 100] }
|
||
}
|
||
}, {});
|
||
assert(r4.length === 2, `$lte failed, expected 2, got ${r4.length}`);
|
||
|
||
// $ne: size != 100
|
||
const r5 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: { $ne: [{ '#attr': 'size' }, 100] }
|
||
}
|
||
}, {});
|
||
assert(r5.length === 2, `$ne failed, expected 2, got ${r5.length}`);
|
||
});
|
||
|
||
// ==================== 布尔运算符测试 ====================
|
||
|
||
it('[5.1]boolean expression $true $false', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{ id: id1, areaId: v4(), ownerId: v4(), district: '杭州', size: 100.0 },
|
||
{ id: id2, areaId: v4(), ownerId: v4(), district: '上海', size: 50.0 },
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// $true: 检查表达式为真
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$true: { $gt: [{ '#attr': 'size' }, 80] }
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id1, `$true failed`);
|
||
|
||
// $false: 检查表达式为假
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
$expr: {
|
||
$false: { $gt: [{ '#attr': 'size' }, 80] }
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].id === id2, `$false failed`);
|
||
});
|
||
|
||
it('[5.2]logic expression $and $or $not', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
const id3 = v4();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{ id: id1, areaId: v4(), ownerId: v4(), district: '杭州', size: 80.0 },
|
||
{ id: id2, areaId: v4(), ownerId: v4(), district: '上海', size: 120.0 },
|
||
{ id: id3, areaId: v4(), ownerId: v4(), district: '北京', size: 100.0 },
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
const ids = [id1, id2, id3];
|
||
|
||
// $and: size > 70 AND size < 110
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: {
|
||
$and: [
|
||
{ $gt: [{ '#attr': 'size' }, 70] },
|
||
{ $lt: [{ '#attr': 'size' }, 110] }
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 2, `$and failed, expected 2, got ${r1.length}`);
|
||
|
||
// $or: size < 90 OR size > 110
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: {
|
||
$or: [
|
||
{ $lt: [{ '#attr': 'size' }, 90] },
|
||
{ $gt: [{ '#attr': 'size' }, 110] }
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 2, `$or failed, expected 2, got ${r2.length}`);
|
||
|
||
// $not: NOT (size = 100)
|
||
const r3 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: {
|
||
id: { $in: ids },
|
||
$expr: {
|
||
$not: { $eq: [{ '#attr': 'size' }, 100] }
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r3.length === 2, `$not failed, expected 2, got ${r3.length}`);
|
||
});
|
||
|
||
// ==================== 日期函数测试 ====================
|
||
|
||
it('[6.1]date expression $year $month $day', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 2024-06-15 14:30:45
|
||
const specificDate = new Date('2024-06-15T14:30:45.000Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificDate,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $year
|
||
const r1 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $year: { '#attr': 'refreshedAt' } }, 2024] }
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `$year failed`);
|
||
|
||
// 测试 $month
|
||
const r2 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $month: { '#attr': 'refreshedAt' } }, 6] }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `$month failed`);
|
||
|
||
// 测试 $day
|
||
const r3 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $day: { '#attr': 'refreshedAt' } }, 15] }
|
||
}
|
||
}, {});
|
||
assert(r3.length === 1, `$day failed`);
|
||
});
|
||
|
||
it('[6.2]date expression $dayOfMonth $dayOfWeek $dayOfYear', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 2024-06-15 是星期六,是一年中的第167天
|
||
const specificDate = new Date('2024-06-15T14:30:45.000Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificDate,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
|
||
// 测试 $dayOfMonth
|
||
const r1 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $dayOfMonth: { '#attr': 'refreshedAt' } }, 15] }
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `$dayOfMonth failed`);
|
||
|
||
// 测试 $dayOfWeek (MySQL: 1=周日, 7=周六; 2024-06-15是周六)
|
||
const r2 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $dayOfWeek: { '#attr': 'refreshedAt' } }, 7] }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `$dayOfWeek failed`);
|
||
|
||
// 测试 $dayOfYear (2024-06-15 是第167天)
|
||
const r3 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $dayOfYear: { '#attr': 'refreshedAt' } }, 167] }
|
||
}
|
||
}, {});
|
||
assert(r3.length === 1, `$dayOfYear failed`);
|
||
});
|
||
|
||
it('[6.3]date expression $weekday $weekOfYear', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 2024-06-15 是第24周
|
||
const specificDate = new Date('2024-06-15T14:30:45.000Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificDate,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $weekday (MySQL WEEKDAY: 0=周一, 6=周日; 2024-06-15是周六=5)
|
||
const r1 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $weekday: { '#attr': 'refreshedAt' } }, 5] }
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `$weekday failed`);
|
||
|
||
// 测试 $weekOfYear
|
||
const r2 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: { $eq: [{ $weekOfYear: { '#attr': 'refreshedAt' } }, 24] }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `$weekOfYear failed`);
|
||
});
|
||
|
||
it('[6.4]$dateDiff with different units', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const now = Date.now();
|
||
const oneYearAgo = now - 365 * 24 * 3600 * 1000;
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: oneYearAgo,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试年份差异
|
||
const r1 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{ $dateDiff: [now, { '#attr': 'refreshedAt' }, 'y'] },
|
||
1
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `$dateDiff year failed`);
|
||
|
||
// 测试月份差异
|
||
const r2 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{ $dateDiff: [now, { '#attr': 'refreshedAt' }, 'M'] },
|
||
11
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `$dateDiff month failed`);
|
||
});
|
||
|
||
it('[6.5]$dateFloor with different units', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 2024-06-15 14:30:45.123
|
||
const specificDate = new Date('2024-06-15T14:30:45.123Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificDate,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
//TODO: 暂不支持
|
||
// // 测试向下取整到月 - 应该是 2024-06-01
|
||
const startOfMonth = new Date('2024-06-01T00:00:00.000Z').valueOf();
|
||
const r1 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{ $dateFloor: [{ '#attr': 'refreshedAt' }, 'M'] },
|
||
startOfMonth
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `$dateFloor month failed`);
|
||
|
||
// 测试向下取整到年 - 应该是 2024-01-01
|
||
const startOfYear = new Date('2024-01-01T00:00:00.000Z').valueOf();
|
||
const r2 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$gte: [
|
||
{ $dateFloor: [{ '#attr': 'refreshedAt' }, 'y'] },
|
||
startOfYear
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `$dateFloor year failed`);
|
||
});
|
||
|
||
// TODO: 暂不支持
|
||
it('[6.6]$dateCeil with different units', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
// 2024-06-15 14:30:45.123
|
||
const specificDate = new Date('2024-06-15T14:30:45.123Z').valueOf();
|
||
const id1 = v4();
|
||
|
||
await store.operate('token', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: id1,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: specificDate,
|
||
value: v4(),
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试向上取整到月 - 应该是 2024-07-01
|
||
const startOfNextMonth = new Date('2024-07-01T00:00:00.000Z').valueOf();
|
||
const r1 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$lte: [
|
||
{ $dateCeil: [{ '#attr': 'refreshedAt' }, 'M'] },
|
||
startOfNextMonth
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1, `$dateCeil month failed`);
|
||
|
||
// 测试向上取整到年 - 应该是 2025-01-01
|
||
const startOfNextYear = new Date('2025-01-01T00:00:00.000Z').valueOf();
|
||
const r2 = await context.select('token', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: id1,
|
||
$expr: {
|
||
$lte: [
|
||
{ $dateCeil: [{ '#attr': 'refreshedAt' }, 'y'] },
|
||
startOfNextYear
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1, `$dateCeil year failed`);
|
||
});
|
||
|
||
// ==================== 聚合函数测试 ====================
|
||
|
||
it('[7.1]aggregation $$count', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const systemId = v4();
|
||
await store.operate('system', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: systemId,
|
||
name: 'test-count',
|
||
description: 'test',
|
||
config: { App: {} },
|
||
folder: '/test',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1', description: 't1', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app2', description: 't2', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app3', description: 't3', type: 'wechatMp', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
}
|
||
} as EntityDict['system']['CreateSingle'], context, {});
|
||
await context.commit();
|
||
|
||
const result = await store.aggregate('application', {
|
||
data: {
|
||
'#aggr': { systemId: 1 },
|
||
'#count-1': { id: 1 }
|
||
},
|
||
filter: { systemId }
|
||
}, context, {});
|
||
|
||
assert(result.length === 1, `Expected 1 group`);
|
||
assert(result[0]['#count-1'] === 3, `Expected count 3, got ${result[0]['#count-1']}`);
|
||
});
|
||
|
||
// TODO: 下面聚合暂不支持
|
||
// it('[7.2]aggregation $$sum', async () => {
|
||
// const context = new TestContext(store);
|
||
// await context.begin();
|
||
|
||
// const areaId = v4();
|
||
// await store.operate('house', {
|
||
// id: v4(),
|
||
// action: 'create',
|
||
// data: [
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 100.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 150.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 200.0 },
|
||
// ]
|
||
// }, context, {});
|
||
// await context.commit();
|
||
|
||
// const result = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': { areaId: 1 },
|
||
// '#sum-1': { $$sum: { '#attr': 'size' } }
|
||
// },
|
||
// filter: { areaId }
|
||
// }, context, {});
|
||
|
||
// assert(result.length === 1, `Expected 1 group`);
|
||
// assert(result[0]['#sum-1'] === 450, `Expected sum 450, got ${result[0]['#sum-1']}`);
|
||
// });
|
||
|
||
// it('[7.3]aggregation $$max $$min', async () => {
|
||
// const context = new TestContext(store);
|
||
// await context.begin();
|
||
|
||
// const areaId = v4();
|
||
// await store.operate('house', {
|
||
// id: v4(),
|
||
// action: 'create',
|
||
// data: [
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 80.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 120.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 200.0 },
|
||
// ]
|
||
// }, context, {});
|
||
// await context.commit();
|
||
|
||
// const result = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': { areaId: 1 },
|
||
// '#max-1': { $$max: { '#attr': 'size' } },
|
||
// '#min-1': { $$min: { '#attr': 'size' } }
|
||
// },
|
||
// filter: { areaId }
|
||
// }, context, {});
|
||
|
||
// assert(result.length === 1, `Expected 1 group`);
|
||
// assert(result[0]['#max-1'] === 200, `Expected max 200, got ${result[0]['#max-1']}`);
|
||
// assert(result[0]['#min-1'] === 80, `Expected min 80, got ${result[0]['#min-1']}`);
|
||
// });
|
||
|
||
// it('[7.4]aggregation $$avg', async () => {
|
||
// const context = new TestContext(store);
|
||
// await context.begin();
|
||
|
||
// const areaId = v4();
|
||
// await store.operate('house', {
|
||
// id: v4(),
|
||
// action: 'create',
|
||
// data: [
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 100.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 200.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 300.0 },
|
||
// ]
|
||
// }, context, {});
|
||
// await context.commit();
|
||
|
||
// const result = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': { areaId: 1 },
|
||
// '#avg-1': { $$avg: { '#attr': 'size' } }
|
||
// },
|
||
// filter: { areaId }
|
||
// }, context, {});
|
||
|
||
// assert(result.length === 1, `Expected 1 group`);
|
||
// assert(result[0]['#avg-1'] === 200, `Expected avg 200, got ${result[0]['#avg-1']}`);
|
||
// });
|
||
|
||
// it('[7.5]aggregation with multiple groups', async () => {
|
||
// const context = new TestContext(store);
|
||
// await context.begin();
|
||
|
||
// const areaId = v4();
|
||
// await store.operate('house', {
|
||
// id: v4(),
|
||
// action: 'create',
|
||
// data: [
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 100.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '杭州', size: 150.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '上海', size: 200.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '上海', size: 300.0 },
|
||
// { id: v4(), areaId, ownerId: v4(), district: '北京', size: 250.0 },
|
||
// ]
|
||
// }, context, {});
|
||
// await context.commit();
|
||
|
||
// const result = await store.aggregate('house', {
|
||
// data: {
|
||
// '#aggr': { district: 1 },
|
||
// '#count-1': { id: 1 },
|
||
// '#sum-1': { $$sum: { '#attr': 'size' } },
|
||
// '#avg-1': { $$avg: { '#attr': 'size' } }
|
||
// },
|
||
// filter: { areaId }
|
||
// }, context, {});
|
||
|
||
// assert(result.length === 3, `Expected 3 groups, got ${result.length}`);
|
||
|
||
// const hz = result.find(r => r['#data']?.district === '杭州');
|
||
// const sh = result.find(r => r['#data']?.district === '上海');
|
||
// const bj = result.find(r => r['#data']?.district === '北京');
|
||
|
||
// assert(hz && hz['#count-1'] === 2 && hz['#sum-1'] === 250, `杭州 aggregation failed`);
|
||
// assert(sh && sh['#count-1'] === 2 && sh['#sum-1'] === 500, `上海 aggregation failed`);
|
||
// assert(bj && bj['#count-1'] === 1 && bj['#sum-1'] === 250, `北京 aggregation failed`);
|
||
// });
|
||
|
||
// ==================== 复合表达式测试 ====================
|
||
|
||
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(),
|
||
entity: 'mobile',
|
||
entityId: v4(),
|
||
refreshedAt: twoDaysAgo,
|
||
value: v4(),
|
||
},
|
||
{
|
||
id: id2,
|
||
env: { type: 'web' } as any,
|
||
applicationId: v4(),
|
||
userId: v4(),
|
||
playerId: v4(),
|
||
entity: 'mobile',
|
||
entityId: 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',
|
||
config: { App: {} },
|
||
folder: '/test',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: appId1,
|
||
name: 'parent_app1',
|
||
description: 't1',
|
||
type: 'web',
|
||
config: { type: 'web', passport: [] } as any
|
||
}
|
||
},
|
||
{
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: appId2,
|
||
name: 'child_app',
|
||
description: 't2',
|
||
type: 'web',
|
||
config: { type: 'web', passport: [] } as any
|
||
}
|
||
}
|
||
]
|
||
}
|
||
} as EntityDict['system']['CreateSingle'], context, {});
|
||
await context.commit();
|
||
|
||
// TODO: 这里有问题
|
||
// // 查询: 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 && r1[0].id === appId1, `Cross-entity expression failed`);
|
||
});
|
||
|
||
// ==================== JSON 操作符补充测试 ====================
|
||
|
||
it('[9.1]json $exists operator', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
|
||
await store.operate('oper', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
action: 'test1',
|
||
data: { name: 'xc', age: 25 },
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
},
|
||
{
|
||
id: id2,
|
||
action: 'test2',
|
||
data: { name: 'zz' }, // 没有 age 字段
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
}
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 $exists: true - 检查 age 字段存在
|
||
const r1 = await context.select('oper', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
data: { age: { $exists: true } }
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id1, `$exists true failed`);
|
||
|
||
// 测试 $exists: false - 检查 age 字段不存在
|
||
const r2 = await context.select('oper', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
data: { age: { $exists: false } }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].id === id2, `$exists false failed`);
|
||
|
||
// 暂不支持这种写法
|
||
// 测试 $exists: 'keyName' - 检查 JSON 对象是否包含指定键
|
||
// const r3 = await context.select('oper', {
|
||
// data: { id: 1 },
|
||
// filter: {
|
||
// id: { $in: [id1, id2] },
|
||
// data: { $exists: 'age' }
|
||
// }
|
||
// }, {});
|
||
// assert(r3.length === 1 && r3[0].id === id1, `$exists keyName failed`);
|
||
});
|
||
|
||
it('[9.2]json nested $and $or in filter', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
const id3 = v4();
|
||
|
||
await store.operate('oper', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
action: 'test1',
|
||
data: { name: 'alice', role: 'admin', level: 10 },
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
},
|
||
{
|
||
id: id2,
|
||
action: 'test2',
|
||
data: { name: 'bob', role: 'user', level: 5 },
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
},
|
||
{
|
||
id: id3,
|
||
action: 'test3',
|
||
data: { name: 'charlie', role: 'admin', level: 3 },
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
}
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试: (role = 'admin' AND level > 5) OR (role = 'user')
|
||
const r1 = await context.select('oper', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2, id3] },
|
||
data: {
|
||
$or: [
|
||
{
|
||
$and: [
|
||
{ role: 'admin' },
|
||
{ level: { $gt: 5 } }
|
||
]
|
||
},
|
||
{ role: 'user' }
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 2, `Nested $and $or failed, expected 2, got ${r1.length}`);
|
||
const ids = r1.map(r => r.id);
|
||
assert(ids.includes(id1) && ids.includes(id2), `Expected id1 and id2`);
|
||
});
|
||
|
||
it('[9.3]json array index access in filter', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
|
||
await store.operate('oper', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{
|
||
id: id1,
|
||
action: 'test1',
|
||
data: { scores: [90, 85, 95] },
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
},
|
||
{
|
||
id: id2,
|
||
action: 'test2',
|
||
data: { scores: [70, 75, 80] },
|
||
targetEntity: 'test',
|
||
bornAt: Date.now(),
|
||
}
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试数组第一个元素 > 80
|
||
const r1 = await context.select('oper', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
data: {
|
||
scores: [{ $gt: 80 }]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id1, `Array index filter failed`);
|
||
|
||
// 测试数组第三个元素 = 95
|
||
const r2 = await context.select('oper', {
|
||
data: { id: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
data: {
|
||
scores: [undefined, undefined, 95]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].id === id1, `Array specific index filter failed`);
|
||
});
|
||
|
||
// ==================== $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' },
|
||
{ id: id2, name: 'user2', nickname: 'n2' },
|
||
{ id: id3, name: 'user3', nickname: 'n3' },
|
||
]
|
||
}, 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`);
|
||
});
|
||
|
||
// ==================== 字符串 $startsWith 在表达式中测试 ====================
|
||
|
||
it('[10.2]string $startsWith in expression', 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: 'test_admin', nickname: 'test' },
|
||
{ id: id2, name: 'test_user', nickname: 'test' },
|
||
{ id: id3, name: 'admin_role', nickname: 'admin1' },
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 测试 name 以 nickname 开头
|
||
const r1 = await context.select('user', {
|
||
data: { id: 1, name: 1, nickname: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2, id3] },
|
||
$expr: {
|
||
$startsWith: [
|
||
{ '#attr': 'name' },
|
||
{ '#attr': 'nickname' }
|
||
]
|
||
}
|
||
}
|
||
}, {});
|
||
assert(r1.length === 2, `$startsWith with two attrs failed, expected 2, got ${r1.length}`);
|
||
const ids = r1.map(r => r.id);
|
||
assert(ids.includes(id1) && ids.includes(id2), `Expected id1 and id2`);
|
||
});
|
||
|
||
// ==================== 投影中的表达式测试 ====================
|
||
|
||
it('[11.1]expression in projection', 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
|
||
const r1 = await context.select('house', {
|
||
data: {
|
||
id: 1,
|
||
size: 1,
|
||
$expr: {
|
||
$multiply: [{ '#attr': 'size' }, 2]
|
||
}
|
||
},
|
||
filter: { id: id1 }
|
||
}, {});
|
||
|
||
assert(r1.length === 1, `Expression in projection failed`);
|
||
assert(r1[0].$expr == 200, `Expected $expr = 200, got ${r1[0].$expr}`);
|
||
});
|
||
|
||
it('[11.2]multiple expressions in projection', 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();
|
||
|
||
// 多个表达式在投影中
|
||
const r1 = await context.select('house', {
|
||
data: {
|
||
id: 1,
|
||
size: 1,
|
||
$expr1: {
|
||
$multiply: [{ '#attr': 'size' }, 2]
|
||
},
|
||
$expr2: {
|
||
$add: [{ '#attr': 'size' }, 50]
|
||
},
|
||
$expr3: {
|
||
$gt: [{ '#attr': 'size' }, 80]
|
||
}
|
||
},
|
||
filter: { id: id1 }
|
||
}, {});
|
||
|
||
assert(r1.length === 1, `Multiple expressions failed`);
|
||
assert(r1[0].$expr1 == 200, `Expected $expr1 = 200`);
|
||
assert(r1[0].$expr2 == 150, `Expected $expr2 = 150`);
|
||
assert(r1[0].$expr3 == true || r1[0].$expr3 == 1, `Expected $expr3 = true`);
|
||
});
|
||
|
||
// ==================== 子查询中的表达式测试 ====================
|
||
|
||
it('[12.1]expression in subquery filter', 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: 'system_a',
|
||
description: 'test',
|
||
config: { App: {} },
|
||
folder: '/a',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app_a1', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app_a2', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
},
|
||
{
|
||
id: systemId2,
|
||
name: 'system_b',
|
||
description: 'test',
|
||
config: { App: {} },
|
||
folder: '/b',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'other_b1', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
}
|
||
]
|
||
} as EntityDict['system']['CreateMulti'], context, {});
|
||
await context.commit();
|
||
|
||
// 查询: 存在以 'app_' 开头的 application 的 system
|
||
const r1 = await context.select('system', {
|
||
data: { id: 1, name: 1 },
|
||
filter: {
|
||
id: { $in: [systemId1, systemId2] },
|
||
application$system: {
|
||
name: { $startsWith: 'app_' }
|
||
}
|
||
}
|
||
}, {});
|
||
|
||
assert(r1.length === 1 && r1[0].id === systemId1, `Subquery with expression failed`);
|
||
});
|
||
|
||
// ==================== 排序中使用表达式 ====================
|
||
|
||
it('[13.1]sorter with expression', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
const id3 = v4();
|
||
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{ id: id1, areaId: v4(), ownerId: v4(), district: '杭州', size: 150.0 },
|
||
{ id: id2, areaId: v4(), ownerId: v4(), district: '上海', size: 100.0 },
|
||
{ id: id3, areaId: v4(), ownerId: v4(), district: '北京', size: 200.0 },
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 按 size 降序排列
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: { id: { $in: [id1, id2, id3] } },
|
||
sorter: [
|
||
{ $attr: { size: 1 }, $direction: 'desc' }
|
||
]
|
||
}, {});
|
||
|
||
assert(r1.length === 3, `Sorter failed`);
|
||
assert(r1[0].id === id3, `Expected first to be id3 (size 200)`);
|
||
assert(r1[1].id === id1, `Expected second to be id1 (size 150)`);
|
||
assert(r1[2].id === id2, `Expected third to be id2 (size 100)`);
|
||
});
|
||
|
||
// ==================== 分页测试 ====================
|
||
|
||
it('[14.1]pagination with indexFrom and count', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const ids: string[] = [];
|
||
const data = [] as any[];
|
||
for (let i = 0; i < 10; i++) {
|
||
const id = v4();
|
||
ids.push(id);
|
||
data.push({
|
||
id,
|
||
areaId: v4(),
|
||
ownerId: v4(),
|
||
district: `district_${i}`,
|
||
size: (i + 1) * 10.0,
|
||
});
|
||
}
|
||
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 获取第 3-5 条记录 (indexFrom=2, count=3)
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: { id: { $in: ids } },
|
||
sorter: [{ $attr: { size: 1 }, $direction: 'asc' }],
|
||
indexFrom: 2,
|
||
count: 3,
|
||
}, {});
|
||
|
||
assert(r1.length === 3, `Pagination failed, expected 3, got ${r1.length}`);
|
||
assert(r1[0].size === 30, `Expected first size 30, got ${r1[0].size}`);
|
||
assert(r1[1].size === 40, `Expected second size 40, got ${r1[1].size}`);
|
||
assert(r1[2].size === 50, `Expected third size 50, got ${r1[2].size}`);
|
||
|
||
// 获取最后 2 条
|
||
const r2 = await context.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: { id: { $in: ids } },
|
||
sorter: [{ $attr: { size: 1 }, $direction: 'asc' }],
|
||
indexFrom: 8,
|
||
count: 5, // 超出范围
|
||
}, {});
|
||
|
||
assert(r2.length === 2, `Pagination overflow failed, expected 2, got ${r2.length}`);
|
||
});
|
||
|
||
// ==================== FOR UPDATE 锁测试 ====================
|
||
|
||
it('[15.1]select for update', 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();
|
||
|
||
await context.begin();
|
||
// FOR UPDATE 查询
|
||
const r1 = await store.select('house', {
|
||
data: { id: 1, size: 1 },
|
||
filter: { id: id1 }
|
||
}, context, { forUpdate: true });
|
||
|
||
assert(r1.length === 1, `FOR UPDATE select failed`);
|
||
await context.commit();
|
||
});
|
||
|
||
// ==================== includedDeleted 选项测试 ====================
|
||
|
||
it('[16.1]select with includedDeleted option', 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();
|
||
|
||
// 删除记录
|
||
await context.begin();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'remove',
|
||
data: {},
|
||
filter: { id: id1 }
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 正常查询应该查不到
|
||
const r1 = await context.select('house', {
|
||
data: { id: 1 },
|
||
filter: { id: id1 }
|
||
}, {});
|
||
assert(r1.length === 0, `Deleted record should not be visible`);
|
||
|
||
// 使用 includedDeleted 应该能查到
|
||
const r2 = await store.select('house', {
|
||
data: { id: 1 },
|
||
filter: { id: id1 }
|
||
}, context, { includedDeleted: true });
|
||
assert(r2.length === 1, `includedDeleted should show deleted record`);
|
||
});
|
||
|
||
// ==================== 物理删除测试 ====================
|
||
|
||
it('[16.2]delete physically', 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();
|
||
|
||
// TODO: 暂不支持物理删除测试
|
||
// 物理删除
|
||
await context.begin();
|
||
await store.operate('house', {
|
||
id: v4(),
|
||
action: 'remove',
|
||
data: {},
|
||
filter: { id: id1 }
|
||
}, context, { deletePhysically: true });
|
||
await context.commit();
|
||
|
||
// 即使使用 includedDeleted 也查不到
|
||
const r1 = await store.select('house', {
|
||
data: { id: 1 },
|
||
filter: { id: id1 }
|
||
}, context, { includedDeleted: true });
|
||
assert(r1.length === 0, `Physically deleted record should not exist`);
|
||
});
|
||
|
||
// ==================== 跨表 JOIN 更新测试 ====================
|
||
|
||
// TODO: 下面会出现死锁,暂不知道为什么
|
||
it('[17.1]update with join filter', 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: 'join_test_system',
|
||
description: 'test',
|
||
config: { App: {} },
|
||
folder: '/test',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: appId1, name: 'join_app1', description: 'old', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
{ id: v4(), action: 'create', data: { id: appId2, name: 'join_app2', description: 'old', type: 'wechatMp', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
}
|
||
} as EntityDict['system']['CreateSingle'], context, {});
|
||
await context.commit();
|
||
|
||
// 通过 system 过滤更新 application
|
||
await context.begin();
|
||
await store.operate('application', {
|
||
id: v4(),
|
||
action: 'update',
|
||
data: { description: 'updated' },
|
||
filter: {
|
||
system: {
|
||
name: 'join_test_system'
|
||
},
|
||
type: 'web'
|
||
}
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 验证只更新了 type='web' 的记录
|
||
const r1 = await context.select('application', {
|
||
data: { id: 1, description: 1 },
|
||
filter: { id: appId1 }
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].description === 'updated', `Join update failed for appId1`);
|
||
|
||
const r2 = await context.select('application', {
|
||
data: { id: 1, description: 1 },
|
||
filter: { id: appId2 }
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].description === 'old', `Join update should not affect appId2`);
|
||
});
|
||
|
||
// ==================== 空值处理测试 ====================
|
||
|
||
// TODO: 空值会报错
|
||
it('[18.1]null value handling', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
|
||
await store.operate('user', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{ id: id1, name: 'user_with_ref', nickname: 'u1', refId: v4() },
|
||
{ id: id2, name: 'user_without_ref', nickname: 'u2' }, // refId 为 null
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 查询 refId 为 null 的记录
|
||
const r1 = await context.select('user', {
|
||
data: { id: 1, name: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
refId: null as any
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id2, `Null filter failed`);
|
||
|
||
// 查询 refId 不为 null 的记录
|
||
const r2 = await context.select('user', {
|
||
data: { id: 1, name: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
refId: { $ne: null as any }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 1 && r2[0].id === id1, `Not null filter failed`);
|
||
});
|
||
|
||
// ==================== 枚举类型测试 ====================
|
||
|
||
it('[19.1]enum type insert and filter', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const id1 = v4();
|
||
const id2 = v4();
|
||
|
||
await store.operate('application', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: [
|
||
{ id: id1, name: 'web_app', description: 't', type: 'web', systemId: v4(), config: { type: 'web', passport: [] } as any },
|
||
{ id: id2, name: 'mp_app', description: 't', type: 'wechatMp', systemId: v4(), config: { type: 'web', passport: [] } as any },
|
||
]
|
||
}, context, {});
|
||
await context.commit();
|
||
|
||
// 按枚举值过滤
|
||
const r1 = await context.select('application', {
|
||
data: { id: 1, type: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
type: 'web'
|
||
}
|
||
}, {});
|
||
assert(r1.length === 1 && r1[0].id === id1, `Enum filter failed`);
|
||
|
||
// 枚举 $in 查询
|
||
const r2 = await context.select('application', {
|
||
data: { id: 1, type: 1 },
|
||
filter: {
|
||
id: { $in: [id1, id2] },
|
||
type: { $in: ['web', 'wechatMp'] }
|
||
}
|
||
}, {});
|
||
assert(r2.length === 2, `Enum $in filter failed`);
|
||
});
|
||
|
||
// ==================== 复杂嵌套查询测试 ====================
|
||
|
||
it('[20.1]deeply nested filter', async () => {
|
||
const store = storeGetter();
|
||
const context = new TestContext(store);
|
||
await context.begin();
|
||
|
||
const platformId = v4();
|
||
const systemId = v4();
|
||
const appId = v4();
|
||
|
||
// 创建 platform -> system -> application 层级结构
|
||
await store.operate('platform', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: platformId,
|
||
name: 'test_platform',
|
||
description: 'test',
|
||
config: {} as any,
|
||
}
|
||
}, context, {});
|
||
|
||
await store.operate('system', {
|
||
id: v4(),
|
||
action: 'create',
|
||
data: {
|
||
id: systemId,
|
||
name: 'nested_system',
|
||
description: 'test',
|
||
config: { App: {} },
|
||
folder: '/test',
|
||
platformId,
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: appId, name: 'nested_app', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
}
|
||
} as EntityDict['system']['CreateSingle'], context, {});
|
||
await context.commit();
|
||
|
||
// 三层嵌套查询: application -> system -> platform
|
||
const r1 = await context.select('application', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
system: {
|
||
id: 1,
|
||
name: 1,
|
||
platform: {
|
||
id: 1,
|
||
name: 1,
|
||
}
|
||
}
|
||
},
|
||
filter: {
|
||
id: appId,
|
||
system: {
|
||
platform: {
|
||
name: 'test_platform'
|
||
}
|
||
}
|
||
}
|
||
}, {});
|
||
|
||
assert(r1.length === 1, `Deeply nested query failed`);
|
||
assert(r1[0].system?.platform?.name === 'test_platform', `Nested projection 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',
|
||
config: { App: {} },
|
||
folder: '/test1',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1_1', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1_2', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app1_3', description: 't', type: 'wechatMp', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
},
|
||
{
|
||
id: systemId2,
|
||
name: 'agg_system2',
|
||
description: 'test',
|
||
config: { App: {} },
|
||
folder: '/test2',
|
||
platformId: 'p1',
|
||
application$system: [
|
||
{ id: v4(), action: 'create', data: { id: v4(), name: 'app2_1', description: 't', type: 'web', config: { type: 'web', passport: [] } as any } },
|
||
]
|
||
}
|
||
]
|
||
} 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`);
|
||
});
|
||
|
||
} |