@natlibfi/marc-record
Version:
MARC record implementation in JavaScript
481 lines (437 loc) • 15.7 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _fixugen = _interopRequireDefault(require("@natlibfi/fixugen"));
var _fixura = require("@natlibfi/fixura");
var _chai = require("chai");
var _debug = _interopRequireDefault(require("debug"));
var _2 = require(".");
/* eslint-disable max-lines */
/* eslint-disable max-statements */
/* eslint-disable complexity */
/* eslint-disable no-unused-vars */
const debug = (0, _debug.default)('@natlibfi/marc-record/index.spec.js'); // <---
/******************************************************************************
*
* Short guide to make generated test cases:
*
* ----------------------------------------------------------------------------
*
* Test case directory structure:
*
* test-fixtures/index/MyTest/01/
*
* metadata.json - Test specifications
* input.json - if input record is not specified in metadata.json
* result.json - if expected output record is not specified in metadata.json
*
* ----------------------------------------------------------------------------
*
* metadata.json:
*
* description: string
*
* Test description.
*
* skip: Set true to skip this particular case.
*
* only: Set true to run only this case.
*
* Specifying input record for test case:
*
* input: object / array of strings
*
* Either object suitable for MarcRecord constructor, or a list of
* strings joined with newlines to be used with MarcRecord.fromString.
*
* noinput: [optional] true/false
*
* Some test cases have no sensible input records, for example cases that test
* MarcRecord constructor. You can omit "input" field by setting "noinput: true".
*
* Specifying expected result of test case:
*
* result: object / array of strings
*
* Record to compare the modifications made to input. Similar to input record,
* you can specify the result either as object for MacrRecord(), or as a list
* of strings for MarcRecord.fromString()
*
* immutable: [optional] true/false
*
* If your test case should not modify the input, set "immutable: true"
* to omit expected result record. In this case, result record is compared
* to input record.
*
* Specifying operations performed in test case:
*
* operations: array of objects
*
* Operations performed to input record. The exact syntax is found from
* runOperation() function below. In general, names match to function names,
* but arguments may have special cases to feed internal values to functions.
*
* Operations are pairs of name and args:
*
* operations: [
* { name: "myFunc", args { ... } },
* { name: "myFunc", args { ... } },
* ...
* ]
*
* Args are parsed in runOperation() function. You can add new operations
* to that specific function, as well as argument parsing for it.
*
* returns: [optional] object / array
*
* In some tests, you are interested in the return values of the operations,
* not the modifications in input record. If "returns" field is present,
* the return value of last operation is compared to it.
*
* For example, you may check return values of MarcRecord.get() or
* MarcRecord.toString(), and expect the input record stays immutable.
*
* throws: [optional] string
*
* In some cases, you are not interested in return values, but how the
* operation fails. I
*
* ----------------------------------------------------------------------------
*
* Consult test case descriptions in test-fixtures/index/ directory tree.
*
* In case your test case is not suitable for automated generation, you can
* add it in mocha way as usual (consult the end of this file for examples).
*
******************************************************************************/
describe('index', () => {
beforeEach(() => {
// Reset global validation options before each case
_2.MarcRecord.setValidationOptions({});
});
//***************************************************************************
//
// Generate tests which use operations table in metadata to perform operations
// to input record, and check it equals to result record after operations.
//
//***************************************************************************
(0, _fixugen.default)({
callback: doTest,
path: [__dirname, '..', 'test-fixtures', 'index'],
useMetadataFile: true,
recurse: true,
fixura: {
reader: _fixura.READERS.JSON,
failWhenNotFound: true
}
});
function doTest(metadata) {
// Get input & expected output
const {
getFixture
} = metadata;
const {
input,
result,
immutable,
noinput,
validationOptions
} = metadata;
// if !noinput and we have input in metadata we use it, otherwise we get it from file input.json
const inputRecord = noinput ? null : getRecord(input, 'input.json');
const record = inputRecord ? _2.MarcRecord.clone(inputRecord, validationOptions) : null;
// Operations may lead to record validation errors after changes. We don't want to
// get those errors when reading the expected result record, so we turn off
// global validation checks temporarily.
_2.MarcRecord.setValidationOptions({
fields: false,
subfields: false,
subfieldValues: false
});
const outputRecord = immutable ? inputRecord : getRecord(result, 'result.json');
_2.MarcRecord.setValidationOptions({});
// Get operations
const {
operations,
returns,
throws
} = metadata;
checkResults(operations, throws, returns);
(0, _chai.expect)(record).to.eql(outputRecord);
return;
//---------------------------------------------------------------------------
function checkResults(operations, throws, returns) {
//debug(`Returns: ${returns} ${result}`);
if (throws) {
try {
return runOps();
} catch (e) {
(0, _chai.expect)(e).to.have.property('message');
(0, _chai.expect)(e).to.have.property('validationResults');
(0, _chai.expect)(e.message).to.match(new RegExp(`^${throws}`, 'u'));
}
return;
}
const result = runOps();
if (returns === undefined) {
return;
}
(0, _chai.expect)(result).to.eql(returns);
function runOps() {
return operations.reduce((_, op) => runOperation(op), record);
}
}
//---------------------------------------------------------------------------
function getRecord(fromMeta, filename) {
const data = fromMeta || getFixture(filename);
if (Array.isArray(data)) {
const text = data.join('\n');
return _2.MarcRecord.fromString(text, validationOptions);
}
return new _2.MarcRecord(data, validationOptions);
}
//---------------------------------------------------------------------------
function runOperation(op) {
const {
name,
args
} = op;
//-------------------------------------------------------------------------
if (name === 'nop') {
return record;
}
//-------------------------------------------------------------------------
if (name === 'insertField') {
(0, _chai.expect)(record.insertField(args) === record);
return record;
}
//-------------------------------------------------------------------------
if (name === 'insertFields') {
(0, _chai.expect)(record.insertFields(args) === record);
return record;
}
//-------------------------------------------------------------------------
if (name === 'appendField') {
(0, _chai.expect)(record.appendField(args) === record);
return record;
}
//-------------------------------------------------------------------------
if (name === 'appendFields') {
(0, _chai.expect)(record.appendFields(args) === record);
return record;
}
//-------------------------------------------------------------------------
if (name === 'removeField') {
const what = function (args) {
const {
string,
field,
regexp,
index
} = args;
if (string || field) {
return string || field;
}
if (regexp) {
return new RegExp(regexp, 'u');
}
if (index !== undefined) {
return record.fields[index];
}
throw new Error(`No arg for ${name}(): ${JSON.stringify(args, null, 2)}`);
}(args);
(0, _chai.expect)(record.removeField(what) === record);
return record;
}
//-------------------------------------------------------------------------
if (name === 'removeFields') {
const what = function (args) {
const {
getRegExp
} = args;
if (getRegExp) {
return record.get(new RegExp(getRegExp, 'u'));
}
throw new Error(`No arg for ${name}(): ${JSON.stringify(args, null, 2)}`);
}(args);
(0, _chai.expect)(record.removeFields(what) === record);
return record;
}
//-------------------------------------------------------------------------
if (name === 'removeSubfield') {
const field = record.fields[args.field];
const subfield = field.subfields[args.subfield];
(0, _chai.expect)(record.removeSubfield(subfield, field) === record);
return record;
}
//-------------------------------------------------------------------------
if (['get', 'pop'].includes(name)) {
const what = function (args) {
const {
string,
regexp
} = args;
if (string) {
return string;
}
if (regexp) {
return new RegExp(regexp, 'u');
}
throw new Error(`No arg for ${name}(): ${JSON.stringify(args, null, 2)}`);
}(args);
if (name === 'pop') {
return record.pop(what); // eslint-disable-line functional/immutable-data
}
return record.get(what);
}
//-------------------------------------------------------------------------
if (name === 'getControlfields') {
return record.getControlfields();
}
//-------------------------------------------------------------------------
if (name === 'getDatafields') {
return record.getDatafields();
}
//-------------------------------------------------------------------------
if (name === 'getValidationOptions') {
return _2.MarcRecord.getValidationOptions();
}
//-------------------------------------------------------------------------
if (name === 'setValidationOptions') {
return _2.MarcRecord.setValidationOptions(args);
}
//-------------------------------------------------------------------------
if (name === 'getValidationErrors') {
return record.getValidationErrors();
}
//-------------------------------------------------------------------------
if (name === 'MarcRecord') {
const {
leader,
fields,
validationOptions
} = args ?? {};
const object = args && {
leader,
fields
};
//debug(`Object: ${JSON.stringify(object, null, 2)}`);
const created = new _2.MarcRecord(object, validationOptions);
(0, _chai.expect)(created).to.be.an('object');
(0, _chai.expect)(object === undefined || created.fields !== object.fields);
//debug(`Created: ${JSON.stringify(created, null, 2)}`);
return created;
}
//-------------------------------------------------------------------------
if (name === 'clone') {
const {
validationOptions
} = args ?? {};
const cloned = _2.MarcRecord.clone(record, validationOptions);
// Expect cloned record to be deeply cloned, and still being identical
(0, _chai.expect)(record._validationOptions !== cloned._validationOptions);
(0, _chai.expect)(record.fields !== cloned.fields);
(0, _chai.expect)(record.leader !== cloned.leader);
(0, _chai.expect)(record.equalsTo(cloned) === true);
return cloned;
}
//-------------------------------------------------------------------------
if (name === 'toString') {
return record.toString().split('\n');
}
//-------------------------------------------------------------------------
if (name === 'toObject') {
return record.toObject();
}
//-------------------------------------------------------------------------
if (name === 'equalsTo') {
const what = function (args) {
const {
self,
clone,
string,
object
} = args;
if (self) {
return record;
}
if (clone) {
return _2.MarcRecord.clone(record);
}
if (string) {
return _2.MarcRecord.fromString(string.join('\n'));
}
if (object) {
return object;
}
const {
leader,
fields,
validationOptions
} = args;
return new _2.MarcRecord({
leader,
fields
}, validationOptions);
}(args);
//debug(`Record: ${JSON.stringify(record, null, 2)}`);
//debug(`What: ${JSON.stringify(what, null, 2)}`);
const result = record.equalsTo(what);
(0, _chai.expect)(_2.MarcRecord.isEqual(record, what) === result);
return result;
}
//-------------------------------------------------------------------------
if (name === 'getFields') {
const {
tag,
value
} = args;
const fields = record.getFields(tag, value);
(0, _chai.expect)(record.containsFieldWithValue(tag, value)).eql(fields.length > 0);
return fields;
}
//-------------------------------------------------------------------------
if (name === 'isTypeOfMaterial') {
const {
target
} = args;
// console.info(`TARGET: '${target}'\n${record.toString()}`); // eslint-disable-line no-console
if (target === 'BK') {
// Book
return record.isBK();
}
if (target === 'CF') {
// Computer File
return record.isCF();
}
if (target === 'CR') {
// Continuing Resource
return record.isCR();
}
if (target === 'MP') {
// Map
return record.isMP();
}
if (target === 'MU') {
// Music
return record.isMU();
}
if (target === 'MX') {
// Mixed
return record.isMX();
}
if (target === 'VM') {
// Visual Material
return record.isVM();
}
return false;
}
//-------------------------------------------------------------------------
if (name === 'getTypeOfMaterial') {
return record.getTypeOfMaterial();
}
//-------------------------------------------------------------------------
throw new Error(`Invalid operation: ${name}`);
}
}
});
//# sourceMappingURL=index.spec.js.map