@testomatio/reporter 1.2.0-beta-4 → 1.2.0

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/lib/xmlReader.js CHANGED
@@ -1,57 +1,57 @@
1
1
  const debug = require('debug')('@testomatio/reporter:xml');
2
- const path = require("path");
2
+ const path = require('path');
3
3
  const chalk = require('chalk');
4
- const fs = require("fs");
5
- const { XMLParser } = require("fast-xml-parser");
4
+ const fs = require('fs');
5
+ const { XMLParser } = require('fast-xml-parser');
6
6
  const { APP_PREFIX, STATUS } = require('./constants');
7
- const { fetchFilesFromStackTrace,
8
- fetchIdFromOutput,
9
- fetchSourceCode,
10
- fetchSourceCodeFromStackTrace,
11
- fetchIdFromCode,
12
- humanize
13
- } = require('./utils/utils');
7
+ const {
8
+ fetchFilesFromStackTrace,
9
+ fetchIdFromOutput,
10
+ fetchSourceCode,
11
+ fetchSourceCodeFromStackTrace,
12
+ fetchIdFromCode,
13
+ humanize,
14
+ } = require('./utils/utils');
14
15
  const upload = require('./fileUploader');
15
16
  const pipesFactory = require('./pipe');
16
17
  const adapterFactory = require('./junit-adapter');
18
+ const config = require('./config');
17
19
 
18
- const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
19
- const TESTOMATIO = process.env.TESTOMATIO; // key?
20
+ const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
20
21
  const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
21
22
 
22
23
  const options = {
23
24
  ignoreDeclaration: true,
24
25
  ignoreAttributes: false,
25
26
  alwaysCreateTextNode: false,
26
- attributeNamePrefix: "",
27
+ attributeNamePrefix: '',
27
28
  parseTagValue: true,
28
29
  };
29
30
 
30
31
  const reduceOptions = {};
31
32
 
32
33
  class XmlReader {
33
-
34
34
  constructor(opts = {}) {
35
35
  this.requestParams = {
36
- apiKey: opts.apiKey || TESTOMATIO,
36
+ apiKey: opts.apiKey || config.TESTOMATIO,
37
37
  url: opts.url || TESTOMATIO_URL,
38
38
  title: TESTOMATIO_TITLE,
39
39
  env: TESTOMATIO_ENV,
40
40
  group_title: TESTOMATIO_RUNGROUP_TITLE,
41
41
  };
42
42
  this.runId = opts.runId || TESTOMATIO_RUN;
43
- this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts)
43
+ this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts);
44
44
  if (!this.adapter) throw new Error('XML adapter for this format not found');
45
45
 
46
46
  this.opts = opts || {};
47
- this.store = {}
47
+ this.store = {};
48
48
  this.pipes = pipesFactory(opts, this.store);
49
49
 
50
50
  this.parser = new XMLParser(options);
51
- this.tests = []
52
- this.stats = {}
51
+ this.tests = [];
52
+ this.stats = {};
53
53
  this.stats.language = opts.lang?.toLowerCase();
54
- this.filesToUpload = {}
54
+ this.filesToUpload = {};
55
55
 
56
56
  this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
57
57
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
@@ -82,8 +82,8 @@ class XmlReader {
82
82
  } else if (jsonResult.assemblies) {
83
83
  return this.processXUnit(jsonResult.assemblies);
84
84
  } else {
85
- console.log(jsonResult)
86
- throw new Error("Format can't be parsed")
85
+ console.log(jsonResult);
86
+ throw new Error("Format can't be parsed");
87
87
  }
88
88
 
89
89
  return this.processJUnit(jsonSuite);
