refactor: mockDatabase

This commit is contained in:
chenos 2022-02-15 00:20:25 +08:00
parent 0d0acba357
commit c5f639338f
12 changed files with 67 additions and 204 deletions

View File

@ -1,6 +1,6 @@
import { Collection } from '../collection';
import { Database } from '../database';
import { generatePrefixByPath, mockDatabase } from './index';
import { mockDatabase } from './index';
test('collection disable authGenId', async () => {
const db = mockDatabase();
@ -91,7 +91,7 @@ describe('collection sync', () => {
]);
await collection.sync();
const tableFields = await (<any>collection.model).queryInterface.describeTable(`${generatePrefixByPath()}_users`);
const tableFields = await (<any>collection.model).queryInterface.describeTable(`${db.getTablePrefix()}users`);
expect(tableFields).toHaveProperty('firstName');
expect(tableFields).toHaveProperty('lastName');
@ -114,7 +114,7 @@ describe('collection sync', () => {
const model = collection.model;
const tableFields = await (<any>model).queryInterface.describeTable(`${generatePrefixByPath()}_posts`);
const tableFields = await (<any>model).queryInterface.describeTable(`${db.getTablePrefix()}posts`);
expect(tableFields['user_id']).toBeUndefined();
});
@ -143,7 +143,7 @@ describe('collection sync', () => {
const model = collection.model;
await collection.sync();
const tableFields = await (<any>model).queryInterface.describeTable(`${generatePrefixByPath()}_posts_tags`);
const tableFields = await (<any>model).queryInterface.describeTable(`${db.getTablePrefix()}posts_tags`);
expect(tableFields['postId']).toBeDefined();
expect(tableFields['tagId']).toBeDefined();
});
@ -184,13 +184,13 @@ test.skip('update collection options', async () => {
},
);
expect(collection.model.getTableName()).toEqual(`${generatePrefixByPath()}_posts`);
expect(collection.model.getTableName()).toEqual(`${db.getTablePrefix()}posts`);
collection.updateOptions({
name: 'articles',
});
expect(collection.model.getTableName()).toEqual(`${generatePrefixByPath()}_articles`);
expect(collection.model.getTableName()).toEqual(`${db.getTablePrefix()}articles`);
});
test('collection with association', async () => {

View File

@ -1,7 +1,7 @@
import { mockDatabase } from './index';
import FilterParser from '../filter-parser';
import { Op } from 'sequelize';
import { Database } from '../database';
import FilterParser from '../filter-parser';
import { mockDatabase } from './index';
test('filter item by string', async () => {
const database = mockDatabase();

View File

@ -1,94 +1 @@
import { uid } from '@nocobase/utils';
import merge from 'deepmerge';
import { Sequelize } from 'sequelize';
import { Database, DatabaseOptions } from '../database';
export function generatePrefixByPath() {
const { id } = require.main;
const key = id
.replace(`${process.env.PWD}/packages`, '')
.replace(/src\/__tests__/g, '')
.replace('.test.ts', '')
.replace(/[^\w]/g, '_')
.replace(/_+/g, '_');
return key;
}
export function getConfig(config: any = {}, options?: any): DatabaseOptions {
if (process.env.DB_DIALECT === 'sqlite') {
const defaults = {
dialect: process.env.DB_DIALECT as any,
storage: ':memory:',
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
// sync: {
// force: true,
// },
hooks: {
beforeDefine(model, options) {
options.tableName = `${generatePrefixByPath()}_${
options.tableName || options.modelName || options.name.plural
}`;
},
},
};
return merge(defaults, config, options);
}
const database = `mock_${uid()}`;
let dbExists = false;
const defaults = {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT as any,
dialect: process.env.DB_DIALECT as any,
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
dialectOptions: {
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci',
},
hooks: {
beforeDefine(model, options) {
options.tableName = `${generatePrefixByPath()}_${
options.tableName || options.modelName || options.name.plural
}`;
},
async beforeSync({ sequelize }: any) {
if (config.database) {
return;
}
if (dbExists) {
return;
}
return;
const db = new Sequelize({
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT as any,
dialect: process.env.DB_DIALECT as any,
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
dialectOptions: {
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci',
},
});
await db.query(`CREATE DATABASE "${database}";`);
await db.close();
sequelize.options.database = database;
sequelize.config.database = database;
const ConnectionManager = sequelize.dialect.connectionManager.constructor;
const connectionManager = new ConnectionManager(sequelize.dialect, sequelize);
sequelize.dialect.connectionManager = connectionManager;
sequelize.connectionManager = connectionManager;
dbExists = true;
},
},
};
return merge(defaults, config, options);
}
export function mockDatabase(options?: DatabaseOptions): Database {
return new Database(getConfig(options));
}
export { mockDatabase } from '../mock-database';

View File

@ -31,7 +31,11 @@ interface MapOf<T> {
[key: string]: T;
}
export type DatabaseOptions = Options | Sequelize;
export interface IDatabaseOptions extends Options {
tablePrefix?: string;
}
export type DatabaseOptions = IDatabaseOptions | Sequelize;
interface RegisterOperatorsContext {
db?: Database;
@ -48,6 +52,7 @@ type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
export class Database extends EventEmitter implements AsyncEmitter {
sequelize: Sequelize;
fieldTypes = new Map();
options: IDatabaseOptions;
models = new Map<string, ModelCtor<any>>();
repositories = new Map<string, RepositoryType>();
operators = new Map();
@ -66,6 +71,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
this.sequelize = options;
} else {
this.sequelize = new Sequelize(options);
this.options = options;
}
this.collections = new Map();
@ -115,6 +121,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
return collection;
}
getTablePrefix() {
return this.options.tablePrefix || '';
}
/**
* get exists collection by its name
* @param name

View File

@ -4,6 +4,7 @@ export * from './database';
export { Database as default } from './database';
export * from './fields';
export * from './magic-attribute-model';
export * from './mock-database';
export * from './relation-repository/belongs-to-many-repository';
export * from './relation-repository/belongs-to-repository';
export * from './relation-repository/hasmany-repository';
@ -12,4 +13,3 @@ export * from './relation-repository/single-relation-repository';
export * from './repository';
export * from './update-associations';

View File

@ -0,0 +1,37 @@
import { merge, uid } from '@nocobase/utils';
import { resolve } from 'path';
import { Database, IDatabaseOptions } from './database';
export class MockDatabase extends Database {
constructor(options: IDatabaseOptions) {
super({
storage: ':memory:',
tablePrefix: `mock_${uid(6)}_`,
...options,
});
this.sequelize.beforeDefine((model, opts) => {
opts.tableName = `${this.getTablePrefix()}${opts.tableName || opts.modelName || opts.name.plural}`;
});
}
}
export function getConfigByEnv() {
return {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_DIALECT,
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
storage: process.env.DB_STORAGE ? resolve(process.cwd(), process.env.DB_STORAGE) : ':memory:',
dialectOptions: {
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci',
},
};
}
export function mockDatabase(options: IDatabaseOptions = {}): MockDatabase {
return new MockDatabase(merge(getConfigByEnv(), options));
}

View File

@ -1,6 +1,6 @@
import { ACL } from '@nocobase/acl';
import { registerActions } from '@nocobase/actions';
import Database, { CleanOptions, CollectionOptions, DatabaseOptions, SyncOptions } from '@nocobase/database';
import Database, { CleanOptions, CollectionOptions, IDatabaseOptions, SyncOptions } from '@nocobase/database';
import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
import { Command, CommandOptions } from 'commander';
@ -18,7 +18,7 @@ export interface ResourcerOptions {
}
export interface ApplicationOptions {
database?: DatabaseOptions;
database?: IDatabaseOptions | Database;
resourcer?: ResourcerOptions;
bodyParser?: any;
cors?: any;

View File

@ -1,14 +0,0 @@
import { mockDatabase } from '../';
describe('mock databasea', () => {
it('mock databasea', async () => {
const db = mockDatabase();
db.collection({
name: 'tests',
fields: [{ type: 'string', name: 'name' }],
});
expect(db.getCollection('tests').model.getTableName()).toBe('_test_mockDatabase_tests');
await db.sync();
await db.close();
});
});

View File

@ -1,34 +0,0 @@
import { mockServer, MockServer } from '../';
describe('mock server', () => {
let api: MockServer;
beforeEach(() => {
api = mockServer({
dataWrapping: false,
});
api.resourcer.registerActionHandlers({
list: async (ctx, next) => {
ctx.body = [1, 2];
await next();
},
});
api.resourcer.define({
name: 'test',
});
});
afterEach(async () => {
return api.destroy();
});
it('agent', async () => {
const response = await api.agent().get('/test');
expect(response.body).toEqual([1, 2]);
});
it('resource', async () => {
const response = await api.agent().resource('test').list();
expect(response.body).toEqual([1, 2]);
});
});

View File

@ -1,2 +1,3 @@
export * from './mockDatabase';
export { mockDatabase } from '@nocobase/database';
export * from './mockServer';

View File

@ -1,45 +0,0 @@
import merge from 'deepmerge';
import Database, { DatabaseOptions } from '@nocobase/database';
export function generatePrefixByPath() {
const { id } = require.main;
const key = id
.replace(`${process.env.PWD}/packages`, '')
.replace(/src\/__tests__/g, '')
.replace('.test.ts', '')
.replace(/[^\w]/g, '_')
.replace(/_+/g, '_');
return key;
}
export function getConfig(config = {}, options?: any): DatabaseOptions {
return merge(
{
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
storage: process.env.DB_STORAGE,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_DIALECT,
logging: process.env.DB_LOG_SQL === 'on',
sync: {
force: true,
alter: {
drop: true,
},
},
hooks: {
beforeDefine(model, options) {
options.tableName = `${generatePrefixByPath()}_${options.tableName || options.name.plural}`;
},
},
},
config || {},
options,
) as any;
}
export function mockDatabase(options?: DatabaseOptions): Database {
return new Database(getConfig(options));
}

View File

@ -1,7 +1,7 @@
import { mockDatabase } from '@nocobase/database';
import Application, { ApplicationOptions } from '@nocobase/server';
import qs from 'qs';
import supertest, { SuperAgentTest } from 'supertest';
import Application, { ApplicationOptions } from '@nocobase/server';
import { getConfig } from './mockDatabase';
interface ActionParams {
filterByTk?: any;
@ -123,10 +123,11 @@ export class MockServer extends Application {
}
}
export function mockServer(options?: ApplicationOptions) {
export function mockServer(options: ApplicationOptions = {}) {
const database = mockDatabase((<any>options?.database) || {});
return new MockServer({
...options,
database: getConfig(options?.database),
database,
});
}