Skip to content
This repository has been archived by the owner on Dec 23, 2021. It is now read-only.

Commit

Permalink
feat: integration test for school
Browse files Browse the repository at this point in the history
  • Loading branch information
Soontao committed Jul 31, 2020
1 parent 60f80b1 commit e536226
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "node",
"request": "launch",
"name": "Typed Example Server",
"program": "${workspaceFolder}/node_modules/.bin/ts-node",
"program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
"args": [
"src/example/typed_simple_server"
],
Expand Down
40 changes: 14 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
],
"dependencies": {
"@newdash/newdash": "^5.12.0",
"@odata/metadata": "^0.1.12",
"@odata/metadata": "^0.1.13",
"@odata/parser": "^0.1.39",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
Expand Down Expand Up @@ -71,8 +71,8 @@
"eslint-plugin-es-beautifier": "^1.0.1",
"event-stream": "^3.3.4",
"jest": "^26.1.0",
"light-odata": "^2.5.2",
"mongodb": "^3.5.9",
"light-odata": "^2.7.0",
"mongodb": "^3.6.0",
"mssql": "^4.1.0",
"odata-v4-inmemory": "^0.1.7",
"odata-v4-mongodb": "^0.1.12",
Expand Down
1 change: 1 addition & 0 deletions src/example/typed_simple_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Class extends BaseODataModel {
@ODataNavigation({ type: 'ManyToOne', entity: () => Teacher, foreignKey: "teacherOneId" })
teacher: any;

// GET http://localhost:50000/Classes?$expand=students($expand=student)
@ODataNavigation({ type: 'OneToMany', entity: () => RelStudentClassAssignment, foreignKey: "classId" })
students: any;

Expand Down
4 changes: 2 additions & 2 deletions src/lib/processor/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ export class ODataProcessor extends Transform {

if (typeof queryAst == 'string') {
queryAst = this.serverType.parser.query(queryAst, {
metadata: this.resourcePath.ast.metadata || this.serverType.$metadata().edmx
// metadata: this.resourcePath.ast.metadata || this.serverType.$metadata().edmx
});

if (!include) {
Expand Down Expand Up @@ -1842,7 +1842,7 @@ export class ODataProcessor extends Transform {
filterAst = qs.parse(filterAst).$filter;
if (typeof filterAst == 'string') {
filterAst = this.serverType.parser.filter(filterAst, {
metadata: this.resourcePath.ast.metadata || this.serverType.$metadata().edmx
// metadata: this.resourcePath.ast.metadata || this.serverType.$metadata().edmx
});
const lastNavigationPath = this.resourcePath.navigation[this.resourcePath.navigation.length - 1];
const queryType = lastNavigationPath.type == 'QualifiedEntityTypeName' ?
Expand Down
4 changes: 2 additions & 2 deletions src/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ export function ODataErrorHandler(err, _, res, next) {
res.send({
error: {
code: statusCode,
message: err.message
// stack: process.env.ODATA_V4_ENABLE_STACKTRACE ? undefined : err.stack
message: err.message,
stack: process.env.ODATA_V4_ENABLE_STACKTRACE ? undefined : err.stack
}
});
} else {
Expand Down
41 changes: 25 additions & 16 deletions src/lib/typeorm/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ import { NotImplementedError, ServerInternalError } from '../error';
import { BaseODataModel } from './model';

const KEY_ODATA_ENTITY_SET = 'odata.entity:entity_set_name';
const KEY_ODATA_PROP_NAVIGATION = 'odata.entity:entity_prop_navigation';

/**
* define odata action for entity
*
* @alias Edm.Action
*/
export const ODataAction = Edm.Action;
/**
* set entity set name for odata entity
*
* @param entitySetName
*/
export function ODataEntitySetName(entitySetName: string) {
return function(target) {
return function (target) {
Reflect.defineMetadata(KEY_ODATA_ENTITY_SET, entitySetName, target);
};
}
Expand All @@ -36,7 +43,7 @@ export function getODataEntitySetName(target: any): string {
* @param options
*/
export function ODataModel(options: EntityOptions = {}, entitySetName?: string) {
return function(target: any): void {
return function (target: any): void {
Entity(options)(target);
if (entitySetName) {
ODataEntitySetName(entitySetName)(target);
Expand All @@ -52,7 +59,7 @@ export function ODataModel(options: EntityOptions = {}, entitySetName?: string)
* @param options
*/
export function ODataColumn(options: ColumnOptions = {}) {
return function(object: any, propertyName: string): void {
return function (object: any, propertyName: string): void {
const { primary, length, precision, nullable } = options;

Column(options)(object, propertyName);
Expand Down Expand Up @@ -134,29 +141,31 @@ export interface NavigationOptions<T extends typeof BaseODataModel> {
* @param options
*/
export function ODataNavigation<T extends typeof BaseODataModel>(options: NavigationOptions<T>) {
return function(object: any, propertyName: string): void {
return function (target: any, propertyName: string): void {

if (isEmpty(options.foreignKey)) {
throw new ServerInternalError(`OneToMany navigation must define the ref 'foreign key' in ${object?.constructor?.name} ${propertyName}`);
throw new ServerInternalError(`OneToMany navigation must define the ref 'foreign key' in ${target?.constructor?.name} ${propertyName}`);
}

Reflect.defineMetadata(KEY_ODATA_PROP_NAVIGATION, options, target, propertyName);

switch (options.type) {
case 'OneToMany':
OneToMany(options.entity, options.foreignKey)(object, propertyName);
Edm.Collection(Edm.EntityType(Edm.ForwardRef(options.entity)))(object, propertyName);
Edm.ForeignKey(options.foreignKey)(object, propertyName);
OneToMany(options.entity, options.foreignKey)(target, propertyName);
Edm.Collection(Edm.EntityType(Edm.ForwardRef(options.entity)))(target, propertyName);
Edm.ForeignKey(options.foreignKey)(target, propertyName);
break;
case 'ManyToOne':
ManyToOne(options.entity)(object, propertyName);
JoinColumn({ name: options.foreignKey })(object, propertyName);
Edm.EntityType(Edm.ForwardRef(options.entity))(object, propertyName);
Edm.ForeignKey(options.foreignKey)(object, propertyName);
ManyToOne(options.entity)(target, propertyName);
JoinColumn({ name: options.foreignKey })(target, propertyName);
Edm.EntityType(Edm.ForwardRef(options.entity))(target, propertyName);
Edm.ForeignKey(options.foreignKey)(target, propertyName);
break;
case 'OneToOne':
OneToOne(options.entity)(object, propertyName);
Edm.EntityType(Edm.ForwardRef(options.entity))(object, propertyName);
JoinColumn({ name: options.foreignKey })(object, propertyName);
Edm.ForeignKey(options.foreignKey)(object, propertyName);
OneToOne(options.entity)(target, propertyName);
Edm.EntityType(Edm.ForwardRef(options.entity))(target, propertyName);
JoinColumn({ name: options.foreignKey })(target, propertyName);
Edm.ForeignKey(options.foreignKey)(target, propertyName);
default:
break;
}
Expand Down
6 changes: 5 additions & 1 deletion src/lib/typeorm/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ export const transformQueryAst = (node: ODataQuery, nameMapper: FieldNameMapper
}
};

traverseAst(traverser, node);
// only first level options
node.value.options.forEach((option) => {
traverseAst(traverser, option);
});


if (where && where.trim().length > 0) {
sqlQuery += ` WHERE ${where}`;
Expand Down
23 changes: 14 additions & 9 deletions src/lib/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Token, TokenType } from '@odata/parser/lib/lexer';
import { Literal } from './literal';
import * as qs from 'qs';
import { ODataServer } from './server';
import { ODataController } from './controller';
import * as Edm from './edm';
import { Literal } from './literal';
import { ODataServer } from './server';

export interface KeyValuePair {
name: string
Expand Down Expand Up @@ -121,7 +121,7 @@ export class ResourcePathVisitor {
}

protected async VisitQueryOptions(node: Token, context: any, type: any) {
await Promise.all(node.value.options.map(async(option) => await this.Visit(option, Object.assign({}, context), type)));
await Promise.all(node.value.options.map(async (option) => await this.Visit(option, Object.assign({}, context), type)));
}

protected VisitSelect(node: Token, context: any, type: any) {
Expand Down Expand Up @@ -244,14 +244,19 @@ export class ResourcePathVisitor {
}

protected async VisitExpand(node: Token, context: any, type: any) {
await Promise.all(node.value.items.map(async(item) => {
const expandPath = item.value.path.raw;
await Promise.all(node.value.items.map(async (item) => {

const part = item.value.path.value[0];
const expandPath = part.raw;
let visitor = this.includes[expandPath];

if (!visitor) {
visitor = new ResourcePathVisitor(node[ODATA_TYPE], this.entitySets);
this.includes[expandPath] = visitor;
}

await visitor.Visit(item, Object.assign({}, context), type);

}));
}

Expand All @@ -263,7 +268,7 @@ export class ResourcePathVisitor {
this.ast.type = TokenType.QueryOptions;
this.ast.raw = node.value.options.map((n) => n.raw).join('&');
this.query = qs.parse(this.ast.raw);
await Promise.all(node.value.options.map(async(item) => await this.Visit(item, Object.assign({}, context), type)));
await Promise.all(node.value.options.map(async (item) => await this.Visit(item, Object.assign({}, context), type)));
}
if (node.value.ref) {
await this.Visit(node.value.ref, Object.assign({}, context), type);
Expand Down Expand Up @@ -360,7 +365,7 @@ export class ResourcePathVisitor {
protected async VisitCompoundKey(node: Token, _: any, type: any) {
this.path += '(\\(';
const lastNavigationPart = this.navigation[this.navigation.length - 1];
lastNavigationPart.key = await Promise.all<KeyValuePair>(node.value.map(async(pair, i) => {
lastNavigationPart.key = await Promise.all<KeyValuePair>(node.value.map(async (pair, i) => {
this.path += i == node.value.length - 1 ? '([^,]+)' : '([^,]+,)';
node[ODATA_TYPENAME] = Edm.getTypeName(type, pair.value.key.value.name, this.serverType.container);
node[ODATA_TYPE] = Edm.getType(type, pair.value.key.value.name, this.serverType.container);
Expand Down Expand Up @@ -469,7 +474,7 @@ export class ResourcePathVisitor {
type = this.serverType.getController(type);
}
context.parameters = Edm.getParameters(type, part.name.split('.').pop());
await Promise.all(node.value.params.value.map(async(param, i) => {
await Promise.all(node.value.params.value.map(async (param, i) => {
await this.Visit(param, context);
if (i < node.value.params.value.length - 1) {
this.path += ',';
Expand All @@ -490,7 +495,7 @@ export class ResourcePathVisitor {
this.path += `/${part.name}`;
this.path += '(\\(';
context.parameters = Edm.getParameters(node[ODATA_TYPE], part.name);
await Promise.all(node.value.params.map(async(param) => await this.Visit(param, Object.assign({}, context))));
await Promise.all(node.value.params.map(async (param) => await this.Visit(param, Object.assign({}, context))));
delete context.parameters;
this.path += '\\))';
}
Expand Down
58 changes: 58 additions & 0 deletions src/test/typeorm/integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { shutdown } from '../utils/server';
import { Class, RelStudentClassAssignment, SchoolEntities, Student, Teacher } from './school_model';
import { createServerAndClient } from './utils';

describe('Typed OData Server Integration Test Suite', () => {

it('should run total integration tests', async () => {

const { server, client } = await createServerAndClient({
name: 'typed_server_integration_test_conn',
type: 'sqljs', synchronize: true,
entities: SchoolEntities
}, ...SchoolEntities);

try {
const students = client.getEntitySet<Student>('Students');
const classes = client.getEntitySet<Class>('Classes'); // renamed by decorator
const teachers = client.getEntitySet<Teacher>('Teachers');
const classRegistry = client.getEntitySet<RelStudentClassAssignment>('RelStudentClassAssignments');

const t1 = await teachers.create({ name: 'turing' });
const s1 = await students.create({ name: 'theo' });
const c1 = await classes.create({
name: 'computer science',
teacherOneId: t1.id,
desc: 'Computer science is the study of computation and information.'
});
const r1 = await classRegistry.create({ classId: c1.id, studentId: s1.id });

const full = await classes.retrieve(c1.id, client.newParam().expand('students($expand=student)'));

expect(full).toMatchObject({
'id': c1.id,
'name': c1.name,
'desc': c1.desc,
'teacherOneId': t1.id,
'students': [
{
'uuid': r1.uuid,
'studentId': s1.id,
'classId': c1.id,
'student': {
'id': s1.id,
'name': 'theo',
'age': null
}
}
]
});

} finally {
await shutdown(server);
}


});

});
29 changes: 29 additions & 0 deletions src/test/typeorm/school_model/Class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BaseODataModel, ODataColumn, ODataEntitySetName, ODataModel, ODataNavigation } from '../../../lib';
import { RelStudentClassAssignment } from './Rel';
import { Teacher } from './Teacher';

// indicate the entity set name for entity
@ODataEntitySetName('Classes')
@ODataModel()
export class Class extends BaseODataModel {

@ODataColumn({ primary: true, generated: 'increment' })
id: number;

@ODataColumn()
name: string;

@ODataColumn()
desc: string

@ODataColumn({ nullable: true })
teacherOneId: number;

@ODataNavigation({ type: 'ManyToOne', entity: () => Teacher, foreignKey: 'teacherOneId' })
teacher: any;

// GET http://localhost:50000/Classes?$expand=students($expand=student)
@ODataNavigation({ type: 'OneToMany', entity: () => RelStudentClassAssignment, foreignKey: 'classId' })
students: any;

}
Loading

0 comments on commit e536226

Please sign in to comment.