@@ -96,7 +96,7 @@ class XmlReader {
96
96
  const resultTests = processTestSuite(testsuite);
97
97
 
98
98
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
99
- const status = (failures > 0 || errors > 0 || hasFailures) ? 'failed' : 'passed';
99
+ const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
100
100
 
101
101
  this.tests = this.tests.concat(resultTests);
102
102
 
@@ -135,21 +135,22 @@ class XmlReader {
135
135
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
136
136
  if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
137
137
 
138
- const tests = defs.map(td => {
139
- const title = td.name.replace(/\(.*?\)/, '').trim();
140
- let example = td.name.match(/\((.*?)\)/);
141
- if (example) example = { ...example[1].split(',') };
142
- const suite = td.TestMethod.className.split(', ')[0].split('.');
143
- const suite_title = suite.pop();
144
- return {
145
- title,
146
- example,
147
- file: suite.join('/'),
148
- description: td.Description,
149
- suite_title,
150
- id: td.Execution.id,
151
- }
152
- }) || [];
138
+ const tests =
139
+ defs.map(td => {
140
+ const title = td.name.replace(/\(.*?\)/, '').trim();
141
+ let example = td.name.match(/\((.*?)\)/);
142
+ if (example) example = { ...example[1].split(',') };
143
+ const suite = td.TestMethod.className.split(', ')[0].split('.');
144
+ const suite_title = suite.pop();
145
+ return {
146
+ title,
147
+ example,
148
+ file: suite.join('/'),
149
+ description: td.Description,
150
+ suite_title,
151
+ id: td.Execution.id,
152
+ };
153
+ }) || [];
153
154
 
154
155
  let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
155
156
  if (!Array.isArray(result)) result = [result].filter(d => !!d);
@@ -160,10 +161,9 @@ class XmlReader {
160
161
  run_time: parseFloat(td.duration) * 1000,
161
162
  status: td.outcome,
162
163
  stack: td.Output.StdOut,
163
- files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
164
+ files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
164
165
  }));
165
166
 
166
-
167
167
  results.forEach(r => {
168
168
  const test = tests.find(t => t.id === r.id) || {};
169
169
  r.suite_title = test.suite_title;
@@ -223,7 +223,7 @@ class XmlReader {
223
223
 
224
224
  if (testCase.failure) {
225
225
  message = testCase.failure.message;
226
- stack = testCase.failure['stack-trace']
226
+ stack = testCase.failure['stack-trace'];
227
227
  }
228
228
  if (testCase.reason) {
229
229
  message = testCase.reason.message;
@@ -250,7 +250,6 @@ class XmlReader {
250
250
  suite_title,
251
251
  run_time,
252
252
  });
253
-
254
253
  });
255
254
  });
256
255
  });
@@ -283,12 +282,12 @@ class XmlReader {
283
282
  passed_count: 0,
284
283
  failed_count: 0,
285
284
  skipped_count: 0,
286
- }
285
+ };
287
286
  this.tests.forEach(t => {
288
287
  this.stats.tests_count++;
289
288
  if (t.status === 'passed') this.stats.passed_count++;
290
289
  if (t.status === 'failed') this.stats.failed_count++;
291
- })
290
+ });
292
291
  if (this.stats.failed_count) this.stats.status = 'failed';
293
292
 
294
293
  return this.stats;
@@ -297,7 +296,7 @@ class XmlReader {
297
296
  fetchSourceCode() {
298
297
  this.tests.forEach(t => {
299
298
  try {
300
- const file = this.adapter.getFilePath(t)
299
+ const file = this.adapter.getFilePath(t);
301
300
  if (!file) return;
302
301
 
303
302
  if (!this.stats.language) {
@@ -310,16 +309,16 @@ class XmlReader {
310
309
  }
311
310
 
312
311
  if (!fs.existsSync(file)) {
313
- debug('Failed to open file with the source code', file)
312
+ debug('Failed to open file with the source code', file);
314
313
  return;
315
314
  }
316
315
  const contents = fs.readFileSync(file).toString();
317
- t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
316
+ t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language });
318
317
  if (t.code) debug('Fetched code for test %s', t.title);
319
- t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language })
318
+ t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language });
320
319
  if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
321
320
  } catch (err) {
322
- debug(err)
321
+ debug(err);
323
322
  }
324
323
  });
325
324
  }
