2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

test: add tests for singlevalue CRUD

- add utils
This commit is contained in:
18alantom 2022-03-29 13:48:39 +05:30
parent f7a2dd8f2b
commit f5d795d95b
7 changed files with 253 additions and 33 deletions

View File

@ -23,12 +23,14 @@ export const sqliteTypeMap: Record<string, KnexColumnType> = {
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,
};
}

View File

@ -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<FieldValueMap[]> {
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() {

View File

@ -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(),
};
}

View File

@ -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<string, RawValue>[] = 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 () {});
});

View File

@ -18,7 +18,8 @@
"@/*": ["src/*"],
"schemas/*": ["schemas/*"],
"backend/*": ["backend/*"],
"common/*": ["common/*"]
"common/*": ["common/*"],
"utils/*": ["utils/*"]
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},

29
utils/index.ts Normal file
View File

@ -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<T, K extends keyof T, V extends keyof T>(
list: T[],
key: K,
valueKey: V,
filterUndefined: boolean = true
): Record<string, unknown> {
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<string, unknown>);
}
export function getRandomString(): string {
return Math.random().toString(36).slice(2, 8);
}

View File

@ -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(