From f5d795d95b88f8ba61b116af2675ed469df20415 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 29 Mar 2022 13:48:39 +0530 Subject: [PATCH] test: add tests for singlevalue CRUD - add utils --- backend/common.ts | 10 +- backend/database/core.ts | 78 ++++++++---- backend/database/tests/helpers.ts | 9 ++ backend/database/tests/testCore.spec.ts | 156 +++++++++++++++++++++++- tsconfig.json | 3 +- utils/index.ts | 29 +++++ vue.config.js | 1 + 7 files changed, 253 insertions(+), 33 deletions(-) create mode 100644 utils/index.ts diff --git a/backend/common.ts b/backend/common.ts index 79a885cb..9eb9cb60 100644 --- a/backend/common.ts +++ b/backend/common.ts @@ -23,12 +23,14 @@ export const sqliteTypeMap: Record = { Color: 'text', }; +export const SYSTEM = '__SYSTEM__'; export const validTypes = Object.keys(sqliteTypeMap); export function getDefaultMetaFieldValueMap() { + const now = new Date().toISOString(); return { - createdBy: '__SYSTEM__', - modifiedBy: '__SYSTEM__', - created: new Date().toISOString(), - modified: new Date().toISOString(), + createdBy: SYSTEM, + modifiedBy: SYSTEM, + created: now, + modified: now, }; } diff --git a/backend/database/core.ts b/backend/database/core.ts index 11077980..c371e07b 100644 --- a/backend/database/core.ts +++ b/backend/database/core.ts @@ -1,5 +1,5 @@ import { knex, Knex } from 'knex'; -import { getRandomString } from '../../frappe/utils'; +import { getRandomString, getValueMapFromList } from 'utils'; import { CannotCommitError, DatabaseError, @@ -15,7 +15,7 @@ import { SchemaMap, TargetField } from '../../schemas/types'; -import { sqliteTypeMap } from '../common'; +import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../common'; import { ColumnDiff, FieldValueMap, @@ -155,9 +155,7 @@ export default class DatabaseCore { */ let fieldValueMap: FieldValueMap = {}; if (isSingle) { - fieldValueMap = await this.#getSingle(schemaName); - fieldValueMap.name = schemaName; - return fieldValueMap; + return await this.#getSingle(schemaName); } if (fields !== '*' && typeof fields === 'string') { @@ -203,7 +201,7 @@ export default class DatabaseCore { start, limit, groupBy, - orderBy = 'creation', + orderBy = 'created', order = 'desc', }: GetAllOptions): Promise { const schema = this.schemaMap[schemaName]; @@ -225,9 +223,9 @@ export default class DatabaseCore { } async getSingleValues( - ...fieldnames: { fieldname: string; parent?: string }[] + ...fieldnames: ({ fieldname: string; parent?: string } | string)[] ): Promise<{ fieldname: string; parent: string; value: RawValue }[]> { - fieldnames = fieldnames.map((fieldname) => { + const fieldnameList = fieldnames.map((fieldname) => { if (typeof fieldname === 'string') { return { fieldname }; } @@ -235,9 +233,9 @@ export default class DatabaseCore { }); let builder = this.knex!('SingleValue'); - builder = builder.where(fieldnames[0]); + builder = builder.where(fieldnameList[0]); - fieldnames.slice(1).forEach(({ fieldname, parent }) => { + fieldnameList.slice(1).forEach(({ fieldname, parent }) => { if (typeof parent === 'undefined') { builder = builder.orWhere({ fieldname }); } else { @@ -279,6 +277,12 @@ export default class DatabaseCore { } async delete(schemaName: string, name: string) { + const schema = this.schemaMap[schemaName]; + if (schema.isSingle) { + await this.#deleteSingle(schemaName, name); + return; + } + await this.#deleteOne(schemaName, name); // delete children @@ -305,12 +309,6 @@ export default class DatabaseCore { // TODO: Implement this for sqlite } - async #deleteSingleValues(singleSchemaName: string) { - return await this.knex!('SingleValue') - .where('parent', singleSchemaName) - .delete(); - } - #getError(err: Error) { let errorType = DatabaseError; if (err.message.includes('SQLITE_ERROR: no such table')) { @@ -588,7 +586,13 @@ export default class DatabaseCore { } async #deleteOne(schemaName: string, name: string) { - return this.knex!(schemaName).where('name', name).delete(); + return await this.knex!(schemaName).where('name', name).delete(); + } + + async #deleteSingle(schemaName: string, fieldname: string) { + return await this.knex!('SingleValue') + .where({ parent: schemaName, fieldname }) + .delete(); } #deleteChildren(schemaName: string, parentName: string) { @@ -690,12 +694,7 @@ export default class DatabaseCore { order: 'asc', }); - const fieldValueMap: FieldValueMap = {}; - for (const row of values) { - fieldValueMap[row.fieldname as string] = row.value as RawValue; - } - - return fieldValueMap; + return getValueMapFromList(values, 'fieldname', 'value') as FieldValueMap; } #insertOne(schemaName: string, fieldValueMap: FieldValueMap) { @@ -721,7 +720,6 @@ export default class DatabaseCore { fieldValueMap: FieldValueMap ) { const fields = this.schemaMap[singleSchemaName].fields; - await this.#deleteSingleValues(singleSchemaName); for (const field of fields) { const value = fieldValueMap[field.fieldname] as RawValue | undefined; @@ -738,12 +736,38 @@ export default class DatabaseCore { fieldname: string, value: RawValue ) { - return await this.knex!('SingleValue') + const names: { name: string }[] = await this.knex!('SingleValue') + .select('name') .where({ parent: singleSchemaName, fieldname, - }) - .update({ value }); + }); + const name = names?.[0]?.name as string | undefined; + + if (name === undefined) { + this.#insertSingleValue(singleSchemaName, fieldname, value); + } else { + return await this.knex!('SingleValue').where({ name }).update({ + value, + modifiedBy: SYSTEM, + modified: new Date().toISOString(), + }); + } + } + + async #insertSingleValue( + singleSchemaName: string, + fieldname: string, + value: RawValue + ) { + const updateMap = getDefaultMetaFieldValueMap(); + const fieldValueMap: FieldValueMap = Object.assign({}, updateMap, { + parent: singleSchemaName, + fieldname, + value, + name: getRandomString(), + }); + return await this.knex!('SingleValue').insert(fieldValueMap); } async #initializeSingles() { diff --git a/backend/database/tests/helpers.ts b/backend/database/tests/helpers.ts index f9c86206..1852a4e2 100644 --- a/backend/database/tests/helpers.ts +++ b/backend/database/tests/helpers.ts @@ -162,3 +162,12 @@ export function getBuiltTestSchemaMap(): SchemaMap { const cleanedSchemas = cleanSchemas(abstractCombined); return addMetaFields(cleanedSchemas); } + +export function getBaseMeta() { + return { + createdBy: 'Administrator', + modifiedBy: 'Administrator', + created: new Date().toISOString(), + modified: new Date().toISOString(), + }; +} \ No newline at end of file diff --git a/backend/database/tests/testCore.spec.ts b/backend/database/tests/testCore.spec.ts index 810a526e..68c4c0f9 100644 --- a/backend/database/tests/testCore.spec.ts +++ b/backend/database/tests/testCore.spec.ts @@ -1,7 +1,8 @@ import * as assert from 'assert'; import 'mocha'; import { getMapFromList } from 'schemas/helpers'; -import { FieldTypeEnum } from 'schemas/types'; +import { FieldTypeEnum, RawValue } from 'schemas/types'; +import { getValueMapFromList } from 'utils'; import { sqliteTypeMap } from '../../common'; import DatabaseCore from '../core'; import { SqliteTableInfo } from '../types'; @@ -124,3 +125,156 @@ describe('DatabaseCore: Migrate and Check Db', function () { } }); }); + +describe('DatabaseCore: CRUD', function () { + let db: DatabaseCore; + const schemaMap = getBuiltTestSchemaMap(); + + this.beforeEach(async function () { + db = new DatabaseCore(); + db.connect(); + db.setSchemaMap(schemaMap); + await db.migrate(); + }); + + this.afterEach(async function () { + await db.close(); + }); + + specify('exists() before insertion', async function () { + for (const schemaName in schemaMap) { + const doesExist = await db.exists(schemaName); + if (['SingleValue', 'SystemSettings'].includes(schemaName)) { + assert.strictEqual(doesExist, true, `${schemaName} exists`); + } else { + assert.strictEqual(doesExist, false, `${schemaName} exists`); + } + } + }); + + specify('CRUD single values', async function () { + /** + * Checking default values which are created when db.migrate + * takes place. + */ + let rows: Record[] = await db.knex!.raw( + 'select * from SingleValue' + ); + const defaultMap = getValueMapFromList( + schemaMap.SystemSettings.fields, + 'fieldname', + 'default' + ); + for (const row of rows) { + assert.strictEqual( + row.value, + defaultMap[row.fieldname as string], + `${row.fieldname} default values equality` + ); + } + + /** + * Insertion and updation for single values call the same function. + * + * Insert + */ + + let localeRow = rows.find((r) => r.fieldname === 'locale'); + const localeEntryName = localeRow?.name as string; + const localeEntryCreated = localeRow?.created as string; + + let locale = 'hi-IN'; + await db.insert('SystemSettings', { locale }); + rows = await db.knex!.raw('select * from SingleValue'); + localeRow = rows.find((r) => r.fieldname === 'locale'); + + assert.notStrictEqual(localeEntryName, undefined, 'localeEntryName'); + assert.strictEqual(rows.length, 2, 'row length'); + assert.strictEqual( + localeRow?.name as string, + localeEntryName, + `localeEntryName ${localeRow?.name}, ${localeEntryName}` + ); + assert.strictEqual( + localeRow?.value, + locale, + `locale ${localeRow?.value}, ${locale}` + ); + assert.strictEqual( + localeRow?.created, + localeEntryCreated, + `locale ${localeRow?.value}, ${locale}` + ); + + /** + * Update + */ + locale = 'ca-ES'; + await db.update('SystemSettings', { locale }); + rows = await db.knex!.raw('select * from SingleValue'); + localeRow = rows.find((r) => r.fieldname === 'locale'); + + assert.notStrictEqual(localeEntryName, undefined, 'localeEntryName'); + assert.strictEqual(rows.length, 2, 'row length'); + assert.strictEqual( + localeRow?.name as string, + localeEntryName, + `localeEntryName ${localeRow?.name}, ${localeEntryName}` + ); + assert.strictEqual( + localeRow?.value, + locale, + `locale ${localeRow?.value}, ${locale}` + ); + assert.strictEqual( + localeRow?.created, + localeEntryCreated, + `locale ${localeRow?.value}, ${locale}` + ); + + /** + * Delete + */ + await db.delete('SystemSettings', 'locale'); + rows = await db.knex!.raw('select * from SingleValue'); + assert.strictEqual(rows.length, 1, 'delete one'); + await db.delete('SystemSettings', 'dateFormat'); + rows = await db.knex!.raw('select * from SingleValue'); + assert.strictEqual(rows.length, 0, 'delete two'); + + const dateFormat = 'dd/mm/yy'; + await db.insert('SystemSettings', { locale, dateFormat }); + rows = await db.knex!.raw('select * from SingleValue'); + assert.strictEqual(rows.length, 2, 'delete two'); + + /** + * Read + * + * getSingleValues + */ + const svl = await db.getSingleValues('locale', 'dateFormat'); + assert.strictEqual(svl.length, 2, 'getSingleValues length'); + for (const sv of svl) { + assert.strictEqual( + sv.parent, + 'SystemSettings', + `singleValue parent ${sv.parent}` + ); + assert.strictEqual( + sv.value, + { locale, dateFormat }[sv.fieldname], + `singleValue value ${sv.value}` + ); + + /** + * get + */ + const svlMap = await db.get('SystemSettings'); + assert.strictEqual(Object.keys(svlMap).length, 2, 'get key length'); + assert.strictEqual(svlMap.locale, locale, 'get locale'); + assert.strictEqual(svlMap.dateFormat, dateFormat, 'get locale'); + } + }); + + specify('CRUD simple nondependent schema', async function () {}); +}); diff --git a/tsconfig.json b/tsconfig.json index c890372a..6330a185 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "@/*": ["src/*"], "schemas/*": ["schemas/*"], "backend/*": ["backend/*"], - "common/*": ["common/*"] + "common/*": ["common/*"], + "utils/*": ["utils/*"] }, "lib": ["esnext", "dom", "dom.iterable", "scripthost"] }, diff --git a/utils/index.ts b/utils/index.ts new file mode 100644 index 00000000..dcac32b4 --- /dev/null +++ b/utils/index.ts @@ -0,0 +1,29 @@ +/** + * Functions in utils/*.ts can be used by the frontend or the backends + * And so should not contain and platforma specific imports. + */ +export function getValueMapFromList( + list: T[], + key: K, + valueKey: V, + filterUndefined: boolean = true +): Record { + if (filterUndefined) { + list = list.filter( + (f) => + (f[valueKey] as unknown) !== undefined && + (f[key] as unknown) !== undefined + ); + } + + return list.reduce((acc, f) => { + const keyValue = String(f[key]); + const value = f[valueKey]; + acc[keyValue] = value; + return acc; + }, {} as Record); +} + +export function getRandomString(): string { + return Math.random().toString(36).slice(2, 8); +} diff --git a/vue.config.js b/vue.config.js index 04f17139..bc2e0e95 100644 --- a/vue.config.js +++ b/vue.config.js @@ -43,6 +43,7 @@ module.exports = { schemas: path.resolve(__dirname, './schemas'), backend: path.resolve(__dirname, './backend'), common: path.resolve(__dirname, './common'), + utils: path.resolve(__dirname, './utils'), }); config.plugins.push(