Skip to content

Commit

Permalink
fix(validation): don't validate inherited STI props (#998)
Browse files Browse the repository at this point in the history
Inherited props of STI entities shouldn't be validated because they don't exist in real entity definition.

Closes #997
  • Loading branch information
whocaresk committed Oct 28, 2020
1 parent 29571d8 commit 63d1f57
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 2 deletions.
5 changes: 4 additions & 1 deletion packages/core/src/EntityManager.ts
Expand Up @@ -475,8 +475,11 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
return entity;
}

const meta = this.metadata.find(entityName)!;
const childMeta = this.metadata.getByDiscriminatorColumn(meta, data as EntityData<T>);

entity = Utils.isEntity<T>(data) ? data : this.getEntityFactory().create<T>(entityName, data as EntityData<T>, { merge: true, refresh, convertCustomTypes });
this.validator.validate(entity, data, this.metadata.find(entityName)!);
this.validator.validate(entity, data, childMeta ?? meta);
this.getUnitOfWork().merge(entity);

return entity;
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/entity/EntityValidator.ts
Expand Up @@ -9,6 +9,10 @@ export class EntityValidator {

validate<T extends AnyEntity<T>>(entity: T, payload: any, meta: EntityMetadata): void {
meta.props.forEach(prop => {
if (prop.inherited) {
return;
}

if ([ReferenceType.ONE_TO_MANY, ReferenceType.MANY_TO_MANY].includes(prop.reference)) {
this.validateCollection(entity, prop);
}
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/metadata/MetadataStorage.ts
@@ -1,4 +1,4 @@
import { EntityMetadata, AnyEntity, Dictionary } from '../typings';
import { EntityMetadata, AnyEntity, Dictionary, EntityData } from '../typings';
import { Utils } from '../utils/Utils';
import { MetadataError } from '../errors';
import { EntityManager } from '../EntityManager';
Expand Down Expand Up @@ -60,6 +60,18 @@ export class MetadataStorage {
return this.metadata;
}

getByDiscriminatorColumn<T>(meta: EntityMetadata<T>, data: EntityData<T>): EntityMetadata<T> | undefined {
const value = data[meta.root.discriminatorColumn!];

if (!value) {
return undefined;
}

const type = meta.root.discriminatorMap![value];

return this.metadata[type];
}

get<T extends AnyEntity<T> = any>(entity: string, init = false, validate = true): EntityMetadata<T> {
if (validate && !init && entity && !this.has(entity)) {
throw MetadataError.missingMetadata(entity);
Expand Down
122 changes: 122 additions & 0 deletions tests/issues/GH997.test.ts
@@ -0,0 +1,122 @@
import { Entity, MikroORM, PrimaryKey, Property, OneToMany, ManyToOne, Collection, QueryOrder } from '@mikro-orm/core';
import { SqliteDriver } from '@mikro-orm/sqlite';

abstract class Base {

@PrimaryKey()
id!: number;

}

@Entity({
discriminatorColumn: 'type',
abstract: true
})
class Parent extends Base {

@Property()
type!: string;

@OneToMany(() => Relation1, e => e.parent)
qaInfo = new Collection<Relation1>(this);

}

@Entity()
class Relation1 extends Base {

@ManyToOne()
parent!: Parent;

}

@Entity({discriminatorValue: 'Child1'})
class Child1 extends Parent {

@OneToMany(() => Child1Specific, e => e.child1)
rel = new Collection<Child1Specific>(this);

}

@Entity()
class Child1Specific extends Base {

@ManyToOne()
child1!: Child1;

}

@Entity({discriminatorValue: 'Child2'})
class Child2 extends Parent {}

describe('GH issue 997', () => {

let orm: MikroORM<SqliteDriver>;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [Base, Relation1, Child1Specific, Parent, Child1, Child2],
dbName: ':memory:',
type: 'sqlite',
});
await orm.getSchemaGenerator().ensureDatabase();
await orm.getSchemaGenerator().dropSchema();
await orm.getSchemaGenerator().createSchema();
});

afterAll(async () => {
await orm.close(true);
});

test(`GH issue 997`, async () => {
// for unknown reason, for me setup from GH845.test.ts doesn't work,
// complaining about default values for table columns,
// so i tweaked it until it works
const c1 = new Child1();
const c2 = new Child2();
await orm.em.persistAndFlush([c1, c2]);
orm.em.clear();

const [ci1, ci2]: [Child1, Child2] = await orm.em.find(Parent, {}, ['qaInfo.parent', 'rel'], { type: QueryOrder.ASC }) as any;

ci1.rel.add(new Child1Specific());
ci1.rel.add(new Child1Specific());
ci1.rel.add(new Child1Specific());
ci2.qaInfo.add(new Relation1());
ci2.qaInfo.add(new Relation1());
ci2.qaInfo.add(new Relation1());

await orm.em.persistAndFlush([ci1, ci2]);
orm.em.clear();

const results = await orm.em.createQueryBuilder(Parent)
.offset(0)
.limit(10)
.orderBy({type: QueryOrder.ASC})
.getResult();

const parents = await orm.em.populate(results, ['qaInfo.parent', 'rel']);

expect(parents[0]).toBeInstanceOf(Child1);
expect(parents[0].type).toBe('Child1');
expect(parents[0].qaInfo.length).toBe(0);
expect((parents[0] as Child1).rel.length).toBe(3);
expect((parents[0] as Child1).rel[0]).toBeInstanceOf(Child1Specific);
expect((parents[0] as Child1).rel[0].child1).toBeInstanceOf(Child1);
expect((parents[0] as Child1).rel[1]).toBeInstanceOf(Child1Specific);
expect((parents[0] as Child1).rel[1].child1).toBeInstanceOf(Child1);
expect((parents[0] as Child1).rel[2]).toBeInstanceOf(Child1Specific);
expect((parents[0] as Child1).rel[2].child1).toBeInstanceOf(Child1);
expect(parents[1]).toBeInstanceOf(Child2);
expect(parents[1].type).toBe('Child2');
expect(parents[1].qaInfo.length).toBe(3);
expect(parents[1].qaInfo[0]).toBeInstanceOf(Relation1);
expect(parents[1].qaInfo[0].parent).toBeInstanceOf(Child2);
expect(parents[1].qaInfo[1]).toBeInstanceOf(Relation1);
expect(parents[1].qaInfo[1].parent).toBeInstanceOf(Child2);
expect(parents[1].qaInfo[2]).toBeInstanceOf(Relation1);
expect(parents[1].qaInfo[2].parent).toBeInstanceOf(Child2);
expect((parents[1] as Child1).rel).toBeUndefined();
});

});

0 comments on commit 63d1f57

Please sign in to comment.