Pad: Add strict validation checks

This commit is contained in:
Richard Hansen 2021-11-27 03:37:34 -05:00
parent 2608a81654
commit 7c870f8a58
5 changed files with 166 additions and 238 deletions

View file

@ -10,79 +10,18 @@ process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 2) throw new Error('Use: node src/bin/checkAllPads.js');
(async () => {
// initialize the database
require('../node/utils/Settings');
const db = require('../node/db/DB');
await db.init();
// load modules
const Changeset = require('../static/js/Changeset');
const padManager = require('../node/db/PadManager');
let revTestedCount = 0;
// get all pads
const res = await padManager.listAllPads();
for (const padId of res.padIDs) {
await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId) => {
const pad = await padManager.getPad(padId);
// check if the pad has a pool
if (pad.pool == null) {
console.error(`[${pad.id}] Missing attribute pool`);
continue;
try {
await pad.check();
} catch (err) {
console.error(`Error in pad ${padId}: ${err.stack || err}`);
return;
}
// create an array with key kevisions
// key revisions always save the full pad atext
const head = pad.getHeadRevisionNumber();
const keyRevisions = [];
for (let rev = 0; rev < head; rev += 100) {
keyRevisions.push(rev);
}
// run through all key revisions
for (const keyRev of keyRevisions) {
// create an array of revisions we need till the next keyRevision or the End
const revisionsNeeded = [];
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
revisionsNeeded.push(rev);
}
// this array will hold all revision changesets
const revisions = [];
// run through all needed revisions and get them from the database
for (const revNum of revisionsNeeded) {
const revision = await db.get(`pad:${pad.id}:revs:${revNum}`);
revisions[revNum] = revision;
}
// check if the revision exists
if (revisions[keyRev] == null) {
console.error(`[${pad.id}] Missing revision ${keyRev}`);
continue;
}
// check if there is a atext in the keyRevisions
let {meta: {atext} = {}} = revisions[keyRev];
if (atext == null) {
console.error(`[${pad.id}] Missing atext in revision ${keyRev}`);
continue;
}
const apool = pad.pool;
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
try {
const cs = revisions[rev].changeset;
atext = Changeset.applyToAText(cs, atext, apool);
revTestedCount++;
} catch (e) {
console.error(`[${pad.id}] Bad changeset at revision ${rev} - ${e.message}`);
}
}
}
}
if (revTestedCount === 0) {
throw new Error('No revisions tested');
}
console.log(`Finished: Tested ${revTestedCount} revisions`);
console.log(`Pad ${padId}: OK`);
}));
console.log('Finished.');
})();

View file

@ -8,75 +8,13 @@
process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 3) throw new Error('Use: node src/bin/checkPad.js $PADID');
// get the padID
const padId = process.argv[2];
let checkRevisionCount = 0;
(async () => {
// initialize database
require('../node/utils/Settings');
const db = require('../node/db/DB');
await db.init();
// load modules
const Changeset = require('../static/js/Changeset');
const padManager = require('../node/db/PadManager');
const exists = await padManager.doesPadExists(padId);
if (!exists) throw new Error('Pad does not exist');
// get the pad
if (!await padManager.doesPadExists(padId)) throw new Error('Pad does not exist');
const pad = await padManager.getPad(padId);
// create an array with key revisions
// key revisions always save the full pad atext
const head = pad.getHeadRevisionNumber();
const keyRevisions = [];
for (let rev = 0; rev < head; rev += 100) {
keyRevisions.push(rev);
}
// run through all key revisions
for (let keyRev of keyRevisions) {
keyRev = parseInt(keyRev);
// create an array of revisions we need till the next keyRevision or the End
const revisionsNeeded = [];
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
revisionsNeeded.push(rev);
}
// this array will hold all revision changesets
const revisions = [];
// run through all needed revisions and get them from the database
for (const revNum of revisionsNeeded) {
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
revisions[revNum] = revision;
}
// check if the pad has a pool
if (pad.pool == null) throw new Error('Attribute pool is missing');
// check if there is an atext in the keyRevisions
let {meta: {atext} = {}} = revisions[keyRev] || {};
if (atext == null) {
console.error(`No atext in key revision ${keyRev}`);
continue;
}
const apool = pad.pool;
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
checkRevisionCount++;
try {
const cs = revisions[rev].changeset;
atext = Changeset.applyToAText(cs, atext, apool);
} catch (e) {
console.error(`Bad changeset at revision ${rev} - ${e.message}`);
continue;
}
}
console.log(`Finished: Checked ${checkRevisionCount} revisions`);
}
await pad.check();
console.log('Finished.');
})();

View file

@ -1,103 +0,0 @@
'use strict';
/*
* This is a debug tool. It checks all revisions for data corruption
*/
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 3) throw new Error('Use: node src/bin/checkPadDeltas.js $PADID');
// get the padID
const padId = process.argv[2];
const expect = require('../tests/frontend/lib/expect');
const diff = require('diff');
(async () => {
// initialize database
require('../node/utils/Settings');
const db = require('../node/db/DB');
await db.init();
// load modules
const Changeset = require('../static/js/Changeset');
const padManager = require('../node/db/PadManager');
const exists = await padManager.doesPadExists(padId);
if (!exists) throw new Error('Pad does not exist');
// get the pad
const pad = await padManager.getPad(padId);
// create an array with key revisions
// key revisions always save the full pad atext
const head = pad.getHeadRevisionNumber();
const keyRevisions = [];
for (let i = 0; i < head; i += 100) {
keyRevisions.push(i);
}
// create an array with all revisions
const revisions = [];
for (let i = 0; i <= head; i++) {
revisions.push(i);
}
let atext = Changeset.makeAText('\n');
// run through all revisions
for (const revNum of revisions) {
// console.log('Fetching', revNum)
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
// check if there is a atext in the keyRevisions
const {meta: {atext: revAtext} = {}} = revision || {};
if (~keyRevisions.indexOf(revNum) && revAtext == null) {
console.error(`No atext in key revision ${revNum}`);
continue;
}
// try glue everything together
try {
// console.log("check revision ", revNum);
const cs = revision.changeset;
atext = Changeset.applyToAText(cs, atext, pad.pool);
} catch (e) {
console.error(`Bad changeset at revision ${revNum} - ${e.message}`);
continue;
}
// check things are working properly
if (~keyRevisions.indexOf(revNum)) {
try {
expect(revision.meta.atext.text).to.eql(atext.text);
expect(revision.meta.atext.attribs).to.eql(atext.attribs);
} catch (e) {
console.error(`Atext in key revision ${revNum} doesn't match computed one.`);
console.log(diff.diffChars(atext.text, revision.meta.atext.text).map((op) => {
if (!op.added && !op.removed) op.value = op.value.length;
return op;
}));
// console.error(e)
// console.log('KeyRev. :', revision.meta.atext)
// console.log('Computed:', atext)
continue;
}
}
}
// check final text is right...
if (pad.atext.text === atext.text) {
console.log('ok');
} else {
console.error('Pad AText doesn\'t match computed one! (Computed ',
atext.text.length, ', db', pad.atext.text.length, ')');
console.log(diff.diffChars(atext.text, pad.atext.text).map((op) => {
if (!op.added && !op.removed) {
op.value = op.value.length;
return op;
}
}));
}
})();