From 43d5cdb4777ed4cc1d7ba2a287e68376d06dacc3 Mon Sep 17 00:00:00 2001 From: Sakura Date: Mon, 13 Dec 2021 10:16:43 +0800 Subject: [PATCH] test: add test for #8443 --- .../8443/closure-table/closure-table.ts | 597 ++++++++++++++++++ .../8443/closure-table/entity/Category.ts | 31 + .../8443/materialized-path/entity/Category.ts | 31 + .../materialized-path/materialized-path.ts | 573 +++++++++++++++++ .../8443/nested-set/entity/Category.ts | 31 + .../8443/nested-set/nested-set.ts | 509 +++++++++++++++ 6 files changed, 1772 insertions(+) create mode 100644 test/github-issues/8443/closure-table/closure-table.ts create mode 100644 test/github-issues/8443/closure-table/entity/Category.ts create mode 100644 test/github-issues/8443/materialized-path/entity/Category.ts create mode 100644 test/github-issues/8443/materialized-path/materialized-path.ts create mode 100644 test/github-issues/8443/nested-set/entity/Category.ts create mode 100644 test/github-issues/8443/nested-set/nested-set.ts diff --git a/test/github-issues/8443/closure-table/closure-table.ts b/test/github-issues/8443/closure-table/closure-table.ts new file mode 100644 index 0000000000..884cd6fe21 --- /dev/null +++ b/test/github-issues/8443/closure-table/closure-table.ts @@ -0,0 +1,597 @@ +import "reflect-metadata"; +import {Category} from "./entity/Category"; +import {Connection} from "../../../../src/connection/Connection"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; + +describe("github issues > #8443 QueryFailedError when tree entity with JoinColumn > closure-table", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [Category] + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("categories should be attached via parent and saved properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + a11.parentCategory = a1; + await categoryRepository.save(a11); + + const a12 = new Category(); + a12.name = "a12"; + a12.parentCategory = a1; + await categoryRepository.save(a12); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(3); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(3); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly and everything must be saved in cascades", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + const a1ChildrenNames = a1Children.map(child => child.name); + a1ChildrenNames.length.should.be.equal(5); + a1ChildrenNames.should.deep.include("a1"); + a1ChildrenNames.should.deep.include("a11"); + a1ChildrenNames.should.deep.include("a12"); + a1ChildrenNames.should.deep.include("a111"); + a1ChildrenNames.should.deep.include("a112"); + }))); + + // todo: finish implementation and implement on other trees + it.skip("categories should remove removed children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + const a11 = new Category(); + a11.name = "a11"; + const a12 = new Category(); + a12.name = "a12"; + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const a1Children1 = await categoryRepository.findDescendants(a1); + const a1ChildrenNames1 = a1Children1.map(child => child.name); + a1ChildrenNames1.length.should.be.equal(3); + a1ChildrenNames1.should.deep.include("a1"); + a1ChildrenNames1.should.deep.include("a11"); + a1ChildrenNames1.should.deep.include("a12"); + + // a1.childCategories = [a11]; + // await categoryRepository.save(a1); + // + // const a1Children2 = await categoryRepository.findDescendants(a1); + // const a1ChildrenNames2 = a1Children2.map(child => child.name); + // a1ChildrenNames2.length.should.be.equal(3); + // a1ChildrenNames2.should.deep.include("a1"); + // a1ChildrenNames2.should.deep.include("a11"); + // a1ChildrenNames2.should.deep.include("a12"); + }))); + + // todo: finish implementation and implement on other trees + it.skip("sub-category should be removed with all its children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + const a11 = new Category(); + a11.name = "a11"; + const a12 = new Category(); + a12.name = "a12"; + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const a1Children1 = await categoryRepository.findDescendants(a1); + const a1ChildrenNames1 = a1Children1.map(child => child.name); + a1ChildrenNames1.length.should.be.equal(3); + a1ChildrenNames1.should.deep.include("a1"); + a1ChildrenNames1.should.deep.include("a11"); + a1ChildrenNames1.should.deep.include("a12"); + + await categoryRepository.remove(a1); + + // a1.childCategories = [a11]; + // await categoryRepository.save(a1); + // + // const a1Children2 = await categoryRepository.findDescendants(a1); + // const a1ChildrenNames2 = a1Children2.map(child => child.name); + // a1ChildrenNames2.length.should.be.equal(3); + // a1ChildrenNames2.should.deep.include("a1"); + // a1ChildrenNames2.should.deep.include("a11"); + // a1ChildrenNames2.should.deep.include("a12"); + }))); + + it("findTrees() tests > findTrees should load all category roots and attached children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findTrees() tests > findTrees should load multiple category roots if they exist", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const b1 = new Category(); + b1.name = "b1"; + + const b11 = new Category(); + b11.name = "b11"; + + const b12 = new Category(); + b12.name = "b12"; + + b1.childCategories = [b11, b12]; + await categoryRepository.save(b1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }, { + id: b1.id, + name: "b1", + childCategories: [ + { + id: b11.id, + name: "b11", + childCategories: [] + }, + { + id: b12.id, + name: "b12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findTrees() tests > findTrees should filter by depth if optionally provided", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + + const categoriesTreeWithEmptyOptions = await categoryRepository.findTrees({}); + categoriesTreeWithEmptyOptions.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + + const categoriesTreeWithDepthZero = await categoryRepository.findTrees({depth: 0}); + categoriesTreeWithDepthZero.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [] + } + ]); + + const categoriesTreeWithDepthOne = await categoryRepository.findTrees({depth: 1}); + categoriesTreeWithDepthOne.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findDescendantsTree() tests > findDescendantsTree should load all category descendents and nested children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findDescendantsTree(a1); + categoriesTree.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + }))); + + it("findDescendantsTree() tests > findDescendantsTree should filter by depth if optionally provided", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findDescendantsTree(a1); + categoriesTree.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + + const categoriesTreeWithEmptyOptions = await categoryRepository.findDescendantsTree(a1, {}); + categoriesTreeWithEmptyOptions.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + + const categoriesTreeWithDepthZero = await categoryRepository.findDescendantsTree(a1, {depth: 0}); + categoriesTreeWithDepthZero.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [] + }); + + const categoriesTreeWithDepthOne = await categoryRepository.findDescendantsTree(a1, {depth: 1}); + categoriesTreeWithDepthOne.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + }))); +}); diff --git a/test/github-issues/8443/closure-table/entity/Category.ts b/test/github-issues/8443/closure-table/entity/Category.ts new file mode 100644 index 0000000000..1df8da16f6 --- /dev/null +++ b/test/github-issues/8443/closure-table/entity/Category.ts @@ -0,0 +1,31 @@ +import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../src/decorator/columns/Column"; +import {TreeParent} from "../../../../../src/decorator/tree/TreeParent"; +import {TreeChildren} from "../../../../../src/decorator/tree/TreeChildren"; +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {Tree} from "../../../../../src/decorator/tree/Tree"; +import { JoinColumn } from "../../../../../src"; + +@Entity() +@Tree("closure-table") +export class Category { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @TreeParent() + @JoinColumn({ + name: "parent_category_id" + }) + parentCategory: Category; + + @TreeChildren({ cascade: true }) + childCategories: Category[]; + + // @TreeLevelColumn() + // level: number; + +} diff --git a/test/github-issues/8443/materialized-path/entity/Category.ts b/test/github-issues/8443/materialized-path/entity/Category.ts new file mode 100644 index 0000000000..41e0401bdb --- /dev/null +++ b/test/github-issues/8443/materialized-path/entity/Category.ts @@ -0,0 +1,31 @@ +import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../src/decorator/columns/Column"; +import {TreeParent} from "../../../../../src/decorator/tree/TreeParent"; +import {TreeChildren} from "../../../../../src/decorator/tree/TreeChildren"; +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {Tree} from "../../../../../src/decorator/tree/Tree"; +import { JoinColumn } from "../../../../../src"; + +@Entity() +@Tree("materialized-path") +export class Category { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @TreeParent() + @JoinColumn({ + name: "parent_category_id" + }) + parentCategory: Category; + + @TreeChildren({ cascade: true }) + childCategories: Category[]; + + // @TreeLevelColumn() + // level: number; + +} diff --git a/test/github-issues/8443/materialized-path/materialized-path.ts b/test/github-issues/8443/materialized-path/materialized-path.ts new file mode 100644 index 0000000000..ae81d1e6e0 --- /dev/null +++ b/test/github-issues/8443/materialized-path/materialized-path.ts @@ -0,0 +1,573 @@ +import "reflect-metadata"; +import {Category} from "./entity/Category"; +import {Connection} from "../../../../src/connection/Connection"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; + +describe("github issues > #8443 QueryFailedError when tree entity with JoinColumn > materialized-path", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [Category], + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("attach should work properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + a11.parentCategory = a1; + await categoryRepository.save(a11); + + const a111 = new Category(); + a111.name = "a111"; + a111.parentCategory = a11; + await categoryRepository.save(a111); + + const a12 = new Category(); + a12.name = "a12"; + a12.parentCategory = a1; + await categoryRepository.save(a12); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(4); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a111"}); + a1Children.should.deep.include({id: 4, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(3); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(3); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly and everything must be saved in cascades", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + const a1ChildrenNames = a1Children.map(child => child.name); + a1ChildrenNames.length.should.be.equal(5); + a1ChildrenNames.should.deep.include("a1"); + a1ChildrenNames.should.deep.include("a11"); + a1ChildrenNames.should.deep.include("a12"); + a1ChildrenNames.should.deep.include("a111"); + a1ChildrenNames.should.deep.include("a112"); + }))); + + it("findTrees() tests > findTrees should load all category roots and attached children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findTrees() testsfindTrees should load multiple category roots if they exist", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const b1 = new Category(); + b1.name = "b1"; + + const b11 = new Category(); + b11.name = "b11"; + + const b12 = new Category(); + b12.name = "b12"; + + b1.childCategories = [b11, b12]; + await categoryRepository.save(b1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }, { + id: b1.id, + name: "b1", + childCategories: [ + { + id: b11.id, + name: "b11", + childCategories: [] + }, + { + id: b12.id, + name: "b12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findTrees() testsfindTrees should filter by depth if optionally provided", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + + const categoriesTreeWithEmptyOptions = await categoryRepository.findTrees({}); + categoriesTreeWithEmptyOptions.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + + const categoriesTreeWithDepthZero = await categoryRepository.findTrees({depth: 0}); + categoriesTreeWithDepthZero.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [] + } + ]); + + const categoriesTreeWithDepthOne = await categoryRepository.findTrees({depth: 1}); + categoriesTreeWithDepthOne.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findDescendantsTree() tests > findDescendantsTree should load all category descendents and nested children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findDescendantsTree(a1); + categoriesTree.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + }))); + + it("findDescendantsTree should filter by depth if optionally provided", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findDescendantsTree(a1); + categoriesTree.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + + const categoriesTreeWithEmptyOptions = await categoryRepository.findDescendantsTree(a1, {}); + categoriesTreeWithEmptyOptions.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + + const categoriesTreeWithDepthZero = await categoryRepository.findDescendantsTree(a1, {depth: 0}); + categoriesTreeWithDepthZero.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [] + }); + + const categoriesTreeWithDepthOne = await categoryRepository.findDescendantsTree(a1, {depth: 1}); + categoriesTreeWithDepthOne.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + }))); +}); diff --git a/test/github-issues/8443/nested-set/entity/Category.ts b/test/github-issues/8443/nested-set/entity/Category.ts new file mode 100644 index 0000000000..b4b5799b85 --- /dev/null +++ b/test/github-issues/8443/nested-set/entity/Category.ts @@ -0,0 +1,31 @@ +import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../src/decorator/columns/Column"; +import {TreeParent} from "../../../../../src/decorator/tree/TreeParent"; +import {TreeChildren} from "../../../../../src/decorator/tree/TreeChildren"; +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {Tree} from "../../../../../src/decorator/tree/Tree"; +import { JoinColumn } from '../../../../../src' + +@Entity() +@Tree("nested-set") +export class Category { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @TreeParent() + @JoinColumn({ + name: "parent_category_id" + }) + parentCategory: Category; + + @TreeChildren({ cascade: true }) + childCategories: Category[]; + + // @TreeLevelColumn() + // level: number; + +} diff --git a/test/github-issues/8443/nested-set/nested-set.ts b/test/github-issues/8443/nested-set/nested-set.ts new file mode 100644 index 0000000000..928273ef42 --- /dev/null +++ b/test/github-issues/8443/nested-set/nested-set.ts @@ -0,0 +1,509 @@ +import "../../../utils/test-setup"; +import "reflect-metadata"; +import {Category} from "./entity/Category"; +import {Connection} from "../../../../src/connection/Connection"; +import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; +import {expect} from "chai"; + +describe("github issues > #8443 QueryFailedError when tree entity with JoinColumn > nested-set", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [Category] + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("attach should work properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + a11.parentCategory = a1; + await categoryRepository.save(a11); + + const a111 = new Category(); + a111.name = "a111"; + a111.parentCategory = a11; + await categoryRepository.save(a111); + + const a12 = new Category(); + a12.name = "a12"; + a12.parentCategory = a1; + await categoryRepository.save(a12); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(4); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a111"}); + a1Children.should.deep.include({id: 4, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(3); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + await categoryRepository.save(a1); + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + a1.childCategories = [a11, a12]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + a1Children.length.should.be.equal(3); + a1Children.should.deep.include({id: 1, name: "a1"}); + a1Children.should.deep.include({id: 2, name: "a11"}); + a1Children.should.deep.include({id: 3, name: "a12"}); + }))); + + it("categories should be attached via children and saved properly and everything must be saved in cascades", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const rootCategories = await categoryRepository.findRoots(); + rootCategories.should.be.eql([{ + id: 1, + name: "a1" + }]); + + const a11Parent = await categoryRepository.findAncestors(a11); + a11Parent.length.should.be.equal(2); + a11Parent.should.deep.include({id: 1, name: "a1"}); + a11Parent.should.deep.include({id: 2, name: "a11"}); + + const a1Children = await categoryRepository.findDescendants(a1); + const a1ChildrenNames = a1Children.map(child => child.name); + a1ChildrenNames.length.should.be.equal(5); + a1ChildrenNames.should.deep.include("a1"); + a1ChildrenNames.should.deep.include("a11"); + a1ChildrenNames.should.deep.include("a12"); + a1ChildrenNames.should.deep.include("a111"); + a1ChildrenNames.should.deep.include("a112"); + }))); + + it("findTrees() tests > findTrees should load all category roots and attached children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findTrees() tests > findTrees should filter by depth if optionally provided", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findTrees(); + categoriesTree.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + + const categoriesTreeWithEmptyOptions = await categoryRepository.findTrees({}); + categoriesTreeWithEmptyOptions.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + + const categoriesTreeWithDepthZero = await categoryRepository.findTrees({depth: 0}); + categoriesTreeWithDepthZero.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [] + } + ]); + + const categoriesTreeWithDepthOne = await categoryRepository.findTrees({depth: 1}); + categoriesTreeWithDepthOne.should.be.eql([ + { + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + } + ]); + }))); + + it("findTrees() tests > findTrees should present a meaningful error message when used with multiple roots + nested sets", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + await categoryRepository.save(a1); + + const b1 = new Category(); + b1.name = "b1"; + + await expect(categoryRepository.save(b1)).to.be.rejectedWith("Nested sets do not support multiple root entities."); + }))); + + it("findDescendantsTree() tests > findDescendantsTree should load all category descendents and nested children", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findDescendantsTree(a1); + categoriesTree.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + }))); + + it("findDescendantsTree() tests > findDescendantsTree should filter by depth if optionally provided", () => Promise.all(connections.map(async connection => { + const categoryRepository = connection.getTreeRepository(Category); + + const a1 = new Category(); + a1.name = "a1"; + + const a11 = new Category(); + a11.name = "a11"; + + const a12 = new Category(); + a12.name = "a12"; + + const a111 = new Category(); + a111.name = "a111"; + + const a112 = new Category(); + a112.name = "a112"; + + a1.childCategories = [a11, a12]; + a11.childCategories = [a111, a112]; + await categoryRepository.save(a1); + + const categoriesTree = await categoryRepository.findDescendantsTree(a1); + console.log(categoriesTree); + categoriesTree.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + + const categoriesTreeWithEmptyOptions = await categoryRepository.findDescendantsTree(a1, {}); + categoriesTreeWithEmptyOptions.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [ + { + id: a111.id, + name: "a111", + childCategories: [] + }, + { + id: a112.id, + name: "a112", + childCategories: [] + } + ] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + + const categoriesTreeWithDepthZero = await categoryRepository.findDescendantsTree(a1, {depth: 0}); + categoriesTreeWithDepthZero.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [] + }); + + const categoriesTreeWithDepthOne = await categoryRepository.findDescendantsTree(a1, {depth: 1}); + categoriesTreeWithDepthOne.should.be.eql({ + id: a1.id, + name: "a1", + childCategories: [ + { + id: a11.id, + name: "a11", + childCategories: [] + }, + { + id: a12.id, + name: "a12", + childCategories: [] + } + ] + }); + }))); +});