nodejs-nomer 1.3.9 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/result.js CHANGED
@@ -36,7 +36,7 @@ var Result = exports["default"] = /*#__PURE__*/function () {
36
36
  var columns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
37
37
  var arr = [];
38
38
  result.split('\n').forEach(function (line) {
39
- arr.push(line.split('\t'));
39
+ if (line.trim()) arr.push(line.split('\t'));
40
40
  });
41
41
  return columns ? arr.map(function (v) {
42
42
  return Object.assign.apply(Object, _toConsumableArray(columns.map(function (k, i) {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "nodejs-nomer",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
4
4
  "description": "NodeJS wrapper for nomer",
5
5
  "bin": {
6
6
  "nomer": "./bin/nomer"
7
7
  },
8
8
  "main": "index.js",
9
9
  "scripts": {
10
- "test": "npm run test",
10
+ "test": "jest",
11
11
  "build": "babel src -d dist",
12
12
  "preinstall": "./install_nomer.sh"
13
13
  },
@@ -27,13 +27,15 @@
27
27
  "homepage": "https://github.com/zedomel/nodejs-nomer#readme",
28
28
  "devDependencies": {
29
29
  "@babel/cli": "^7.24.8",
30
- "@babel/core": "^7.24.9",
31
- "@babel/preset-env": "^7.24.8",
30
+ "@babel/core": "^7.28.5",
31
+ "@babel/preset-env": "^7.28.5",
32
+ "babel-jest": "^30.2.0",
32
33
  "babel-loader": "^9.1.3",
34
+ "jest": "^30.2.0",
33
35
  "webpack": "^5.93.0",
34
36
  "webpack-cli": "^5.1.4"
35
37
  },
36
38
  "dependencies": {
37
39
  "properties-file": "^3.5.1"
38
40
  }
39
- }
41
+ }
package/src/result.js CHANGED
@@ -9,7 +9,8 @@ export default class Result {
9
9
  static tsv(result, columns = null) {
10
10
  const arr = [];
11
11
  result.split('\n').forEach((line) => {
12
- arr.push(line.split('\t'));
12
+ if (line.trim())
13
+ arr.push(line.split('\t'));
13
14
  });
14
15
 
15
16
  return columns ? arr.map((v) => Object.assign(...columns.map((k, i) => ({ [k]: v[i] })))) : arr
@@ -0,0 +1,334 @@
1
+ import { Nomer } from '../src/index';
2
+ import readline from "readline";
3
+ import fs from "fs"
4
+
5
+ describe('Nomer Integration Tests', () => {
6
+ let nomer;
7
+
8
+ beforeAll(() => {
9
+ // Initialize Nomer instance
10
+ nomer = new Nomer();
11
+ });
12
+
13
+ describe('version', () => {
14
+ it('should return nomer version string', () => {
15
+ const result = nomer.version();
16
+ expect(result).toBeTruthy();
17
+ expect(result).toContain('0.5.17');
18
+ });
19
+ });
20
+
21
+ describe('properties', () => {
22
+ it('should return properties configuration', () => {
23
+ const result = nomer.properties();
24
+ expect(result).toBeTruthy();
25
+ expect(result).toContain('nomer.');
26
+ });
27
+ });
28
+
29
+ describe('inputSchema', () => {
30
+ it('should return input schema', () => {
31
+ const result = nomer.inputSchema();
32
+ expect(result).toBeTruthy();
33
+ // Should be valid JSON
34
+ expect(() => JSON.parse(result)).not.toThrow();
35
+ });
36
+ });
37
+
38
+ describe('outputSchema', () => {
39
+ it('should return output schema', () => {
40
+ const result = nomer.outputSchema();
41
+ expect(result).toBeTruthy();
42
+ // Should be valid JSON
43
+ expect(() => JSON.parse(result)).not.toThrow();
44
+ });
45
+ });
46
+
47
+ describe('matcher', () => {
48
+ it('should return list of matchers in tsv format', () => {
49
+ const result = nomer.matcher();
50
+ expect(result).toBeTruthy();
51
+ expect(result).toContain('col'); // TSV format
52
+ });
53
+
54
+ it('should return list of matchers in json format', () => {
55
+ const result = nomer.matcher('json');
56
+ expect(result).toBeTruthy();
57
+ expect(() => JSON.parse(result)).not.toThrow();
58
+ });
59
+
60
+ it('should return verbose matcher information', () => {
61
+ const result = nomer.matcher('tsv', true);
62
+ expect(result).toBeTruthy();
63
+ expect(result).toContain('col');
64
+ });
65
+ });
66
+
67
+ describe('append', () => {
68
+ it('should append taxonomic information for valid species', () => {
69
+ const result = nomer.append('\tHomo sapiens\t', 'col');
70
+ expect(result).toBeTruthy();
71
+ expect(result).toContain('HAS_ACCEPTED_NAME');
72
+ });
73
+
74
+ it('should return result in json format', () => {
75
+ const result = nomer.append('\tHomo sapiens\t', 'col', 'json');
76
+ expect(result).toBeTruthy();
77
+ expect(() => JSON.parse(result)).not.toThrow();
78
+ });
79
+
80
+ it('should handle invalid taxa gracefully', () => {
81
+ const result = nomer.append('\tInvalidTaxonXYZ123\t', 'col');
82
+ expect(result).toBeTruthy();
83
+ expect(result).toContain('NONE');
84
+ });
85
+ });
86
+
87
+ describe('replace', () => {
88
+ it('should replace query with matched taxonomic information', () => {
89
+ const result = nomer.replace('\tHomo sapiens\t', 'col');
90
+ expect(result).toBeTruthy();
91
+ expect(result).toContain('COL:6MB3T')
92
+ });
93
+
94
+ it('should handle invalid taxa', () => {
95
+ const query = '\tInvalidtaxonXYZ123\t';
96
+ const result = nomer.replace(query, 'col');
97
+ expect(result).toBeTruthy();
98
+ expect(result).toContain('null');
99
+ });
100
+ });
101
+
102
+ describe('appendAsync', () => {
103
+ it('should return a spawn process', (done) => {
104
+ const process = nomer.appendAsync('col');
105
+
106
+ expect(process).toBeTruthy();
107
+ expect(process.stdin).toBeTruthy();
108
+ expect(process.stdout).toBeTruthy();
109
+ expect(process.stderr).toBeTruthy();
110
+
111
+ let output = '';
112
+
113
+ process.stdout.on('data', (data) => {
114
+ output += data.toString();
115
+ });
116
+
117
+ process.on('close', (code) => {
118
+ expect(code).toBe(0);
119
+ expect(output).toBeTruthy();
120
+ expect(output).toContain('HAS_ACCEPTED_NAME');
121
+ done();
122
+ });
123
+
124
+ // Write test data and close stdin
125
+ process.stdin.write('\tHomo sapiens\t\n');
126
+ process.stdin.end();
127
+ }, 10000);
128
+
129
+ it('should handle streaming multiple queries', (done) => {
130
+ const process = nomer.appendAsync('col');
131
+
132
+ let output = '';
133
+
134
+ process.stdout.on('data', (data) => {
135
+ output += data.toString();
136
+ });
137
+
138
+ process.on('close', (code) => {
139
+ expect(code).toBe(0);
140
+ expect(output).toBeTruthy();
141
+ // Each line should be valid JSON
142
+ const lines = output.trim().split('\n');
143
+ lines.forEach(line => {
144
+ if (line.trim()) {
145
+ expect(line).toContain('HAS_ACCEPTED_NAME');
146
+ }
147
+ });
148
+ done();
149
+ });
150
+
151
+ // Write multiple queries
152
+ process.stdin.write('\tHomo sapiens\t\n');
153
+ process.stdin.write('\tCanis lupus\t\n');
154
+ process.stdin.end();
155
+ }, 10000);
156
+ });
157
+
158
+ it('should handle streaming multiple queries in json', (done) => {
159
+ const process = nomer.appendAsync('col', 'json');
160
+
161
+ let output = '';
162
+
163
+ process.stdout.on('data', (data) => {
164
+ output += data.toString();
165
+ });
166
+
167
+ process.on('close', (code) => {
168
+ expect(code).toBe(0);
169
+ expect(output).toBeTruthy();
170
+ // Each line should be valid JSON
171
+ const lines = output.trim().split('\n');
172
+ lines.forEach(line => {
173
+ if (line.trim()) {
174
+ expect(() => JSON.parse(line)).not.toThrow();
175
+ }
176
+ });
177
+ done();
178
+ });
179
+
180
+ // Write multiple queries
181
+ process.stdin.write('\tHomo sapiens\t\n');
182
+ process.stdin.write('\tCanis lupus\t\n');
183
+ process.stdin.end();
184
+ }, 10000);
185
+
186
+ it('should handle streaming multiple wht pipe split', (done) => {
187
+ const process = nomer.appendAsync('col');
188
+
189
+ let output = [];
190
+
191
+ const rl = readline.createInterface({ input: process.stdout });
192
+ rl.on('line', (line) => {
193
+ const trimmed = line.replace(/\r$/, ''); // strip CR if present
194
+ if (trimmed.length > 0) {
195
+ output.push(trimmed);
196
+ }
197
+ });
198
+
199
+ process.on('close', (code) => {
200
+ expect(code).toBe(0);
201
+ expect(output).toBeTruthy();
202
+ // Each line should be valid JSON
203
+ output.forEach(line => {
204
+ if (line.trim()) {
205
+ expect(line).toMatch(/HAS_ACCEPTED_NAME|SYNONYM_OF/);
206
+ }
207
+ });
208
+ done();
209
+ });
210
+
211
+ // Write multiple queries
212
+ process.stdin.write('\tHomo sapiens\t\n');
213
+ process.stdin.write('\tCanis lupus\t\n');
214
+ process.stdin.write('\tBauhinia acuminata\t\n');
215
+ process.stdin.end();
216
+ }, 10000);
217
+
218
+ // describe('clean', () => {
219
+ // it('should execute clean command', () => {
220
+ // const result = nomer.clean();
221
+ // // Clean may return empty or success message
222
+ // expect(result !== null).toBe(true);
223
+ // });
224
+ // });
225
+
226
+ describe('Nomer with custom properties', () => {
227
+ it('should use custom properties file', () => {
228
+ // Create a nomer instance with properties
229
+ const tempFile = './test/test.properties';
230
+ try {
231
+ fs.writeFileSync(tempFile, 'nomer.test.property=value\n');
232
+ const customNomer = new Nomer(tempFile);
233
+ const result = customNomer.properties();
234
+ expect(result).toBeTruthy();
235
+ expect(result).toContain('nomer.test.property=value');
236
+ } finally {
237
+ fs.unlinkSync(tempFile);
238
+ }
239
+ });
240
+
241
+ it('should use custom echo option', () => {
242
+ const customNomer = new Nomer(null, '-n');
243
+ const result = customNomer.append('\Homo sapiens\t');
244
+ expect(result).toBeTruthy();
245
+ });
246
+ });
247
+
248
+ describe('toJson', () => {
249
+ it('should convert result to json', () => {
250
+ const tsvResult = nomer.append('\tHomo sapiens\t', 'col', 'json');
251
+ const jsonResult = nomer.toJson(tsvResult);
252
+ expect(jsonResult).toBeTruthy();
253
+ expect(Array.isArray(jsonResult)).toBe(true);
254
+ if (jsonResult.length > 0) {
255
+ expect(typeof jsonResult[0]).toBe('object');
256
+ expect(jsonResult[0]).toHaveProperty('species');
257
+ }
258
+ });
259
+ });
260
+
261
+ describe('toArray', () => {
262
+ it('should convert result to array', () => {
263
+ const result = nomer.append('\tHomo sapiens\t', 'col');
264
+ const arrayResult = nomer.toArray(result);
265
+ expect(arrayResult).toBeTruthy();
266
+ expect(Array.isArray(arrayResult)).toBe(true);
267
+ });
268
+
269
+ it('should convert result to array with custom columns', () => {
270
+ const result = nomer.append('\tHomo sapiens\t', 'col');
271
+ const columns = ['id', 'name'];
272
+ const arrayResult = nomer.toArray(result, columns);
273
+ expect(arrayResult).toBeTruthy();
274
+ expect(Array.isArray(arrayResult)).toBe(true);
275
+ if (arrayResult.length > 0) {
276
+ expect(arrayResult[0]).toHaveProperty('id');
277
+ expect(arrayResult[0]).toHaveProperty('name');
278
+ }
279
+ });
280
+
281
+ it('should convert streaming result to array', (done) => {
282
+ const process = nomer.appendAsync('col');
283
+
284
+ let output = '';
285
+ process.stdout.on('data', (data) => {
286
+ if (data.toString().trim())
287
+ output += data.toString();
288
+ });
289
+
290
+ process.on('close', (code) => {
291
+ expect(code).toBe(0);
292
+ expect(output).toBeTruthy();
293
+ const arrayResult = nomer.toArray(output);
294
+ expect(arrayResult).toBeTruthy();
295
+ expect(Array.isArray(arrayResult)).toBe(true);
296
+ // Each line should be valid JSON
297
+ arrayResult.forEach(arr => {
298
+ expect(arr[3]).toMatch(/HAS_ACCEPTED_NAME|SYNONYM_OF/);
299
+ });
300
+ done();
301
+ });
302
+
303
+ // Write multiple queries
304
+ process.stdin.write('\tHomo sapiens\t\n');
305
+ process.stdin.write('\tCanis lupus\t\n');
306
+ process.stdin.write('\tBauhinia acuminata\t\n');
307
+ process.stdin.end();
308
+ }, 10000);
309
+ });
310
+
311
+ describe('toObject', () => {
312
+ it('should convert result to object with schema', () => {
313
+ const result = nomer.append('\tHomo sapiens\t', 'col');
314
+ const objectResult = nomer.toObject(result);
315
+ expect(objectResult).toBeTruthy();
316
+ expect(Array.isArray(objectResult)).toBe(true);
317
+ if (objectResult.length > 0) {
318
+ expect(typeof objectResult[0]).toBe('object');
319
+ }
320
+ });
321
+ });
322
+
323
+ describe('getPropertiesPath', () => {
324
+ it('should return null when no properties path is set', () => {
325
+ const testNomer = new Nomer();
326
+ expect(testNomer.getPropertiesPath()).toBe(null);
327
+ });
328
+
329
+ it('should return properties path when set', () => {
330
+ const testNomer = new Nomer('./test.properties');
331
+ expect(testNomer.getPropertiesPath()).toBe('./test.properties');
332
+ });
333
+ });
334
+ });
package/test/script.js ADDED
@@ -0,0 +1,48 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const Nomer = require("nodejs-nomer");
5
+
6
+ const tempFilePath = path.join(os.tmpdir(), 'nomer.properties');
7
+ fs.writeFileSync(tempFilePath, `nomer.schema.input=[{"column":0,"type":"externalId"},{"column": 1,"type":"name"},{"column": 2,"type":"path"}]`);
8
+
9
+ console.log(tempFilePath);
10
+ const nomer = new Nomer.Nomer(tempFilePath);
11
+
12
+ console.time("version")
13
+ console.log(nomer.version());
14
+ console.timeEnd("version")
15
+
16
+
17
+ console.time("append async")
18
+ const process = nomer.appendAsync("gbif")
19
+ process.stdout.on('data', (data) => {
20
+ const obj = nomer.toObject(data.toString()).filter((obj) => obj.matchType !== "NONE")
21
+ console.log(obj);
22
+ })
23
+ process.stdout.on('end', () => {
24
+ console.log('append async end');
25
+ })
26
+
27
+ process.stdin.on('error', (e) => {
28
+ console.log('stdin error: ' + e)
29
+ });
30
+
31
+ // process.stdin.write("\tSenegalia\t\n")
32
+ process.stdin.write("\tSenegalia\tPlantae\n")
33
+ process.stdin.end()
34
+
35
+
36
+ // console.time("append")
37
+ // const results = []
38
+ // for (let index = 0; index < 100; index++) {
39
+ // results.push(nomer.append("\tBauhinia rufa\t", "gbif"))
40
+ // }
41
+ // console.timeEnd("append")
42
+
43
+ // console.time("toObject")
44
+ // for (let index = 0; index < results.length; index++) {
45
+ // const element = results[index];
46
+ // nomer.toObject(element)
47
+ // }
48
+ // console.timeEnd("toObject")
@@ -0,0 +1,182 @@
1
+ const { execSync, spawn } = require('child_process');
2
+ import { runNomer, runNomerAsync, getNomerSimpleCmd, getNomerValidateCmd, getNomerMatchCmd } from "../src/utils";
3
+
4
+ // Mock child_process
5
+ jest.mock('child_process');
6
+
7
+ describe('utils', () => {
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ // Suppress console.log in tests
11
+ jest.spyOn(console, 'log').mockImplementation(() => { });
12
+ });
13
+
14
+ afterEach(() => {
15
+ console.log.mockRestore();
16
+ });
17
+
18
+ describe('getNomerValidateCmd', () => {
19
+ it('should throw error when filepath is empty string', () => {
20
+ expect(() => getNomerValidateCmd("")).toThrow();
21
+ });
22
+
23
+ it('should generate basic validate command', () => {
24
+ const result = getNomerValidateCmd("/path/to/file");
25
+ expect(result).toBe("curl -L /path/to/file | nomer validate-term");
26
+ });
27
+
28
+ it('should generate validate command with custom cmd', () => {
29
+ const result = getNomerValidateCmd("/path/to/file", "validate-term-link");
30
+ expect(result).toBe("curl -L /path/to/file | nomer validate-term-link");
31
+ });
32
+
33
+ it('should generate validate command with properties', () => {
34
+ const result = getNomerValidateCmd("/path/to/file", "validate-term", "props.properties");
35
+ expect(result).toBe("curl -L /path/to/file | nomer validate-term -p props.properties");
36
+ });
37
+ });
38
+
39
+ describe('getNomerMatchCmd', () => {
40
+ it('should generate basic match command without query', () => {
41
+ const result = getNomerMatchCmd();
42
+ expect(result).toBe("nomer append globi-taxon-cache");
43
+ });
44
+
45
+ it('should generate match command with query', () => {
46
+ const result = getNomerMatchCmd("Homo sapiens");
47
+ expect(result).toBe("echo -e 'Homo sapiens' | nomer append globi-taxon-cache");
48
+ });
49
+
50
+ it('should generate match command with custom echo option', () => {
51
+ const result = getNomerMatchCmd("Homo sapiens", "append", "globi-taxon-cache", null, null, "-n");
52
+ expect(result).toBe("echo -n 'Homo sapiens' | nomer append globi-taxon-cache");
53
+ });
54
+
55
+ it('should generate match command with properties', () => {
56
+ const result = getNomerMatchCmd("", "append", "globi-taxon-cache", "props.properties");
57
+ expect(result).toBe("nomer append globi-taxon-cache -p props.properties");
58
+ });
59
+
60
+ it('should generate match command with output format', () => {
61
+ const result = getNomerMatchCmd("", "append", "globi-taxon-cache", null, "json");
62
+ expect(result).toBe("nomer append globi-taxon-cache -o json");
63
+ });
64
+
65
+ it('should generate match command with all options', () => {
66
+ const result = getNomerMatchCmd("Homo sapiens", "replace", "custom-matcher", "props.properties", "json", "-e");
67
+ expect(result).toBe("echo -e 'Homo sapiens' | nomer replace custom-matcher -p props.properties -o json");
68
+ });
69
+ });
70
+
71
+ describe('getNomerSimpleCmd', () => {
72
+ it('should generate basic version command', () => {
73
+ const result = getNomerSimpleCmd();
74
+ expect(result).toBe("nomer version");
75
+ });
76
+
77
+ it('should generate command with custom cmd', () => {
78
+ const result = getNomerSimpleCmd("clean");
79
+ expect(result).toBe("nomer clean");
80
+ });
81
+
82
+ it('should generate command with properties', () => {
83
+ const result = getNomerSimpleCmd("version", false, "props.properties");
84
+ expect(result).toBe("nomer version -p props.properties");
85
+ });
86
+
87
+ it('should generate command with output format', () => {
88
+ const result = getNomerSimpleCmd("version", false, null, "json");
89
+ expect(result).toBe("nomer version -o json");
90
+ });
91
+
92
+ it('should generate command with verbose flag', () => {
93
+ const result = getNomerSimpleCmd("version", true);
94
+ expect(result).toBe("nomer version -v");
95
+ });
96
+
97
+ it('should generate command with all options', () => {
98
+ const result = getNomerSimpleCmd("matchers", true, "props.properties", "tsv");
99
+ expect(result).toBe("nomer matchers -p props.properties -o tsv -v");
100
+ });
101
+ });
102
+
103
+ describe('runNomer', () => {
104
+ it('should execute command and return trimmed result', () => {
105
+ const mockResult = Buffer.from('result output\n\n');
106
+ execSync.mockReturnValue(mockResult);
107
+
108
+ const result = runNomer('nomer version');
109
+
110
+ expect(execSync).toHaveBeenCalledWith('nomer version', {
111
+ maxBuffer: 4096 * 4096
112
+ });
113
+ expect(result).toBe('result output');
114
+ });
115
+
116
+ it('should return null on error and log message', () => {
117
+ const mockError = new Error('Command failed');
118
+ execSync.mockImplementation(() => {
119
+ throw mockError;
120
+ });
121
+
122
+ const result = runNomer('nomer version');
123
+
124
+ expect(console.log).toHaveBeenCalledWith('Error running nomer cmd: Command failed');
125
+ expect(result).toBe(null);
126
+ });
127
+
128
+ it('should handle empty result', () => {
129
+ execSync.mockReturnValue(Buffer.from(''));
130
+
131
+ const result = runNomer('nomer version');
132
+
133
+ expect(result).toBeUndefined();
134
+ });
135
+
136
+ it('should trim whitespace from result', () => {
137
+ const mockResult = Buffer.from(' result with spaces \n\t');
138
+ execSync.mockReturnValue(mockResult);
139
+
140
+ const result = runNomer('nomer version');
141
+
142
+ expect(result).toBe(' result with spaces');
143
+ });
144
+ });
145
+
146
+ describe('runNomerAsync', () => {
147
+ it('should spawn process with correct arguments', () => {
148
+ const mockProcess = { stdout: {}, stderr: {}, stdin: {} };
149
+ spawn.mockReturnValue(mockProcess);
150
+
151
+ const result = runNomerAsync('nomer append globi-taxon-cache');
152
+
153
+ expect(spawn).toHaveBeenCalledWith('nomer', ['append', 'globi-taxon-cache'], {
154
+ shell: true
155
+ });
156
+ expect(result).toBe(mockProcess);
157
+ });
158
+
159
+ it('should handle complex commands with multiple arguments', () => {
160
+ const mockProcess = { stdout: {}, stderr: {}, stdin: {} };
161
+ spawn.mockReturnValue(mockProcess);
162
+
163
+ const result = runNomerAsync('nomer append globi-taxon-cache -p props.properties -o json');
164
+
165
+ expect(spawn).toHaveBeenCalledWith('nomer', ['append', 'globi-taxon-cache', '-p', 'props.properties', '-o', 'json'], {
166
+ shell: true
167
+ });
168
+ expect(result).toBe(mockProcess);
169
+ });
170
+
171
+ it('should split command on whitespace', () => {
172
+ const mockProcess = { stdout: {}, stderr: {}, stdin: {} };
173
+ spawn.mockReturnValue(mockProcess);
174
+
175
+ runNomerAsync('echo -e test | nomer version');
176
+
177
+ expect(spawn).toHaveBeenCalledWith('echo', ['-e', 'test', '|', 'nomer', 'version'], {
178
+ shell: true
179
+ });
180
+ });
181
+ });
182
+ });