@@ -327,21 +326,22 @@ class XmlReader {
327
326
  formatTests() {
328
327
  this.tests.forEach(t => {
329
328
  if (t.file) {
330
- t.file = t.file.replace(process.cwd() + path.sep, '')
329
+ t.file = t.file.replace(process.cwd() + path.sep, '');
331
330
  }
332
331
 
333
- this.adapter.formatTest(t)
332
+ this.adapter.formatTest(t);
334
333
 
335
334
  t.title = humanize(t.title);
336
335
  });
337
336
  }
338
337
 
339
338
  formatErrors() {
340
- this.tests.filter(t => !!t.stack).forEach(t => {
341
- t.stack = this.formatStack(t)
342
- t.message = this.adapter.formatMessage(t);
343
- });
344
-
339
+ this.tests
340
+ .filter(t => !!t.stack)
341
+ .forEach(t => {
342
+ t.stack = this.formatStack(t);
343
+ t.message = this.adapter.formatMessage(t);
344
+ });
345
345
  }
346
346
 
347
347
  formatStack(t) {
@@ -359,7 +359,7 @@ class XmlReader {
359
359
  async uploadArtifacts() {
360
360
  for (const test of this.tests.filter(t => !!t.stack)) {
361
361
  let files = [];
362
- if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
362
+ if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
363
363
  files = [...files, ...fetchFilesFromStackTrace(test.stack)];
364
364
 
365
365
  if (!files.length) continue;
@@ -378,7 +378,7 @@ class XmlReader {
378
378
  group_title: this.requestParams.group_title,
379
379
  };
380
380
 
381
- debug("Run", runParams);
381
+ debug('Run', runParams);
382
382
 
383
383
  return Promise.all(this.pipes.map(p => p.createRun(runParams)));
384
384
  }
@@ -391,12 +391,10 @@ class XmlReader {
391
391
  this.formatErrors();
392
392
  this.formatTests();
393
393
 
394
- debug(
395
- 'Uploading data',
396
- {
397
- ...this.stats,
398
- tests: this.tests,
399
- })
394
+ debug('Uploading data', {
395
+ ...this.stats,
396
+ tests: this.tests,
397
+ });
400
398
 
401
399
  const dataString = {
402
400
  ...this.stats,
@@ -411,59 +409,62 @@ class XmlReader {
411
409
 
412
410
  module.exports = XmlReader;
413
411
 
414
-
415
412
  function reduceTestCases(prev, item) {
416
413
  let testCases = item.testcase;
417
414
  if (!testCases) testCases = item['test-case'];
418
415
  if (!Array.isArray(testCases)) {
419
- testCases = [testCases]
416
+ testCases = [testCases];
420
417
  }
421
418
  const suiteOutput = item['system-out'] || item.output || item.log || '';
422
419
  const suiteErr = item['system-err'] || item.output || item.log || '';
423
- testCases.filter(t => !!t).forEach(testCaseItem => {
424
- const file = testCaseItem.file || item.filepath || '';
425
-
426
- let stack = '';
427
- let message = '';
428
- if (testCaseItem.error) stack = testCaseItem.error;
429
- if (testCaseItem.failure) stack = testCaseItem.failure;
430
- if (testCaseItem?.failure?.['stack-trace']) stack = testCaseItem.failure['stack-trace'];
431
- if (testCaseItem?.failure?.message) message = testCaseItem.failure.message;
432
- if (testCaseItem?.error?.message) message = testCaseItem.error.message;
433
-
434
- if (testCaseItem.failure && testCaseItem.failure['#text']) stack = testCaseItem.failure['#text'];
435
- if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
436
- if (!message) message = stack.trim().split('\n')[0];
437
-
438
- // eslint-disable-next-line
439
- stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim()
440
- const testId = fetchIdFromOutput(stack);
441
-
442
- let status = STATUS.PASSED.toString();
443
- if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
444
- if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
445
-
446
- prev.push({
447
- create: true,
448
- file,
449
- stack,
450
- test_id: testId,
451
- message,
452
- line: testCaseItem.lineno,
453
- // seconds are used in junit reports, but ms are used by testomatio
454
- run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
455
- status,
456
- title: testCaseItem.name,
457
- suite_title: reduceOptions.preferClassname ? testCaseItem.classname : (item.name || testCaseItem.classname),
458
- })
459
- });
420
+ testCases
421
+ .filter(t => !!t)
422
+ .forEach(testCaseItem => {
423
+ const file = testCaseItem.file || item.filepath || '';
424
+
425
+ let stack = '';
426
+ let message = '';
427
+ if (testCaseItem.error) stack = testCaseItem.error;
428
+ if (testCaseItem.failure) stack = testCaseItem.failure;
429
+ if (testCaseItem?.failure?.['stack-trace']) stack = testCaseItem.failure['stack-trace'];
430
+ if (testCaseItem?.failure?.message) message = testCaseItem.failure.message;
431
+ if (testCaseItem?.error?.message) message = testCaseItem.error.message;
432
+
433
+ if (testCaseItem.failure && testCaseItem.failure['#text']) stack = testCaseItem.failure['#text'];
434
+ if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
435
+ if (!message) message = stack.trim().split('\n')[0];
436
+
437
+ // eslint-disable-next-line
438
+ stack = `${
439
+ testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
440
+ }\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
441
+ const testId = fetchIdFromOutput(stack);
442
+
443
+ let status = STATUS.PASSED.toString();
444
+ if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
445
+ if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
446
+
447
+ prev.push({
448
+ create: true,
449
+ file,
450
+ stack,
451
+ test_id: testId,
452
+ message,
453
+ line: testCaseItem.lineno,
454
+ // seconds are used in junit reports, but ms are used by testomatio
455
+ run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
456
+ status,
457
+ title: testCaseItem.name,
458
+ suite_title: reduceOptions.preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname,
459
+ });
460
+ });
460
461
  return prev;
461
462
  }
462
463
 
463
464
  function processTestSuite(testsuite) {
464
465
  if (!testsuite) return [];
465
- if (testsuite.testsuite) return processTestSuite(testsuite.testsuite)
466
- if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite'])
466
+ if (testsuite.testsuite) return processTestSuite(testsuite.testsuite);
467
+ if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite']);
467
468
 
468
469
  let suites = testsuite;
469
470
  if (!Array.isArray(testsuite)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.2.0-beta-4",
3
+ "version": "1.2.0",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -12,7 +12,8 @@
12
12
  "@aws-sdk/lib-storage": "^3.279.0",
13
13
  "@octokit/rest": "^19.0.5",
14
14
  "aws-sdk": "^2.1072.0",
15
- "axios": "^0.25.0",
15
+ "axios": "^1.6.2",
16
+ "axios-retry": "^3.9.1",
16
17
  "callsite-record": "^4.1.4",
17
18
  "chalk": "^4.1.0",
18
19
  "commander": "^4.1.1",
@@ -30,6 +31,7 @@
30
31
  "lodash.memoize": "^4.1.2",
31
32
  "lodash.merge": "^4.6.2",
32
33
  "minimatch": "^9.0.3",
34
+ "promise-retry": "^2.0.1",
33
35
  "uuid": "^9.0.0"
34
36
  },
35
37
  "files": [
@@ -51,7 +53,7 @@
51
53
  "test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
52
54
  "test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
53
55
  "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
54
- "test:storage": "npx mocha ./tests-storage/**"
56
+ "test:storage": "npx mocha tests-storage/artifact-storage.test.js && npx mocha tests-storage/data-storage.test.js && TESTOMATIO_INTERCEPT_CONSOLE_LOGS=true npx mocha tests-storage/logger.test.js && npx mocha tests-storage/logger-2.test.js && npx mocha tests-storage/reporter-functions.test.js"
55
57
  },
56
58
  "devDependencies": {
57
59
  "@cucumber/cucumber": "^9.3.0",
@@ -1,142 +0,0 @@
1
- const debug = require('debug')('@testomatio/reporter:storage');
2
- const { join, resolve } = require('path');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const uuid = require('uuid');
6
- const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
7
- const { specificTestInfo } = require('./utils/utils');
8
-
9
- class ArtifactStorage {
10
-
11
- constructor(params) {
12
- this.isFile = false;
13
- this.isMemory = true;
14
- this.storage = params?.toFile;
15
-
16
- if (this.storage) {
17
- // this is fine to use when you start saving artifacts;
18
- // but when you need to retrieve them, you will not know if there is "isFile" param,
19
- // because you need to create a new instance of ArtifactStorage inside client
20
- // so the solution is make it opposite to isMemory (which will be retrieved from global)
21
- this.isFile = true;
22
- this.isMemory = false;
23
- this.tmpDirFullpath = this.createTestomatTmpDir(ArtifactStorage._tmpPrefix);
24
- debug('SAVE to tmp folder mode enabled!');
25
- }
26
- }
27
-
28
- static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
29
- // this assumes to use multiple files for each test. do we really want this?
30
- const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
31
- const dirpath = join(os.tmpdir(), tmpDirName);
32
- // why json?
33
- const filepath = resolve(dirpath, `${suffix}.json`);
34
-
35
- return fs.promises.appendFile(filepath, JSON.stringify(artifact));
36
- }
37
-
38
- static async artifact(artifact, context) {
39
- // TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
40
- // const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
41
-
42
- // you save the artifact without specifying its source - test id
43
- if (Array.isArray(global.testomatioArtifacts)) {
44
- debug("Saving artifacts to global storage");
45
-
46
- global.testomatioArtifacts.push(artifact);
47
- }
48
-
49
- if (global?.testomatioArtifacts === undefined) {
50
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
51
-
52
- const testSuffix = specificTestInfo(context.test);
53
-
54
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
55
- debug("Saving artifacts to memory tmp folder");
56
-
57
- return ArtifactStorage.storeToFile(tmpDirNames[tmpDirNames.length - 1], artifact, testSuffix);
58
- }
59
- }
60
- }
61
-
62
- async artifactByTestName(test) {
63
- const list = [];
64
-
65
- if (this.isFile && this.tmpDirFullpath) {
66
- const files = fs.readdirSync(this.tmpDirFullpath);
67
-
68
- for (const file of files) {
69
- if (file.includes(test)) {
70
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
71
-
72
- list.push(JSON.parse(buff.toString()));
73
- }
74
- }
75
- }
76
-
77
- return list;
78
- }
79
-
80
- // returns all the content from all files; do we really need it?
81
- async tmpContents() {
82
- const contents = [];
83
-
84
- if (this.isFile && this.tmpDirFullpath) {
85
- const files = fs.readdirSync(this.tmpDirFullpath);
86
-
87
- for (const file of files) {
88
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
89
- const content = buff.toString();
90
-
91
- contents.push(content);
92
- }
93
- }
94
-
95
- return contents;
96
- }
97
-
98
- createTestomatTmpDir(clientPrefix) {
99
- return fs.mkdtempSync(join(os.tmpdir(), clientPrefix));
100
- }
101
-
102
- clearTmpDirByName(name) {
103
- const tmpDirPath = join(os.tmpdir(), name);
104
-
105
- if (fs.existsSync(tmpDirPath)) {
106
- fs.rmSync(tmpDirPath, { recursive: true });
107
- debug(` Testomat tmpDir = ${tmpDirPath} was deleted successfully!`);
108
-
109
-
110
- }
111
- }
112
-
113
- // no need to use multiple dirs; we can implement it later if required
114
- static tmpTestomatDirNames() {
115
- const subname = ArtifactStorage._tmpPrefix || "tsmt_reporter";
116
-
117
- return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
118
- .filter((item) => item.isDirectory())
119
- .map((item) => item.name)
120
- .filter((name) => name.includes(subname));
121
- }
122
-
123
- cleanup() {
124
- // when you do a cleanup, I know nothing about isFile param
125
- if (this.isFile && this.tmpDirFullpath) {
126
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
127
-
128
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
129
- for (const name of tmpDirNames) {
130
- this.clearTmpDirByName(name);
131
- }
132
- }
133
- }
134
- else {
135
- debug("The tmp folder has not been created! Nothing to delete");
136
- }
137
- }
138
- }
139
-
140
- ArtifactStorage._tmpPrefix = "tsmt_reporter";
141
-
142
- module.exports = ArtifactStorage;
@@ -1,25 +0,0 @@
1
- // const debug = require('debug')('@testomatio/reporter:logger');
2
- const { DataStorage } = require('./dataStorage');
3
-
4
- class ArtifactStorage {
5
- constructor() {
6
- this.dataStorage = new DataStorage('artifact');
7
-
8
- // singleton
9
- if (!ArtifactStorage.instance) {
10
- ArtifactStorage.instance = this;
11
- }
12
- }
13
-
14
- save(data, context = null) {
15
- this.dataStorage.putData(data, context);
16
- }
17
-
18
- get(context) {
19
- this.dataStorage.getData(context);
20
- }
21
- }
22
-
23
- ArtifactStorage.instance = null;
24
-
25
- module.exports = new ArtifactStorage();