From dd66f313f317027b9695e658aa6de236ff53df74 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Wed, 23 Dec 2020 11:36:22 +0100 Subject: [PATCH 1/2] Supports "createMany" model method --- src/factory.ts | 43 ++++++++++++++++++++++- src/glossary.ts | 23 +++++++++++++ src/utils/getRandomNumber.ts | 8 +++++ test/model/createMany.test.ts | 64 +++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/utils/getRandomNumber.ts create mode 100644 test/model/createMany.test.ts diff --git a/src/factory.ts b/src/factory.ts index c85d9854..0852f354 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -6,6 +6,7 @@ import { EntityInstance, ModelDictionary, PrimaryKeyType, + RelationKind, } from './glossary' import { first } from './utils/first' import { executeQuery } from './query/executeQuery' @@ -14,6 +15,7 @@ import { createModel } from './model/createModel' import { invariant } from './utils/invariant' import { updateEntity } from './model/updateEntity' import { OperationError, OperationErrorType } from './errors/OperationError' +import { getRandomNumber } from './utils/getRandomNumber' /** * Create a database with the given models. @@ -31,6 +33,7 @@ export function factory( modelName, props, db, + acc, ) return acc }, {}) @@ -39,7 +42,12 @@ export function factory( function createModelApi< Dictionary extends ModelDictionary, ModelName extends string ->(modelName: ModelName, declaration: ModelDeclaration, db: Database) { +>( + modelName: ModelName, + declaration: ModelDeclaration, + db: Database, + factory: FactoryAPI, +) { let modelPrimaryKey: PrimaryKeyType const api: ModelAPI = { @@ -70,6 +78,39 @@ function createModelApi< return entity }, + createMany(count, options) { + const resolvedCount = count || getRandomNumber(5, 10) + const getRelations = options?.relations + ? () => + Object.entries(options.relations).reduce((acc, [key, count]) => { + const propDeclaration = declaration[key] + + if ('__type' in propDeclaration) { + const isManyOfRelationType = + propDeclaration.__type === RelationKind.ManyOf + const resolvedCount = isManyOfRelationType + ? (count as number) + : 1 + const relationalModel = factory[propDeclaration.modelName] + const records = isManyOfRelationType + ? relationalModel.createMany(resolvedCount) + : relationalModel.create() + + acc[key] = records + } + + return acc + }, {}) + : null + + const createdEntities = new Array(resolvedCount) + .fill(null) + .map>(() => { + return api.create(getRelations?.()) + }) + + return createdEntities + }, count(query) { if (!query) { return db[modelName].size diff --git a/src/glossary.ts b/src/glossary.ts index 8acd2ecf..b286ee98 100644 --- a/src/glossary.ts +++ b/src/glossary.ts @@ -105,6 +105,14 @@ export interface ModelAPI< create( initialValues?: Partial>, ): EntityInstance + /** + * Create multiple entities for the model. + * @param count Amount of models to create. + */ + createMany( + count?: number, + options?: CreateManyOptions, + ): EntityInstance[] /** * Return the total number of entities. */ @@ -182,4 +190,19 @@ export type Value< : ReturnType } +type SubType = Pick< + P, + { + [K in keyof P]: P[K] extends Condition ? K : never + }[keyof P] +> + +interface CreateManyOptions> { + relations: { + [K in keyof SubType | ManyOf>]: T[K] extends ManyOf + ? number + : boolean + } +} + export type Database = Record>> diff --git a/src/utils/getRandomNumber.ts b/src/utils/getRandomNumber.ts new file mode 100644 index 00000000..bb8dbcda --- /dev/null +++ b/src/utils/getRandomNumber.ts @@ -0,0 +1,8 @@ +/** + * Return a random number in range. + * @param min The lowest number to return. + * @param max The highest number to return. + */ +export function getRandomNumber(min: number = 5, max: number = 50) { + return Math.floor(Math.random() * (max - min + 1) + min) +} diff --git a/test/model/createMany.test.ts b/test/model/createMany.test.ts new file mode 100644 index 00000000..17be9763 --- /dev/null +++ b/test/model/createMany.test.ts @@ -0,0 +1,64 @@ +import { name, random } from 'faker' +import { factory, manyOf, oneOf, primaryKey } from '../../src' + +test('creates multiple entities with a fixed count', () => { + const db = factory({ + user: { + id: primaryKey(random.uuid), + firstName: name.firstName, + }, + }) + + db.user.createMany(5) + + const allUsers = db.user.getAll() + expect(allUsers).toHaveLength(5) +}) + +test('allows to specify count of relational entities to create', () => { + const db = factory({ + user: { + id: primaryKey(random.uuid), + country: oneOf('country'), + posts: manyOf('post'), + }, + post: { + id: primaryKey(random.uuid), + title: random.words, + }, + country: { + id: primaryKey(random.uuid), + name: random.word, + }, + }) + + db.user.createMany(3, { + relations: { + /** + * @todo How to specify that multiple entities should reuse the same relational model? + * I.e. multiple users from the same country. + * + * @todo `Boolean` value of `oneOf` relation has no sense: + * an entity cannot be created without all relational models specified. + */ + country: false, + posts: 2, + }, + }) + + const allUsers = db.user.getAll() + const allPosts = db.post.getAll() + const allCountries = db.country.getAll() + + expect(allUsers).toHaveLength(3) + // Each of 3 random "user" should have "2" posts created. + expect(allPosts).toHaveLength(6) + // Each of 3 random "user" should have its own "country". + expect(allCountries).toHaveLength(3) + + allUsers.forEach((user, index) => { + expect(user.posts).toHaveLength(2) + expect(user.country).toHaveProperty('id', allCountries[index].id) + expect(user.country).toHaveProperty('name', allCountries[index].name) + }) +}) From 575c1eb12d61ca5a0d22d9ad2040cc90d60acb7c Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Mon, 28 Dec 2020 09:26:24 +0100 Subject: [PATCH 2/2] createMany: Remove unnecessary "resolvedCount" computation --- src/factory.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/factory.ts b/src/factory.ts index 0852f354..2aaef234 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -88,12 +88,9 @@ function createModelApi< if ('__type' in propDeclaration) { const isManyOfRelationType = propDeclaration.__type === RelationKind.ManyOf - const resolvedCount = isManyOfRelationType - ? (count as number) - : 1 const relationalModel = factory[propDeclaration.modelName] const records = isManyOfRelationType - ? relationalModel.createMany(resolvedCount) + ? relationalModel.createMany(count as number) : relationalModel.create() acc[key] = records