From 98bf8c9006e08c5b9ef6d416fa2a262c039a1c94 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 13 Nov 2024 22:50:27 +0530 Subject: [PATCH] test: add tests for self relationships (#10071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initially I thought that we will have to add special checks to allow relationships referencing itself. However, it turned out that not to be the case. Instead, it had more to do with how self relationships are defined in Mikro ORM. In case of `belongsTo` relationship we have to define the other side as well either as a `hasMany` or `hasOne`. For example: **❌ The following code will fail, because no children are defined for the parent** ```ts const user = model.define("user", { id: model.number(), username: model.text(), parent: model.belongsTo(() => user) }) ``` **✅ Addition of children relationship will make things work** ```ts const user = model.define("user", { id: model.number(), username: model.text(), parent: model.belongsTo(() => user, { mappedBy: "children" }), children: model.hasMany(() => user, { mappedBy: "parent" }), }) ``` We can see the similar setup here with our `ProductCategory` MikroORM entity. https://github.com/medusajs/medusa/blob/develop/packages/modules/product/src/models/product-category.ts#L87-L94 @adrien2p Correct me if I am wrong. But I have added the tests for now so that we know the behavior of self relationships --- .../src/dml/__tests__/entity-builder.spec.ts | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts index 75cd192d5a2a3..db304de5a9d9b 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -4702,6 +4702,236 @@ describe("Entity builder", () => { }, }) }) + + test("define entity with relationship to itself via hasMany", () => { + const user = model.define("user", { + id: model.number(), + username: model.text(), + parent: model.belongsTo(() => user, { mappedBy: "children" }), + children: model.hasMany(() => user, { mappedBy: "parent" }), + }) + + const [User] = toMikroOrmEntities([user]) + + expectTypeOf(new User()).toMatchTypeOf<{ + id: number + username: string + deleted_at: Date | null + parent: { + id: number + username: string + deleted_at: Date | null + } + children: { + id: number + username: string + deleted_at: Date | null + }[] + }>() + + const metaData = MetadataStorage.getMetadataFromDecorator(User) + expect(metaData.className).toEqual("User") + expect(metaData.path).toEqual("User") + expect(metaData.properties).toEqual({ + id: { + reference: "scalar", + type: "number", + columnType: "integer", + name: "id", + fieldName: "id", + nullable: false, + getter: false, + setter: false, + }, + username: { + reference: "scalar", + type: "string", + columnType: "text", + name: "username", + fieldName: "username", + nullable: false, + getter: false, + setter: false, + }, + parent: { + name: "parent", + reference: "m:1", + entity: "User", + persist: false, + nullable: false, + }, + parent_id: { + name: "parent_id", + reference: "m:1", + entity: "User", + columnType: "text", + fieldName: "parent_id", + mapToPk: true, + nullable: false, + onDelete: undefined, + isForeignKey: true, + }, + children: { + cascade: undefined, + entity: "User", + mappedBy: "parent", + name: "children", + orphanRemoval: true, + reference: "1:m", + }, + created_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "created_at", + fieldName: "created_at", + defaultRaw: "now()", + onCreate: expect.any(Function), + nullable: false, + getter: false, + setter: false, + }, + updated_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "updated_at", + fieldName: "updated_at", + defaultRaw: "now()", + onCreate: expect.any(Function), + onUpdate: expect.any(Function), + nullable: false, + getter: false, + setter: false, + }, + deleted_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "deleted_at", + fieldName: "deleted_at", + nullable: true, + getter: false, + setter: false, + }, + }) + }) + + test("define relationship with self via hasOne", () => { + const user = model.define("user", { + id: model.number(), + username: model.text(), + parent: model.belongsTo(() => user, { mappedBy: "child" }), + child: model.hasOne(() => user, { mappedBy: "parent" }), + }) + + const [User] = toMikroOrmEntities([user]) + + expectTypeOf(new User()).toMatchTypeOf<{ + id: number + username: string + deleted_at: Date | null + parent: { + id: number + username: string + deleted_at: Date | null + } + child: { + id: number + username: string + deleted_at: Date | null + } + }>() + + const metaData = MetadataStorage.getMetadataFromDecorator(User) + expect(metaData.className).toEqual("User") + expect(metaData.path).toEqual("User") + expect(metaData.properties).toEqual({ + id: { + reference: "scalar", + type: "number", + columnType: "integer", + name: "id", + fieldName: "id", + nullable: false, + getter: false, + setter: false, + }, + username: { + reference: "scalar", + type: "string", + columnType: "text", + name: "username", + fieldName: "username", + nullable: false, + getter: false, + setter: false, + }, + parent: { + name: "parent", + mappedBy: "child", + reference: "1:1", + entity: "User", + nullable: false, + onDelete: undefined, + owner: true, + }, + parent_id: { + name: "parent_id", + type: "string", + columnType: "text", + isForeignKey: true, + persist: false, + reference: "scalar", + getter: false, + setter: false, + nullable: false, + }, + child: { + cascade: undefined, + entity: "User", + mappedBy: "parent", + name: "child", + nullable: false, + reference: "1:1", + }, + created_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "created_at", + fieldName: "created_at", + defaultRaw: "now()", + onCreate: expect.any(Function), + nullable: false, + getter: false, + setter: false, + }, + updated_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "updated_at", + fieldName: "updated_at", + defaultRaw: "now()", + onCreate: expect.any(Function), + onUpdate: expect.any(Function), + nullable: false, + getter: false, + setter: false, + }, + deleted_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "deleted_at", + fieldName: "deleted_at", + nullable: true, + getter: false, + setter: false, + }, + }) + }) }) describe("Entity builder | manyToMany", () => {