From a39c4c7d18d4c59407e5f377c10c697ba7b87e4e Mon Sep 17 00:00:00 2001 From: ctcpip Date: Thu, 9 May 2024 16:27:34 -0500 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=9A=9A=20add=20test=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test.js => test/test.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test.js => test/test.js (100%) diff --git a/test.js b/test/test.js similarity index 100% rename from test.js rename to test/test.js From bdd29a9a7744cbaf0be9bd31df827d20aa79f0ce Mon Sep 17 00:00:00 2001 From: ctcpip Date: Thu, 9 May 2024 16:30:21 -0500 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A5=85=20improve=20error=20handling?= =?UTF-8?q?=20for=20decode=20failures,=20add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 11 ++++++- package.json | 2 +- test/corrupt-file | 1 + test/empty-file | 0 test/test.js | 81 +++++++++++++++++++++++++++++++++++++++++------ test/valid-file | 1 + 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 test/corrupt-file create mode 100644 test/empty-file create mode 100644 test/valid-file diff --git a/index.js b/index.js index 26e0983..b1230ba 100644 --- a/index.js +++ b/index.js @@ -116,7 +116,16 @@ module.exports = function (Adapter) { if (error) return error.code === 'ENOENT' ? resolve() : reject(error) - record = msgpack.decode(buffer) + if(buffer.length === 0) { + return reject(new Error(`Decode record failed. File is empty: ${filePath}`)) + } + else { + try { + record = msgpack.decode(buffer) + } catch (e) { + return reject(new Error(`Decode record failed. File is corrupt: ${filePath}`, { cause: e })) + } + } if (!(type in self.db)) self.db[type] = {} diff --git a/package.json b/package.json index 45bb4d1..8b4868c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "eslint index.js", "postpublish": "npm run tag", "tag": "git tag `npm v fortune-fs version` && git push origin --tags", - "test": "npm run lint && node test.js" + "test": "npm run lint && node test/test.js" }, "dependencies": { "lockfile": "^1.0.4", diff --git a/test/corrupt-file b/test/corrupt-file new file mode 100644 index 0000000..549dd39 --- /dev/null +++ b/test/corrupt-file @@ -0,0 +1 @@ +‚¢id£bar \ No newline at end of file diff --git a/test/empty-file b/test/empty-file new file mode 100644 index 0000000..e69de29 diff --git a/test/test.js b/test/test.js index 280f631..bbd6a00 100644 --- a/test/test.js +++ b/test/test.js @@ -1,22 +1,85 @@ 'use strict' var testAdapter = require('fortune/test/adapter') -var fsAdapter = require('./index') +var fsAdapter = require('../index') const fortune = require('fortune') -const assert = require('node:assert/strict') +const fs = require('node:fs') +const run = require('tapdance') testAdapter(fsAdapter) -assert.doesNotThrow(() => { +run((assert, comment) => { + comment('concurrentReads validation') + const concurrentReads = 1 + const store = fortune({}, { adapter: [fsAdapter, { concurrentReads }], }) - assert.equal(store.adapter.options.concurrentReads, concurrentReads) -}) -assert.throws(() => { - fortune({}, { - adapter: [fsAdapter, { concurrentReads: 0 }], + assert(store.adapter.options.concurrentReads === concurrentReads, `adapter has expected concurrentReads value --- expected: ${store.adapter.options.concurrentReads} --- actual: ${concurrentReads}`) + + let thrown = false + let expectedError + + try { + fortune({}, { + adapter: [fsAdapter, { concurrentReads: 0 }], + }) + } catch (e) { + thrown = true + expectedError = e.message === 'concurrentReads must be > 0' + if(!expectedError) { + // only log the error if it was something we did not expect + console.error(e) + } + } + + assert(thrown, 'concurrentReads 0 is not valid') + assert(expectedError, 'got expected error for concurrentReads 0') +}); + +(async () => { + + run(async (assert, comment) => { + comment('msgpack decode validation') + + const type = 'foo' + const schema = {} + schema[type] = { bar: Boolean } + + const store = fortune( + schema, + { adapter: [fsAdapter] }) + + fs.mkdirSync(`db/${type}`, { recursive: true }) + fs.copyFileSync('test/empty-file', `db/${type}/1`) + fs.copyFileSync('test/valid-file', `db/${type}/3`) + fs.copyFileSync('test/corrupt-file', `db/${type}/6`) + + let error = null + + try { + await store.find(type, [1]) + } catch (e) { + error = e + } + + assert(error.message.includes('Decode record failed. File is empty'), `empty error message is present: ${error.message}`) + + error = null + + try { + await store.find(type, [6]) + } catch (e) { + error = e + } + + assert(error.message.includes('Decode record failed. File is corrupt'), `corrupt error message is present ${error.message}`) + + const result = await store.find(type, [3]) + assert(result.payload.records.length === 1, 'valid record is found') + }) -}) + +})() diff --git a/test/valid-file b/test/valid-file new file mode 100644 index 0000000..232ca77 --- /dev/null +++ b/test/valid-file @@ -0,0 +1 @@ +‚¢id£barĂ \ No newline at end of file From 4eb5ec7c552784913a9dfc2081dcb8fcc88c80e1 Mon Sep 17 00:00:00 2001 From: ctcpip Date: Thu, 9 May 2024 23:31:48 -0500 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=85=20add=20more=20tests=20for=20corr?= =?UTF-8?q?upt/empty=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test.js | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/test/test.js b/test/test.js index bbd6a00..e34eb8f 100644 --- a/test/test.js +++ b/test/test.js @@ -52,15 +52,19 @@ run((assert, comment) => { schema, { adapter: [fsAdapter] }) + const emptyID = 1 + const validID = 3 + const corruptID = 6 + fs.mkdirSync(`db/${type}`, { recursive: true }) - fs.copyFileSync('test/empty-file', `db/${type}/1`) - fs.copyFileSync('test/valid-file', `db/${type}/3`) - fs.copyFileSync('test/corrupt-file', `db/${type}/6`) + fs.copyFileSync('test/empty-file', `db/${type}/${emptyID}`) + fs.copyFileSync('test/valid-file', `db/${type}/${validID}`) + fs.copyFileSync('test/corrupt-file', `db/${type}/${corruptID}`) let error = null try { - await store.find(type, [1]) + await store.find(type, [emptyID]) } catch (e) { error = e } @@ -70,16 +74,38 @@ run((assert, comment) => { error = null try { - await store.find(type, [6]) + await store.find(type, [corruptID]) } catch (e) { error = e } assert(error.message.includes('Decode record failed. File is corrupt'), `corrupt error message is present ${error.message}`) - const result = await store.find(type, [3]) + let result = await store.find(type, [validID]) assert(result.payload.records.length === 1, 'valid record is found') + error = null + + try { + await store.update(type, [{ id: emptyID }]) + } catch (e) { + error = e + } + + assert(error, 'trying to update an empty file fails: ' + error) + + error = null + + try { + await store.update(type, [{ id: corruptID }]) + } catch (e) { + error = e + } + + assert(error, 'trying to update a corrupt file fails: ' + error) + + result = await store.create(type, [{ id: emptyID }, {id: corruptID}]) + assert(result.payload.records.length === 2, 'successfully replaced empty and corrupt files via store.create()') }) })()