From 9dd590c45981f80099ffcebc8768ab5b96bf51f4 Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Tue, 22 Feb 2022 23:38:13 +0800 Subject: [PATCH] fix Collection rest api (#205) * test: collections rest api * test: add more test cases * fix: test case * fix: Error: src/schema-component/antd/array-table/TableRecordActionDesigner.tsx(3,38): error TS2307: Cannot find module '@nocobase/client' or its corresponding type declarations. * fix: belongsToMany create with empty value * fix: relation query test * test: appends + sort * fix: belongsToMany find error * fix: add cleanDb to fix database test * fix: mysql test Co-authored-by: chenos --- .../belongs-to-many-repository.test.ts | 42 ++ .../belongs-to-many-repository.ts | 3 +- .../multiple-relation-repository.ts | 15 +- packages/database/src/update-guard.ts | 10 +- packages/plugin-acl/src/__tests__/prepare.ts | 2 + .../__tests__/http-api/collections.test.ts | 462 ++++++++++++++++++ .../src/__tests__/index.ts | 1 + .../src/__tests__/server-hook-impl.test.ts | 1 + .../src/__tests__/server-hook.test.ts | 1 + 9 files changed, 524 insertions(+), 13 deletions(-) create mode 100644 packages/plugin-collection-manager/src/__tests__/http-api/collections.test.ts diff --git a/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts b/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts index bc1157ca2..164ddee80 100644 --- a/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +++ b/packages/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts @@ -7,8 +7,11 @@ describe('belongs to many with target key', function () { let db: Database; let Tag: Collection; let Post: Collection; + let Color: Collection; + beforeEach(async () => { db = mockDatabase(); + Post = db.collection({ name: 'posts', filterTargetKey: 'title', @@ -114,6 +117,7 @@ describe('belongs to many', () => { let Post; let Tag; let PostTag; + let Color; beforeEach(async () => { db = mockDatabase(); @@ -154,6 +158,15 @@ describe('belongs to many', () => { { type: 'belongsToMany', name: 'posts', through: 'posts_tags' }, { type: 'string', name: 'name' }, { type: 'string', name: 'status' }, + { type: 'hasMany', name: 'colors' }, + ], + }); + + Color = db.collection({ + name: 'colors', + fields: [ + { type: 'string', name: 'name' }, + { type: 'belongsTo', name: 'tag' }, ], }); @@ -269,6 +282,35 @@ describe('belongs to many', () => { expect(t1).toBeNull(); }); + test('find with sort & appends', async () => { + const p1 = await Post.repository.create({ + values: { + title: 'p1', + tags: [ + { + name: 't1', + colors: [ + { + name: 'red', + }, + ], + }, + { name: 't2', colors: [{ name: 'green' }] }, + ], + }, + }); + + const PostTagsRepository = new BelongsToManyRepository(Post, 'tags', p1.id); + const tags = await PostTagsRepository.find({ + appends: ['colors'], + sort: ['name'], + limit: 20, + offset: 0, + }); + + console.log(tags); + }); + test('update raw attribute', async () => { const otherTag = await Tag.repository.create({ values: { name: 'other_tag' }, diff --git a/packages/database/src/relation-repository/belongs-to-many-repository.ts b/packages/database/src/relation-repository/belongs-to-many-repository.ts index 7581752ee..42c067eb4 100644 --- a/packages/database/src/relation-repository/belongs-to-many-repository.ts +++ b/packages/database/src/relation-repository/belongs-to-many-repository.ts @@ -33,7 +33,8 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen const transaction = await this.getTransaction(options); const createAccessor = this.accessors().create; - const values = options.values; + + const values = options.values || {}; const sourceModel = await this.getSourceModel(transaction); diff --git a/packages/database/src/relation-repository/multiple-relation-repository.ts b/packages/database/src/relation-repository/multiple-relation-repository.ts index b17827931..7493ee779 100644 --- a/packages/database/src/relation-repository/multiple-relation-repository.ts +++ b/packages/database/src/relation-repository/multiple-relation-repository.ts @@ -32,11 +32,14 @@ export abstract class MultipleRelationRepository extends RelationRepository { async find(options?: FindOptions): Promise { const transaction = await this.getTransaction(options); - const findOptions = this.extendFindOptions( - this.buildQueryOptions({ - ...options, - }), - ); + const findOptions = { + ...this.extendFindOptions( + this.buildQueryOptions({ + ...options, + }), + ), + subQuery: false, + }; const getAccessor = this.accessors().get; const sourceModel = await this.getSourceModel(transaction); @@ -44,7 +47,7 @@ export abstract class MultipleRelationRepository extends RelationRepository { if (findOptions.include && findOptions.include.length > 0) { const ids = ( await sourceModel[getAccessor]({ - ...omit(findOptions, 'order'), + ...findOptions, includeIgnoreAttributes: false, attributes: [this.targetKey()], group: `${this.targetModel.name}.${this.targetKey()}`, diff --git a/packages/database/src/update-guard.ts b/packages/database/src/update-guard.ts index a90d8883a..954cc06f3 100644 --- a/packages/database/src/update-guard.ts +++ b/packages/database/src/update-guard.ts @@ -1,10 +1,8 @@ -import { flatten } from 'flat'; -import lodash, { keys } from 'lodash'; - -import { Collection } from './collection'; -import { BelongsTo, HasOne, Model, ModelCtor } from 'sequelize'; +import lodash from 'lodash'; +import { Model, ModelCtor } from 'sequelize'; import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository'; + type UpdateValueItem = string | number | UpdateValues; type UpdateValues = { @@ -130,7 +128,7 @@ export class UpdateGuard { return values; } - let valuesKeys = Object.keys(values); + let valuesKeys = Object.keys(values || {}); // handle whitelist if (this.whiteList) { diff --git a/packages/plugin-acl/src/__tests__/prepare.ts b/packages/plugin-acl/src/__tests__/prepare.ts index 1822dbdaa..1ea1ca258 100644 --- a/packages/plugin-acl/src/__tests__/prepare.ts +++ b/packages/plugin-acl/src/__tests__/prepare.ts @@ -19,6 +19,8 @@ export async function prepareApp() { registerActions: true, }); + await app.cleanDb(); + app.plugin(PluginUiSchema); app.plugin(PluginCollectionManager); diff --git a/packages/plugin-collection-manager/src/__tests__/http-api/collections.test.ts b/packages/plugin-collection-manager/src/__tests__/http-api/collections.test.ts new file mode 100644 index 000000000..9bc87cdfd --- /dev/null +++ b/packages/plugin-collection-manager/src/__tests__/http-api/collections.test.ts @@ -0,0 +1,462 @@ +import { MockServer } from '@nocobase/test'; +import { createApp } from '..'; + +describe('collections repository', () => { + let app: MockServer; + + beforeEach(async () => { + app = await createApp(); + await app.install({ clean: true }); + await app.start(); + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'tags', + fields: [ + { + name: 'title', + type: 'string', + }, + ], + }, + }); + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'foos', + fields: [ + { + name: 'title', + type: 'string', + }, + ], + }, + }); + await app + .agent() + .resource('collections.fields', 'tags') + .create({ + values: { + name: 'foos', + target: 'foos', + type: 'belongsToMany', + }, + }); + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'comments', + fields: [ + { + name: 'title', + type: 'string', + }, + ], + }, + }); + await app + .agent() + .resource('collections') + .create({ + values: { + name: 'posts', + fields: [ + { + name: 'title', + type: 'string', + }, + { + name: 'comments', + target: 'comments', + type: 'hasMany', + }, + { + name: 'tags', + target: 'tags', + type: 'belongsToMany', + }, + ], + }, + }); + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('case 1', async () => { + const response1 = await app.agent().resource('posts').create(); + const postId = response1.body.data.id; + const response2 = await app.agent().resource('posts.comments', postId).create(); + expect(response2.statusCode).toBe(200); + }); + + it('case 2', async () => { + const response = await app.agent().resource('posts').create(); + const postId = response.body.data.id; + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 1', + }, + }); + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 2', + }, + }); + const response2 = await app + .agent() + .resource('posts') + .list({ + filter: { + 'comments.title': 'comment 1', + }, + }); + expect(response2.body.data[0].id).toBe(1); + }); + + it('case 3', async () => { + const response = await app.agent().resource('posts').create(); + const postId = response.body.data.id; + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 1', + }, + }); + const response2 = await app + .agent() + .resource('posts') + .list({ + filter: { + 'comments.id': 3, + }, + }); + expect(response2.body.data.length).toBe(0); + }); + + it('case 4', async () => { + const response = await app.agent().resource('posts').create(); + const postId = response.body.data.id; + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 1', + }, + }); + const response2 = await app + .agent() + .resource('posts') + .list({ + filter: { + $and: [ + { + 'comments.title': 'comment 1', + }, + ], + }, + }); + expect(response2.body.data[0].id).toBe(1); + }); + + it('case 5', async () => { + const response = await app.agent().resource('posts').create(); + const postId = response.body.data.id; + await app.agent().resource('posts.tags', postId).create({ + values: {}, + }); + + expect(await app.db.getRepository('tags').count()).toEqual(1); + }); + + it('case 6', async () => { + const response = await app + .agent() + .resource('posts') + .create({ + values: { + tags: [ + {}, + { + title: 'Tag1', + }, + ], + }, + }); + const postId = response.body.data.id; + const response1 = await app.agent().resource('posts.tags', postId).list(); + expect(response1.body.data.length).toBe(2); + }); + + it('case 7', async () => { + const response = await app + .agent() + .resource('posts') + .create({ + values: { + tags: [ + {}, + { + title: 'Tag1', + }, + ], + }, + }); + const postId = response.body.data.id; + const response1 = await app + .agent() + .resource('posts.tags', postId) + .list({ + filter: { + title: 'Tag1', + }, + }); + expect(response1.body.data.length).toBe(1); + }); + + it('case 8', async () => { + const response = await app + .agent() + .resource('posts') + .create({ + values: { + tags: [ + {}, + { + title: 'Tag1', + }, + { + title: 'Tag2', + }, + ], + }, + }); + const postId = response.body.data.id; + const response1 = await app + .agent() + .resource('posts.tags', postId) + .list({ + filter: { + $or: [{ title: 'Tag1' }, { title: 'Tag2' }], + }, + }); + expect(response1.body.data.length).toBe(2); + }); + + it('case 9', async () => { + const response = await app + .agent() + .resource('posts') + .create({ + values: { + tags: [ + {}, + { + title: 'Tag1', + }, + { + title: 'Tag2', + }, + ], + }, + }); + const postId = response.body.data.id; + const response1 = await app + .agent() + .resource('posts.tags', postId) + .list({ + filter: { + $or: [{ title: 'Tag1' }, { title: 'Tag2' }], + }, + }); + expect(response1.body.data.length).toBe(2); + }); + + it('case 10', async () => { + const response = await app + .agent() + .resource('posts') + .create({ + values: { + tags: [ + {}, + { + title: 'Tag1', + }, + { + title: 'Tag2', + }, + ], + }, + }); + const postId = response.body.data.id; + const response1 = await app + .agent() + .resource('posts.tags', postId) + .list({ + appends: ['foos'], + page: 1, + pageSize: 20, + sort: ['-createdAt', '-id'], + }); + + expect(response1.body.data[0]['id']).toEqual(3); + }); + + it('case 11', async () => { + const response = await app.agent().resource('posts').create(); + const postId = response.body.data.id; + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 1', + }, + }); + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 2', + }, + }); + const response2 = await app.agent().resource('posts').create(); + const postId2 = response2.body.data.id; + await app + .agent() + .resource('posts.comments', postId2) + .create({ + values: { + title: 'comment 2', + }, + }); + await app + .agent() + .resource('posts.comments', postId2) + .create({ + values: { + title: 'comment 2', + }, + }); + const response3 = await app + .agent() + .resource('posts') + .list({ + filter: { + $or: [ + { + 'comments.title': 'comment 1', + }, + { + 'comments.title': 'comment 2', + }, + ], + }, + }); + expect(response3.body.data.length).toBe(2); + }); + + it('case 12', async () => { + const response = await app.agent().resource('posts').create(); + const postId = response.body.data.id; + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 1', + }, + }); + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 2', + }, + }); + await app + .agent() + .resource('posts.comments', postId) + .create({ + values: { + title: 'comment 3', + }, + }); + const response2 = await app + .agent() + .resource('posts.comments', postId) + .list({ + filter: { + $or: [ + { + title: 'comment 1', + }, + { + title: 'comment 2', + }, + ], + }, + }); + expect(response2.body.data.length).toBe(2); + }); + + it('case 13', async () => { + const tagRepository = app.db.getRepository('tags'); + const tag1 = await tagRepository.create({ values: { title: 'tag1' } }); + const tag2 = await tagRepository.create({ values: { title: 'tag2' } }); + const tag3 = await tagRepository.create({ values: { title: 'tag3' } }); + await app + .agent() + .resource('posts') + .create({ + values: { + tags: [tag1.get('id'), tag3.get('id')], + }, + }); + await app + .agent() + .resource('posts') + .create({ + values: { + tags: [tag2.get('id')], + }, + }); + await app + .agent() + .resource('posts') + .create({ + values: { + tags: [tag2.get('id'), tag3.get('id')], + }, + }); + + const response1 = await app + .agent() + .resource('posts') + .list({ + filter: { + $or: [{ 'tags.title': 'tag1' }, { 'tags.title': 'tag3' }], + }, + }); + expect(response1.body.data.length).toBe(2); + }); +}); diff --git a/packages/plugin-collection-manager/src/__tests__/index.ts b/packages/plugin-collection-manager/src/__tests__/index.ts index 20bbc7e05..961430805 100644 --- a/packages/plugin-collection-manager/src/__tests__/index.ts +++ b/packages/plugin-collection-manager/src/__tests__/index.ts @@ -3,6 +3,7 @@ import CollectionManagerPlugin from '..'; export async function createApp() { const app = mockServer(); + await app.cleanDb(); app.plugin(CollectionManagerPlugin); await app.load(); return app; diff --git a/packages/plugin-ui-schema-storage/src/__tests__/server-hook-impl.test.ts b/packages/plugin-ui-schema-storage/src/__tests__/server-hook-impl.test.ts index 0e719119a..f9d81a2a8 100644 --- a/packages/plugin-ui-schema-storage/src/__tests__/server-hook-impl.test.ts +++ b/packages/plugin-ui-schema-storage/src/__tests__/server-hook-impl.test.ts @@ -19,6 +19,7 @@ describe('server hooks', () => { registerActions: true, }); + await app.cleanDb(); db = app.db; app.plugin(UiSchemaStoragePlugin); diff --git a/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts b/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts index 46f979ce8..f6523c5b0 100644 --- a/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts +++ b/packages/plugin-ui-schema-storage/src/__tests__/server-hook.test.ts @@ -58,6 +58,7 @@ describe('server hooks', () => { registerActions: true, }); + await app.cleanDb(); db = app.db; app.plugin(UiSchemaStoragePlugin);