UNPKG

@natlibfi/marc-record

Version:

MARC record implementation in JavaScript

481 lines (437 loc) 15.7 kB
"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