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:
parent
f7a2dd8f2b
commit
f5d795d95b
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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(),
|
||||
};
|
||||
}
|
@ -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 () {});
|
||||
});
|
||||
|
@ -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
29
utils/index.ts
Normal 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);
|
||||
}
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user