Enabled multi-part images; added debug/info logging; new tests; split out image.ts from qr.ts
This commit is contained in:
Родитель
caba20fe3a
Коммит
1c8cf8f733
|
@ -67,7 +67,7 @@ Issuer signing keys can be validated before being uploaded to their well-known U
|
|||
The tool currently verifies proper encoding of the:
|
||||
- QR code image
|
||||
- Numeric QR data (header, content)
|
||||
- Smart Health Card file (schema)
|
||||
- SMART Health Card file (schema)
|
||||
- JWS (schema, deflate compression, format, size limits, signature, issuer key retrieval)
|
||||
- JWS payload (schema)
|
||||
- FHIR bundle (schema)
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -24,8 +24,10 @@
|
|||
"commander": "^7.1.0",
|
||||
"execa": "^5.0.0",
|
||||
"file-type": "^16.2.0",
|
||||
"gm": "^1.23.1",
|
||||
"got": "^11.8.2",
|
||||
"istextorbinary": "^5.12.0",
|
||||
"jimp": "^0.16.1",
|
||||
"jpeg-js": "^0.4.2",
|
||||
"jsqr": "^1.3.1",
|
||||
"node-jose": "^2.0.0",
|
||||
|
@ -35,6 +37,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/bmp-js": "^0.1.0",
|
||||
"@types/gm": "^1.18.9",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node": "^14.14.31",
|
||||
"@types/node-jose": "^1.1.5",
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import jose, { JWK } from 'node-jose';
|
||||
import svg2img from 'svg2img';
|
||||
import Jimp from 'jimp';
|
||||
|
||||
interface KeyGenerationArgs {
|
||||
kty: string;
|
||||
|
@ -47,4 +49,45 @@ generateAndStoreKey('wrong_kty_key.json', { kty: 'RSA', size: 2048 });
|
|||
generateAndStoreKey('missing_kid_key.json', { kty: 'EC', size: 'P-256', props: { alg: 'ES256', crv: 'P-256', use: 'sig' } });
|
||||
|
||||
|
||||
// TODO: generate files with missing algs, once omit is implemented
|
||||
function svgToImage(filePath: string): Promise<unknown> {
|
||||
|
||||
const baseFileName = filePath.slice(0, filePath.lastIndexOf('.'));
|
||||
|
||||
return new
|
||||
Promise<Buffer>((resolve, reject) => {
|
||||
svg2img(filePath, { width: 600, height: 600 },
|
||||
(error: unknown, buffer: Buffer) => {
|
||||
error ? reject("Could not create image from svg") : resolve(buffer);
|
||||
});
|
||||
})
|
||||
.then((buffer) => {
|
||||
fs.writeFileSync(baseFileName + '.png', buffer);
|
||||
return Jimp.read(baseFileName + '.png');
|
||||
})
|
||||
.then(png => {
|
||||
return Promise.all([
|
||||
png.write(baseFileName + '.bmp'),
|
||||
png.grayscale().quality(100).write(baseFileName + '.jpg')
|
||||
]);
|
||||
})
|
||||
.catch(err => { console.error(err); });
|
||||
}
|
||||
|
||||
|
||||
async function generateImagesFromSvg(dir: string) {
|
||||
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = path.join(dir, files[i]);
|
||||
if (path.extname(file) === '.svg') {
|
||||
await svgToImage(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: generate files with missing algs, once omit is implemented
|
||||
|
||||
void generateImagesFromSvg(outdir);
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import svg2img from 'svg2img'; // svg files to image buffer
|
||||
import jsQR from 'jsqr'; // qr image decoder
|
||||
import { ErrorCode } from './error';
|
||||
import Log from './logger';
|
||||
import { FileInfo } from './file';
|
||||
import * as qr from './qr';
|
||||
import { PNG } from 'pngjs';
|
||||
import fs from 'fs';
|
||||
|
||||
|
||||
export async function validate(images: FileInfo[]): Promise<{ result: JWS | undefined, log: Log }> {
|
||||
|
||||
const log = new Log(
|
||||
images.length > 1 ?
|
||||
'QR images (' + images.length.toString() + ')' :
|
||||
'QR image');
|
||||
|
||||
|
||||
const shcStrings : SHC[] = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const shc = await decode(images[i], log);
|
||||
if(shc === undefined) return {result: undefined, log: log};
|
||||
shcStrings.push(shc);
|
||||
log.info(images[i].name + " decoded");
|
||||
log.debug(images[i].name + ' = ' + shc);
|
||||
}
|
||||
|
||||
|
||||
log.child = (await qr.validate(shcStrings)).log;
|
||||
|
||||
|
||||
return { result: JSON.stringify(shcStrings), log: log };
|
||||
}
|
||||
|
||||
|
||||
// takes file path to QR data and returns base64 data
|
||||
async function decode(fileInfo: FileInfo, log: Log): Promise<string | undefined> {
|
||||
|
||||
let svgBuffer;
|
||||
|
||||
switch (fileInfo.fileType) {
|
||||
|
||||
case 'svg':
|
||||
svgBuffer = await svgToImageBuffer(fileInfo.buffer.toString(), log);
|
||||
fileInfo.image = PNG.sync.read(svgBuffer);
|
||||
fs.writeFileSync(fileInfo.path + '.png', svgBuffer);
|
||||
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'png':
|
||||
case 'jpg':
|
||||
case 'bmp':
|
||||
return Promise.resolve(decodeQrBuffer(fileInfo, log));
|
||||
|
||||
default:
|
||||
log.fatal("Unknown data in file", ErrorCode.UNKNOWN_FILE_DATA);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// the svg data is turned into an image buffer. these values ensure that the resulting image is readable
|
||||
// by the QR image decoder. 300x300 fails while 400x400 suceedeeds
|
||||
const svgImageWidth = 600;
|
||||
|
||||
|
||||
// Converts a SVG file into a QR image buffer (as if read from a image file)
|
||||
async function svgToImageBuffer(svgPath: string, log: Log): Promise<Buffer> {
|
||||
|
||||
// TODO: create a test that causes failure here
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
svg2img(svgPath, { width: svgImageWidth, height: svgImageWidth },
|
||||
(error: unknown, buffer: Buffer) => {
|
||||
if (error) {
|
||||
log.fatal("Could not convert SVG to image. Error: " + (error as Error).message);
|
||||
reject(undefined);
|
||||
}
|
||||
resolve(buffer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Decode QR image buffer to base64 string
|
||||
function decodeQrBuffer(fileInfo: FileInfo, log: Log): string | undefined {
|
||||
|
||||
const result: JWS | undefined = undefined;
|
||||
|
||||
//const png = PNG.sync.read(image);
|
||||
const data = fileInfo.image;
|
||||
|
||||
if(!data) {
|
||||
log.fatal('Could not read image data from : ' + fileInfo.name);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO : create a test that causes failure here
|
||||
const code = jsQR(new Uint8ClampedArray(data.data.buffer), data.width, data.height);
|
||||
|
||||
if (code == null) {
|
||||
log.fatal('Could not decode QR image from : ' + fileInfo.name, ErrorCode.QR_DECODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
return code.data;
|
||||
}
|
|
@ -50,6 +50,9 @@ export async function validate(jws: JWS): Promise<ValidationResult> {
|
|||
const rawPayload = parts[1];
|
||||
|
||||
|
||||
log.debug('JWS.header = ' + Buffer.from(parts[0], 'base64').toString());
|
||||
log.debug('JWS.key (hex) = ' + Buffer.from(parts[2], 'binary').toString('hex'));
|
||||
|
||||
let inflatedPayload;
|
||||
try {
|
||||
inflatedPayload = pako.inflateRaw(Buffer.from(rawPayload, 'base64'), { to: 'string' });
|
||||
|
@ -107,7 +110,7 @@ async function downloadKey(keyPath: string, log: Log): Promise<JWK.Key[] | undef
|
|||
return await got(keyPath).json<{ keys: unknown[] }>()
|
||||
// TODO: split up download/parsing to provide finer-grainded error message
|
||||
.then(async keysObj => {
|
||||
log.debug("Downloaded issuer key : " + JSON.stringify(keysObj));
|
||||
log.debug("Downloaded issuer key : " + JSON.stringify(keysObj, null, 2));
|
||||
return [
|
||||
await keys.store.add(JSON.stringify(keysObj.keys[0]), 'json'),
|
||||
await keys.store.add(JSON.stringify(keysObj.keys[1]), 'json')
|
||||
|
|
115
src/qr.ts
115
src/qr.ts
|
@ -1,69 +1,23 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import svg2img from 'svg2img'; // svg files to image buffer
|
||||
import { PNG } from 'pngjs'; // png image file reader
|
||||
import jsQR from 'jsqr'; // qr image decoder
|
||||
import { ErrorCode } from './error';
|
||||
import * as jws from './jws-compact';
|
||||
import Log from './logger';
|
||||
import { FileInfo } from './file';
|
||||
|
||||
|
||||
export async function validate(qr: FileInfo[]): Promise<{ result: JWS | undefined, log: Log }> {
|
||||
export async function validate(qr: string[]): Promise<{ result: JWS | undefined, log: Log }> {
|
||||
|
||||
const log = new Log('QR code (' + (qr[0].fileType as string) + ')');
|
||||
const log = new Log(
|
||||
qr.length > 1 ?
|
||||
'QR numeric (' + qr.length.toString() + ')' :
|
||||
'QR numeric');
|
||||
|
||||
const results: JWS | undefined = await decode(qr, log);
|
||||
const jwsString: JWS | undefined = shcChunksToJws(qr, log); //await decode(qr, log);
|
||||
|
||||
results && await jws.validate(results);
|
||||
jwsString && (log.child = (await jws.validate(jwsString)).log);
|
||||
|
||||
return { result: results, log: log };
|
||||
}
|
||||
|
||||
|
||||
// the svg data is turned into an image buffer. these values ensure that the resulting image is readable
|
||||
// by the QR image decoder.
|
||||
const svgImageWidth = 600;
|
||||
const svgImageHeight = 600;
|
||||
const svgImageQuality = 100;
|
||||
|
||||
|
||||
// TODO: find minimal values that cause the resulting image to fail decoding.
|
||||
|
||||
// Converts a SVG file into a QR image buffer (as if read from a image file)
|
||||
async function svgToImageBuffer(svgPath: string, log: Log): Promise<Buffer> {
|
||||
|
||||
// TODO: create a test that causes failure here
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
svg2img(svgPath, { width: svgImageWidth, height: svgImageHeight, quality: svgImageQuality },
|
||||
(error: unknown, buffer: Buffer) => {
|
||||
if (error) {
|
||||
log.fatal("Could not convert SVG to image. Error: " + (error as Error).message);
|
||||
reject(undefined);
|
||||
}
|
||||
resolve(buffer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Decode QR image buffer to base64 string
|
||||
function decodeQrBuffer(image: Buffer, log: Log): string | undefined {
|
||||
|
||||
const result: JWS | undefined = undefined;
|
||||
|
||||
const png = PNG.sync.read(image);
|
||||
|
||||
// TODO : create a test that causes failure here
|
||||
const code = jsQR(new Uint8ClampedArray(png.data.buffer), png.width, png.height);
|
||||
|
||||
if (code == null) {
|
||||
log.fatal("Could not decode QR image.", ErrorCode.QR_DECODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
return code.data;
|
||||
return { result: jwsString, log: log };
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,16 +30,8 @@ function shcChunksToJws(shc: string[], log : Log): JWS | undefined {
|
|||
|
||||
const chunkResult = shcToJws(shcChunk, log, chunkCount);
|
||||
|
||||
|
||||
if(!chunkResult) continue; // move on to next chunk
|
||||
|
||||
// if (chunkResult.errors.length > 0) {
|
||||
// // propagate errors, if any
|
||||
// for (let err of chunkResult.errors) {
|
||||
// result.error(err.message, err.code, err.logLevel); // TODO: overload this method to take a LogInfo
|
||||
// }
|
||||
// continue; // move on to next chunk
|
||||
// }
|
||||
// bad header is fatal (according to tests)
|
||||
if(!chunkResult) return undefined; // move on to next chunk
|
||||
|
||||
const chunkIndex = chunkResult.chunkIndex;
|
||||
|
||||
|
@ -105,6 +51,10 @@ function shcChunksToJws(shc: string[], log : Log): JWS | undefined {
|
|||
}
|
||||
}
|
||||
|
||||
if(shc.length > 1) log.info('All shc parts decoded');
|
||||
|
||||
log.debug('JWS = ' + jwsChunks.join(''));
|
||||
|
||||
return jwsChunks.join('');
|
||||
}
|
||||
|
||||
|
@ -155,39 +105,8 @@ function shcToJws(shc: string, log: Log, chunkCount = 1): {result: JWS, chunkInd
|
|||
// merge the array into a single base64 string
|
||||
.join('');
|
||||
|
||||
log.info( shc.slice(0, shc.lastIndexOf('/')) + '/... decoded');
|
||||
log.debug( shc.slice(0, shc.lastIndexOf('/')) + '/... = ' + jws);
|
||||
|
||||
return { result: jws, chunkIndex : chunkIndex};
|
||||
}
|
||||
|
||||
|
||||
// takes file path to QR data and returns base64 data
|
||||
async function decode(fileInfo: FileInfo[], log: Log): Promise<string | undefined> {
|
||||
|
||||
let svgBuffer;
|
||||
//const result = new ResultWithErrors();
|
||||
|
||||
switch (fileInfo[0].fileType) { // TODO: how to deal with different inconsistent files
|
||||
|
||||
case 'svg':
|
||||
svgBuffer = await svgToImageBuffer(fileInfo[0].buffer.toString(), log); // TODO: handle multiple files
|
||||
return decodeQrBuffer(svgBuffer, log);
|
||||
|
||||
case 'shc':
|
||||
return Promise.resolve(shcChunksToJws(fileInfo.map(fi => fi.buffer.toString()), log));
|
||||
|
||||
case 'png':
|
||||
return decodeQrBuffer(fileInfo[0].buffer, log); // TODO: handle multiple files
|
||||
|
||||
case 'jpg':
|
||||
log.fatal("jpg : Not implemented", ErrorCode.NOT_IMPLEMENTED);
|
||||
return undefined;
|
||||
|
||||
case 'bmp':
|
||||
log.fatal("bmp : Not implemented", ErrorCode.NOT_IMPLEMENTED);
|
||||
return undefined;
|
||||
|
||||
default:
|
||||
log.fatal("Unknown data in file", ErrorCode.UNKNOWN_FILE_DATA);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ async function processOptions() {
|
|||
try {
|
||||
fileData.push(await getFileData(path));
|
||||
} catch (error) {
|
||||
log.error((error as Error).message);
|
||||
console.log((error as Error).message);
|
||||
process.exitCode = ErrorCode.DATA_FILE_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ async function processOptions() {
|
|||
if (logFilePathIsValid) {
|
||||
output.log.toFile(options.logout, options, true);
|
||||
} else {
|
||||
console.log(log.toString(level));
|
||||
console.log(output.log.toString(level));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
type JWS = string;
|
||||
|
||||
type SHC = string;
|
||||
|
||||
interface HealthCard {
|
||||
"verifiableCredential": JWS[]
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as jws from './jws-compact';
|
|||
import * as jwsPayload from './jws-payload';
|
||||
import * as fhirBundle from './fhirBundle';
|
||||
import * as qr from './qr';
|
||||
|
||||
import * as image from './image';
|
||||
|
||||
|
||||
|
||||
|
@ -51,11 +51,11 @@ export async function validateCard(fileData: FileInfo[], type: ValidationType):
|
|||
switch (type.toLocaleLowerCase()) {
|
||||
|
||||
case "qr":
|
||||
result = await qr.validate(fileData);
|
||||
result = await image.validate(fileData);
|
||||
break;
|
||||
|
||||
case "qrnumeric":
|
||||
result = await qr.validate(fileData);
|
||||
result = await qr.validate(fileData.map((fi)=>fi.buffer.toString('utf-8')));
|
||||
break;
|
||||
|
||||
case "healthcard":
|
||||
|
|
|
@ -11,14 +11,9 @@ import { LogLevels } from '../src/logger';
|
|||
|
||||
const testdataDir = './testdata/';
|
||||
|
||||
// async function testCard(fileName: string[], fileType: ValidationType = 'healthcard', levels: LogLevels[] = [LogLevels.ERROR, LogLevels.FATAL]): Promise<{ title: string, message: string, code: ErrorCode }[]> {
|
||||
// const filePath = path.join(testdataDir, fileName);
|
||||
// const log = (await validateCard([await getFileData(filePath)], fileType)).log;
|
||||
// return log.flatten().filter(i => { return levels.includes(i.level); });
|
||||
// }
|
||||
|
||||
|
||||
async function testCard(fileName: string[], fileType: ValidationType = 'healthcard', levels : LogLevels[] = [LogLevels.ERROR, LogLevels.FATAL]): Promise<{ title: string, message: string, code: ErrorCode }[]> {
|
||||
async function testCard(fileName: string | string[], fileType: ValidationType = 'healthcard', levels: LogLevels[] = [LogLevels.ERROR, LogLevels.FATAL]): Promise<{ title: string, message: string, code: ErrorCode }[]> {
|
||||
if (typeof fileName === 'string') fileName = [fileName];
|
||||
const files = [];
|
||||
for (const fn of fileName) { // TODO: I tried a map here, but TS didn't like the async callback
|
||||
files.push(await getFileData(path.join(testdataDir, fn)));
|
||||
|
@ -52,17 +47,23 @@ test("Cards: valid 00 QR numeric", async () => expect(await testCard(['example-0
|
|||
test("Cards: valid 01 QR numeric", async () => expect(await testCard(['example-01-f-qr-code-numeric-value-0.txt'], "qrnumeric")).toHaveLength(0));
|
||||
test("Cards: valid 02 QR numeric", async () => expect(
|
||||
await testCard(['example-02-f-qr-code-numeric-value-0.txt',
|
||||
'example-02-f-qr-code-numeric-value-1.txt',
|
||||
'example-02-f-qr-code-numeric-value-2.txt'], "qrnumeric")).toHaveLength(0));
|
||||
'example-02-f-qr-code-numeric-value-1.txt',
|
||||
'example-02-f-qr-code-numeric-value-2.txt'], "qrnumeric")).toHaveLength(0));
|
||||
|
||||
test("Cards: valid 00 QR code", async () => expect(await testCard(['example-00-g-qr-code-0.svg'], "qr")).toHaveLength(0));
|
||||
test("Cards: valid 01 QR code", async () => expect(await testCard(['example-01-g-qr-code-0.svg'], "qr")).toHaveLength(0));
|
||||
/* TODO: enable once QR chunk image parsing works
|
||||
|
||||
test("Cards: valid 02 QR code", async () => expect(
|
||||
await testCard(['example-02-g-qr-code-0.svg',
|
||||
'example-02-g-qr-code-1.svg',
|
||||
'example-02-g-qr-code-2.svg'], "qr")).toHaveLength(0));
|
||||
*/
|
||||
await testCard(['example-02-g-qr-code-0.svg', 'example-02-g-qr-code-1.svg', 'example-02-g-qr-code-2.svg'], "qr")).toHaveLength(0));
|
||||
|
||||
test("Cards: valid 02 QR code PNG", async () => expect(
|
||||
await testCard(['example-02-g-qr-code-0.png', 'example-02-g-qr-code-1.png', 'example-02-g-qr-code-2.png'], "qr")).toHaveLength(0));
|
||||
|
||||
test("Cards: valid 02 QR code JPG", async () => expect(
|
||||
await testCard(['example-02-g-qr-code-0.jpg', 'example-02-g-qr-code-1.jpg', 'example-02-g-qr-code-2.jpg'], "qr")).toHaveLength(0));
|
||||
|
||||
test("Cards: valid 02 QR code BMP", async () => expect(
|
||||
await testCard(['example-02-g-qr-code-0.bmp', 'example-02-g-qr-code-1.bmp', 'example-02-g-qr-code-2.bmp'], "qr")).toHaveLength(0));
|
||||
|
||||
test("Cards: invalid deflate", async () => {
|
||||
const results = await testCard(['test-example-00-e-file-invalid_deflate.smart-health-card']);
|
||||
|
@ -74,7 +75,7 @@ test("Cards: invalid deflate", async () => {
|
|||
test("Cards: no deflate", async () => {
|
||||
const results = await testCard(['test-example-00-e-file-no_deflate.smart-health-card']);
|
||||
expect(results).toHaveLength(2);
|
||||
// expect(results[0].code).toBe(ErrorCode.JWS_TOO_LONG); // FIXME: fix for chunk
|
||||
// expect(results[0].code).toBe(ErrorCode.JWS_TOO_LONG); // FIXME: fix for chunk
|
||||
expect(results[0].code).toBe(ErrorCode.INFLATION_ERROR);
|
||||
expect(results[1].code).toBe(ErrorCode.JSON_PARSE_ERROR);
|
||||
});
|
||||
|
@ -94,10 +95,9 @@ test("Cards: invalid QR mode", async () => {
|
|||
*/
|
||||
|
||||
test("Cards: invalid QR header", async () => {
|
||||
const results = await testCard(['test-example-00-f-qr-code-numeric-wrong_qr_header.txt'], 'qr');
|
||||
expect(results).toHaveLength(2);
|
||||
const results = await testCard(['test-example-00-f-qr-code-numeric-wrong_qr_header.txt'], 'qrnumeric');
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].code).toBe(ErrorCode.INVALID_NUMERIC_QR_HEADER);
|
||||
expect(results[1].code).toBe(ErrorCode.JSON_PARSE_ERROR); // FIXME: this shouldn't be returned, we should stop after QR failure
|
||||
});
|
||||
|
||||
/* TODO: FIX this test
|
||||
|
|
|
@ -106,10 +106,13 @@ test("Cards: valid 02 jws", () => expect(testCliCommand('node . --path testdata/
|
|||
test("Cards: valid 02 jws-payload", () => expect(testCliCommand('node . --path testdata/example-02-c-jws-payload-minified.json --type jwspayload --loglevel warning')).toBe(0));
|
||||
test("Cards: valid 02 fhirBundle", () => expect(testCliCommand('node . --path testdata/example-02-a-fhirBundle.json --type fhirbundle --loglevel warning')).toBe(0));
|
||||
test("Cards: valid 02 qr-code-numeric", () => expect(testCliCommand('node . --path testdata/example-02-f-qr-code-numeric-value-0.txt --path testdata/example-02-f-qr-code-numeric-value-1.txt --path testdata/example-02-f-qr-code-numeric-value-2.txt --type qrnumeric --loglevel info')).toBe(0));
|
||||
test("Cards: valid 02 qr-code.svg", () => expect(testCliCommand('node . --path testdata/example-02-g-qr-code-0.svg --path testdata/example-02-g-qr-code-0.svg --path testdata/example-02-g-qr-code-0.svg --type qr --loglevel info')).toBe(0));
|
||||
test("Cards: valid 02 qr-code.svg", () => expect(testCliCommand('node . --path testdata/example-02-g-qr-code-0.svg --path testdata/example-02-g-qr-code-1.svg --path testdata/example-02-g-qr-code-2.svg --type qr --loglevel info')).toBe(0));
|
||||
test("Cards: valid 02 qr-code.png", () => expect(testCliCommand('node . --path testdata/example-02-g-qr-code-0.png --path testdata/example-02-g-qr-code-1.png --path testdata/example-02-g-qr-code-2.png --type qr --loglevel info')).toBe(0));
|
||||
test("Cards: valid 02 qr-code.jpg", () => expect(testCliCommand('node . --path testdata/example-02-g-qr-code-0.jpg --path testdata/example-02-g-qr-code-1.jpg --path testdata/example-02-g-qr-code-2.jpg --type qr --loglevel info')).toBe(0));
|
||||
test("Cards: valid 02 qr-code.bmp", () => expect(testCliCommand('node . --path testdata/example-02-g-qr-code-0.bmp --path testdata/example-02-g-qr-code-1.bmp --path testdata/example-02-g-qr-code-2.bmp --type qr --loglevel info')).toBe(0));
|
||||
|
||||
|
||||
|
||||
test("Cards: valid qr.png", () => expect(testCliCommand('node . --path testdata/qr.png --type qr --loglevel info')).toBe(0));
|
||||
test("Cards: valid qr-90.pngd", () => expect(testCliCommand('node . --path testdata/qr-90.png --type qr --loglevel info')).toBe(0));
|
||||
|
||||
// Bad paths to data files
|
||||
test("Cards: missing healthcard", () => expect(testCliCommand('node . --path bogus-path/bogus-file.json --type healthcard --loglevel info')).toBe(ErrorCode.DATA_FILE_NOT_FOUND));
|
||||
|
|
Загрузка…
Ссылка в новой задаче