diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index eb4d9d5..1d78835 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x, 18.x] + node-version: [16.x, 18.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9f7ec86 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +src/tinj.js +.github/ +*.json +*.md diff --git a/index.js b/index.js index 133dd3b..ec43d39 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,6 @@ -import hash from "./src/hash"; +import { tinj } from "./src/tinj.js"; +import hash from "./src/hash.js"; export default hash; +// hash.setHmacKey("hashscripttest"); +// console.log(hash.hash("test", "test")); diff --git a/package-lock.json b/package-lock.json index dbcee5c..fffb48b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,58 +1,97 @@ { "name": "hashscript", "version": "0.0.1", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "dequal": { + "packages": { + "": { + "name": "hashscript", + "version": "0.0.1", + "license": "AGPL-3.0", + "devDependencies": { + "prettier": "^3.0.0-alpha.6", + "uvu": "^0.5.6" + } + }, + "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "diff": { + "node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.3.1" + } }, - "kleur": { + "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "mri": { + "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true + "node_modules/prettier": { + "version": "3.0.0-alpha.9-for-vscode", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.9-for-vscode.tgz", + "integrity": "sha512-1PRIN3j5DKFi/BGBYB6aErxkq76ml2n1AHPmwrqh/bCiBkmXio5eFTrNMpDls/529r+8TSzVLQMhDFzrwMA2ag==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } }, - "sade": { + "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, - "requires": { + "dependencies": { "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" } }, - "uvu": { + "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dev": true, - "requires": { + "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" } } } diff --git a/package.json b/package.json index 624ebc0..35c3aff 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "test": "node src/hash.test.js", - "format:prettier": "prettier \"**/*.js\" --write --ignore-path .gitignore" + "format:prettier": "prettier \"**/*.js\" --ignore-path .prettierignore --ignore-path .gitignore --write ." }, "repository": { "type": "git", @@ -21,7 +21,11 @@ }, "homepage": "https://github.com/code-jammers/hashscript#readme", "devDependencies": { - "prettier": "^2.7.1", + "prettier": "^3.0.0-alpha.6", + "uvu": "^0.5.6" + }, + "dependencies": { + "prettier": "^3.0.0-alpha.6", "uvu": "^0.5.6" }, "type": "module" diff --git a/src/hash.js b/src/hash.js index 048145e..8f13646 100644 --- a/src/hash.js +++ b/src/hash.js @@ -4,13 +4,13 @@ const hash = { setHmacKey: function (key) { hash.key = key; }, - hash: function (head, text) { + hash: /*tinj0*/ function (head, text) { if (hash.key == null) { console.warn( - "WARNING: no hmac given to setHmac function, using empty string." + "WARNING: no hmac given to setHmac function, using empty string.", ); } - return digest(hash?.key ?? "", head + text); + return tinj1 ?? digest(hash?.key ?? "", head + text); }, hashToInt: function (head, text, digits) { // why was 13 chosen? @@ -25,9 +25,9 @@ const hash = { "" + parseInt( hash.hash(head, text).substring(0, /*HASH_SUBSTRS_FIX:*/ 13), - 16 + 16, ) - ).substring(0, digits) + ).substring(0, digits), ); }, // PLURAL DIGITS @@ -51,8 +51,8 @@ const hash = { hashInferPluralDigitCsv: function (head, val) { // HASH_NODIGITS_FIX - var varCount = val.match(/\$[A-Z]/g)?.length; - if (varCount == null) varCount = 13; + var varCount = tinj4 ?? val.match(/\$[A-Z]/g)?.length ?? 13; + // if (varCount == null) varCount = 13; return hash.hashToPluralDigitCsv(head, val, varCount); }, }; diff --git a/src/hash.test.js b/src/hash.test.js index 331337a..aa22203 100644 --- a/src/hash.test.js +++ b/src/hash.test.js @@ -1,20 +1,36 @@ -import { test } from "uvu"; +import { test, suite } from "uvu"; import { exec } from "child_process"; import * as assert from "uvu/assert"; import hash from "./hash.js"; import { roundTrip } from "./roundTrip.js"; +import { tinj } from "./tinj.js"; hash.setHmacKey("hashscripttest"); -test("Hash exists", async () => { +let testModule = "Hash"; + +Array.prototype.testEach = function (name, test) { + let s = suite(`Test ${this[0].t} [${testModule} Module] ${name}`); + let testno = this[0].t; + for (let i = 0; i < this.length; i++) { + let testName = `Test ${testno}[${i}] [${testModule} Module] ${name}`; + s(testName, () => { + tinj(this[i].t, this[i].inj); + test(this[i]); + }); + } + s.run(); +}; + +[{ t: 0 /*test # 0*/, inj: null }].testEach("Hash exists", () => { assert.is(true, Boolean(hash)); }); -test("Hash is string", () => { +[{ t: 1, inj: null }].testEach("Hash is string", () => { assert.is(true, typeof hash.hash("head", "text") === "string"); }); -test("node run script", () => { +[{ t: 2, inj: null }].testEach("node run script", () => { exec( `"echo" "-n" '12AM$A in the morning, or $B, or $C. Even at $D or $E' | openssl sha256 -hmac "hashscripttest"`, (error, stdout, stderr) => { @@ -28,18 +44,18 @@ test("node run script", () => { } const processedHash = hash.hash( "12AM", - "$A in the morning, or $B, or $C. Even at $D or $E" + "$A in the morning, or $B, or $C. Even at $D or $E", ); const hashToCompare = stdout .replace("(stdin)= ", "") .replace("SHA256", ""); assert.is(processedHash.trim(), hashToCompare.trim()); - } + }, ); }); // HASH_SUBSTRS_FIX -test("hash substrings test", () => { +[{ t: 3, inj: null }].testEach("hash substrings test", () => { var last = null; for (var i = 2; i < 10; i += 2) { var current = hash.hashToInt("1", "test", i); @@ -51,44 +67,69 @@ test("hash substrings test", () => { }); // HASH_NODIGITS_FIX -test("without digits can hash", () => { - assert.ok(hash.hashInferPluralDigitCsv("test", "test") != null); +[ + { + // fix - text having no digits can hash + t: 4, + inj: null, + expect: (val) => val == "6,8,3,8,4,11,3,8,11,2,11,2,10", + }, + { + // resurrect the bug: text having no digits can't hash + t: 4, + inj: 0, + expect: (val) => isNaN(val), + }, +].testEach("without digits can hash", ({ t, inj, expect }) => { + assert.ok(expect(hash.hashInferPluralDigitCsv("test", "test"))); }); +testModule = "Round Trip"; + // HASH_ROUNDTRIP_FIX -test("hash round trip", () => { +[ + { t: 5, inj: null, expect: "{test}10 or 11 or 6" }, + { t: 5, inj: 2, expect: "{test}2 or 2 or 2" }, //resurrect bug: no round trip +].testEach("hash round trip", ({ t, inj, expect }) => { + let res = true; var hash = "10 11 6"; var text = "{test}1 or 2 or 3"; var actual = text; try { actual = roundTrip(/*text:*/ text, /*hash:*/ hash, /*dynVars:*/ false); } catch {} - var expected = "{test}10 or 11 or 6"; - assert.is(actual, expected); + // var expected = "{test}10 or 11 or 6"; + + //assert.is(actual, expected); + assert.ok(actual == expect); }); // HASH_ROUNDTRIP_LIT2VAR -test("hash round trip literal to var", () => { +[ + { t: 6, inj: null, expect: "{test}$A or $B" }, // literal N convert 2 $A+ feature + { t: 6, inj: 2, expect: "{test}2 or 2" }, // disfeature literal N converted 2 $A+ +].testEach("hash round trip literal to var", ({ t, inj, expect }) => { var hash = "2 5"; var text = "{test}2 or 5"; var actual = text; try { actual = roundTrip(/*text:*/ text, /*hash:*/ hash, /*dynVars:*/ true); } catch {} - var expected = "{test}$A or $B"; - assert.is(actual, expected); + assert.is(actual, expect); }); // HASH_ROUNDTRIP_VAR2LIT_FIX -test("hash round trip var to literal", () => { +[ + { t: 7, inj: null, expect: "{test}2 or 5" }, // $A+ convert to literal N feature + { t: 7, inj: "2", expect: "{test}2 or 2" }, // disfeature $A+ convert 2 literal N +].testEach("hash round trip var to literal", ({ t, inj, expect }) => { var hash = "2 5"; var text = "{test}$A or $B"; var actual = text; try { actual = roundTrip(/*text:*/ text, /*hash:*/ hash, /*dynVars:*/ false); } catch {} - var expected = "{test}2 or 5"; - assert.is(actual, expected); + assert.is(actual, expect); }); -test.run(); +// test.run(); diff --git a/src/roundTrip.js b/src/roundTrip.js index 7a3bfc5..f76a3e5 100644 --- a/src/roundTrip.js +++ b/src/roundTrip.js @@ -3,10 +3,13 @@ import { getHashDigit } from "./getHashDigit.js"; export function roundTrip(text, hash, dynVars) { var varReplace = !dynVars && text.indexOf("$A") > -1; if (varReplace) { - return text.replace(/\$[A-Z]/g, (match) => getHashDigit(hash, match)); // HASH_ROUNDTRIP_VAR2LIT_FIX + return text.replace( + /\$[A-Z]/g, + (match) => tinj7 ?? getHashDigit(hash, match), + ); // HASH_ROUNDTRIP_VAR2LIT_FIX } var vari = 0; - var vars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // HASH_ROUNDTRIP_FIX + var vars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let h = hash; var t = ""; var startI = text.indexOf("}") < 0 ? 0 : text.indexOf("}") + 1; @@ -26,9 +29,9 @@ export function roundTrip(text, hash, dynVars) { i -= 1; var endI = h.indexOf(" ") > -1 ? h.indexOf(" ") : h.length; if (dynVars) { - t += "$" + vars[vari++ % vars.length]; + t += tinj6 ?? "$" + vars[vari++ % vars.length]; } else { - t += h.substring(0, endI); + t += tinj5 ?? h.substring(0, endI); // HASH_ROUNDTRIP_FIX } h = h.substring(endI + 1); } else { diff --git a/src/tinj.js b/src/tinj.js new file mode 100644 index 0000000..084089b --- /dev/null +++ b/src/tinj.js @@ -0,0 +1,28 @@ +// tinj - [t]est value [inj]ection +// +// The possible tinj0-tinj9999 values allow you to to repurpose a line of code +// of implementation, optionally reversing* its implementation +// +// * reversing the code line could mean to disfeature it, or to resurrect a bug +// the code line is fixing, by null-coalescing with an injected test value +// +// Example implementation for the feature 'negative values become positive': +// let abs = (n) => tinj11 ?? Math.abs(n); +// +// Abs-value test (test#11) featured test: +// tinj(11, null); +// console.assert(abs(-1) > -1); +// +// Abs-value (test#11) disfeatured test: +// tinj(11, -1); +// console.assert(abs(-1) < 0); +// +// For each featured-disfeatured pairing, use a different tinj# (test number) +// so you can cross-reference a unit test pair with its implementation code. +// +export function tinj(t, inj) { + for (let i=0; i