2f2b734e0a3271c47530b90d11b03664d59a2383
[squeep-mystery-box] / test / lib / mystery-box.js
1 /* eslint-env mocha */
2 /* eslint-disable capitalized-comments */
3 'use strict';
4
5 const assert = require('assert');
6 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
7 const MysteryBox = require('../../lib/mystery-box');
8 const stubLogger = require('../stub-logger');
9
10 describe('MysteryBox', function () {
11 let mb, options, object;
12 beforeEach(function () {
13 options = {
14 encryptionSecret: 'this is not a very good secret',
15 };
16 });
17 afterEach(function () {
18 sinon.restore();
19 });
20
21 describe('constructor', function () {
22 it('needs a secret', async function () {
23 options = {};
24 assert.rejects(() => new MysteryBox(stubLogger, options));
25 });
26
27 it('accepts multiple secrets', async function () {
28 this.slow(500);
29 options = {
30 encryptionSecret: ['first poor secret', 'second poor secret'],
31 };
32 mb = new MysteryBox(stubLogger, options);
33 object = {
34 foo: 'bar',
35 baz: 'quux',
36 flarp: 13,
37 };
38 const encryptedResult = await mb.pack(object);
39 const decryptedResult = await mb.unpack(encryptedResult);
40 assert.deepStrictEqual(decryptedResult, object);
41 });
42
43 it('covers options', function () {
44 assert.rejects(() => new MysteryBox(stubLogger));
45 });
46
47 it('covers bad flags', function () {
48 options.defaultFlags = 300;
49 assert.rejects(() => new MysteryBox(stubLogger, options), RangeError);
50 });
51
52 it('covers missing ciphers', function () {
53 sinon.stub(MysteryBox._test.crypto, 'getCiphers').returns(['rot13']);
54 assert.rejects(() => new MysteryBox(stubLogger, options));
55 });
56 }); // constructor
57
58 describe('_keyFromSecret', function () {
59 it('covers invalid', async function () {
60 assert.rejects(() => MysteryBox._keyFromSecret('unknown deriver', 'secret', 'salt', 32), RangeError);
61 });
62 }); // _keyFromSecret
63
64 describe('pack, unpack', function () {
65 beforeEach(function () {
66 mb = new MysteryBox(stubLogger, options);
67 });
68
69 it('covers packing unsupported version', async function () {
70 assert.rejects(() => mb.pack({}, 0), RangeError);
71 });
72
73 it('covers unpacking unsupported version', async function () {
74 const badBuffer = Buffer.alloc(128);
75 badBuffer.writeUInt8(0, 0); // No such thing as version 0
76 const badPayload = badBuffer.toString('base64');
77 assert.rejects(() => mb.unpack(badPayload), RangeError);
78 });
79
80 it('encrypts and decrypts default version', async function () {
81 this.slow(500);
82 object = {
83 foo: 'bar',
84 baz: 'quux',
85 flarp: 13,
86 };
87 const encryptedResult = await mb.pack(object);
88 const decryptedResult = await mb.unpack(encryptedResult);
89 assert.deepStrictEqual(decryptedResult, object);
90 });
91
92 it('encrypts and decrypts default version, buffer contents', async function () {
93 this.slow(500);
94 object = Buffer.from('a fine little buffer');
95 const encryptedResult = await mb.pack(object);
96 const decryptedResult = await mb.unpack(encryptedResult);
97 assert.deepStrictEqual(decryptedResult, object);
98 });
99
100 it('decrypts secondary (older) secret', async function () {
101 this.slow(500);
102 const oldmb = new MysteryBox(stubLogger, { encryptionSecret: 'old secret' });
103 const newmb = new MysteryBox(stubLogger, { encryptionSecret: ['new secret', 'old secret'] });
104 object = {
105 foo: 'bar',
106 baz: 'quux',
107 flarp: 13,
108 };
109 const oldEncrypted = await oldmb.pack(object);
110 const newDecrypted = await newmb.unpack(oldEncrypted);
111 assert.deepStrictEqual(newDecrypted, object);
112 });
113
114 it('fails to decrypt invalid secret', async function () {
115 this.slow(500);
116 const oldmb = new MysteryBox(stubLogger, { encryptionSecret: 'very old secret' });
117 const newmb = new MysteryBox(stubLogger, { encryptionSecret: ['new secret', 'old secret'] });
118 object = {
119 foo: 'bar',
120 baz: 'quux',
121 flarp: 13,
122 };
123 const oldEncrypted = await oldmb.pack(object);
124 assert.rejects(() => newmb.unpack(oldEncrypted));
125 });
126
127 it('encrypts and decrypts all available versions +brotli', async function () {
128 Object.keys(mb.versionParameters).map((v) => Number(v)).forEach(async (version) => {
129 object = {
130 foo: 'bar',
131 baz: 'quux',
132 flarp: 13,
133 };
134 const encryptedResult = await mb.pack(object, version, 0x00);
135 const decryptedResult = await mb.unpack(encryptedResult);
136 assert.deepStrictEqual(decryptedResult, object, `${version} results not symmetric`);
137 });
138 });
139
140 it('encrypts and decrypts all available versions +flate', async function () {
141 Object.keys(mb.versionParameters).map((v) => Number(v)).forEach(async (version) => {
142 object = {
143 foo: 'bar',
144 baz: 'quux',
145 flarp: 13,
146 };
147 const encryptedResult = await mb.pack(object, version, 0x01);
148 const decryptedResult = await mb.unpack(encryptedResult);
149 assert.deepStrictEqual(decryptedResult, object, `${version} results not symmetric`);
150 });
151 });
152
153 it('handles large object +brotli', async function () {
154 this.timeout(5000);
155 this.slow(2000);
156 const firstChar = 32, lastChar = 126;
157 const rnd = () => {
158 return Math.floor(firstChar + (lastChar - firstChar + 1) * Math.random());
159 }
160 object = {
161 longProperty: 'x'.repeat(384 * 1024).split('').map(() => String.fromCharCode(rnd())).join(''),
162 };
163 const encryptedResult = await mb.pack(object, mb.bestVersion, 0x00);
164 const decryptedResult = await mb.unpack(encryptedResult);
165 assert.deepStrictEqual(decryptedResult, object);
166 });
167
168 it('handles large object +flate', async function () {
169 this.timeout(5000);
170 this.slow(2000);
171 const firstChar = 32, lastChar = 126;
172 const rnd = () => {
173 return Math.floor(firstChar + (lastChar - firstChar + 1) * Math.random());
174 }
175 object = {
176 longProperty: 'x'.repeat(384 * 1024).split('').map(() => String.fromCharCode(rnd())).join(''),
177 };
178 const encryptedResult = await mb.pack(object, mb.bestVersion, 0x01);
179 const decryptedResult = await mb.unpack(encryptedResult);
180 assert.deepStrictEqual(decryptedResult, object);
181 });
182
183 it('handles undefined', async function () {
184 assert.rejects(() => mb.unpack(), RangeError);
185 });
186
187 it('handles incomplete', async function () {
188 this.slow(500);
189 const encryptedResult = await mb.pack({ foo: 'bar' });
190 assert.rejects(() => mb.unpack(encryptedResult.slice(0, 6)), RangeError);
191 });
192
193 }); // pack, unpack
194
195 }); // MysteryBox