Skip to content

Commit 600db95

Browse files
Merge pull request #125 from godaddy/add-behavior-tests
test: add comprehensive behavior tests
2 parents 649d0ef + 4819c79 commit 600db95

2 files changed

Lines changed: 314 additions & 1 deletion

File tree

test/asherah-behavior.spec.ts

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
import { describe, it, beforeEach, afterEach } from 'mocha';
2+
import { strict as assert } from 'assert';
3+
import {
4+
asherah_setup_static_memory_async,
5+
asherah_shutdown_async,
6+
assert_throws_async
7+
} from './asherah';
8+
import {
9+
setup_async,
10+
encrypt_async,
11+
decrypt_async,
12+
encrypt_string,
13+
encrypt_string_async,
14+
decrypt_string,
15+
decrypt_string_async,
16+
get_setup_status,
17+
set_max_stack_alloc_item_size
18+
} from '../dist/asherah';
19+
20+
describe('Asherah Behavior Tests', function() {
21+
this.timeout(5000); // Allow longer timeout for stress tests
22+
23+
describe('Setup and Shutdown Behavior', function() {
24+
it('should not allow double setup', async function() {
25+
await asherah_setup_static_memory_async();
26+
try {
27+
await assert_throws_async(async () => {
28+
await setup_async({
29+
ServiceName: 'test',
30+
ProductID: 'test',
31+
Metastore: 'test-debug-memory',
32+
KMS: 'test-debug-static',
33+
ExpireAfter: null,
34+
CheckInterval: null,
35+
ConnectionString: null,
36+
ReplicaReadConsistency: null,
37+
DynamoDBEndpoint: null,
38+
DynamoDBRegion: null,
39+
DynamoDBTableName: null,
40+
SessionCacheMaxSize: null,
41+
SessionCacheDuration: null,
42+
RegionMap: null,
43+
PreferredRegion: null,
44+
EnableRegionSuffix: null,
45+
EnableSessionCaching: null,
46+
Verbose: null,
47+
DisableZeroCopy: null,
48+
EnableCanaries: true
49+
});
50+
}, 'Should not allow setup when already initialized');
51+
} finally {
52+
await asherah_shutdown_async();
53+
}
54+
});
55+
56+
it('should not allow operations after shutdown', async function() {
57+
await asherah_setup_static_memory_async();
58+
await asherah_shutdown_async();
59+
60+
await assert_throws_async(async () => {
61+
await encrypt_string_async('partition', 'data');
62+
}, 'Should not allow encryption after shutdown');
63+
});
64+
65+
it('should handle rapid setup/shutdown cycles', async function() {
66+
for (let i = 0; i < 10; i++) {
67+
await asherah_setup_static_memory_async();
68+
assert(get_setup_status(), `Setup should succeed on iteration ${i}`);
69+
await asherah_shutdown_async();
70+
assert(!get_setup_status(), `Shutdown should succeed on iteration ${i}`);
71+
}
72+
});
73+
});
74+
75+
describe('Encryption/Decryption Behavior', function() {
76+
beforeEach(async function() {
77+
await asherah_setup_static_memory_async();
78+
});
79+
80+
afterEach(async function() {
81+
await asherah_shutdown_async();
82+
});
83+
84+
it('should handle maximum size data', async function() {
85+
// Test with progressively larger data sizes
86+
const sizes = [1024, 10240, 102400, 1048576]; // 1KB, 10KB, 100KB, 1MB
87+
88+
for (const size of sizes) {
89+
const largeData = Buffer.alloc(size, 'x');
90+
const encrypted = await encrypt_async('partition', largeData);
91+
const decrypted = await decrypt_async('partition', encrypted);
92+
93+
assert(Buffer.compare(largeData, decrypted) === 0,
94+
`Should correctly handle ${size} byte buffer`);
95+
}
96+
});
97+
98+
it('should handle unicode and special characters', async function() {
99+
const testStrings = [
100+
'你好世界', // Chinese
101+
'🔐🔑🛡️', // Emojis
102+
'Hello\x00World', // Null byte
103+
'Line1\nLine2\rLine3\r\n', // Various line endings
104+
'{"json": "value", "nested": {"key": "value"}}', // JSON
105+
'<script>alert("xss")</script>', // HTML/JS
106+
'Robert\'); DROP TABLE Students;--', // SQL injection attempt
107+
'\\u0000\\u0001\\u0002', // Unicode escapes
108+
];
109+
110+
for (const testStr of testStrings) {
111+
const encrypted = await encrypt_string_async('partition', testStr);
112+
const decrypted = await decrypt_string_async('partition', encrypted);
113+
assert.strictEqual(decrypted, testStr,
114+
`Should handle special string: ${testStr.substring(0, 20)}...`);
115+
}
116+
});
117+
118+
it('should produce different ciphertexts for same plaintext', async function() {
119+
const plaintext = 'same data';
120+
const encrypted1 = await encrypt_string_async('partition', plaintext);
121+
const encrypted2 = await encrypt_string_async('partition', plaintext);
122+
123+
assert.notStrictEqual(encrypted1, encrypted2,
124+
'Should produce different ciphertexts (nonce/IV randomization)');
125+
126+
// But both should decrypt to same value
127+
const decrypted1 = await decrypt_string_async('partition', encrypted1);
128+
const decrypted2 = await decrypt_string_async('partition', encrypted2);
129+
assert.strictEqual(decrypted1, plaintext);
130+
assert.strictEqual(decrypted2, plaintext);
131+
});
132+
133+
it('should handle concurrent operations', async function() {
134+
const operations = 100;
135+
const promises = [];
136+
137+
// Mix of encryptions and decryptions
138+
for (let i = 0; i < operations; i++) {
139+
if (i % 2 === 0) {
140+
promises.push(
141+
encrypt_string_async(`partition${i % 10}`, `data${i}`)
142+
.then(encrypted => decrypt_string_async(`partition${i % 10}`, encrypted))
143+
.then(decrypted => ({ original: `data${i}`, decrypted }))
144+
);
145+
} else {
146+
promises.push(
147+
encrypt_async(`partition${i % 10}`, Buffer.from(`data${i}`))
148+
.then(encrypted => decrypt_async(`partition${i % 10}`, encrypted))
149+
.then(decrypted => ({
150+
original: `data${i}`,
151+
decrypted: decrypted.toString()
152+
}))
153+
);
154+
}
155+
}
156+
157+
const results = await Promise.all(promises);
158+
results.forEach(({ original, decrypted }) => {
159+
assert.strictEqual(decrypted, original,
160+
'Concurrent operations should maintain data integrity');
161+
});
162+
});
163+
164+
it('should handle partition isolation', async function() {
165+
const data = 'secret data';
166+
const encrypted1 = await encrypt_string_async('partition1', data);
167+
const encrypted2 = await encrypt_string_async('partition2', data);
168+
169+
// Encrypted data should be different for different partitions
170+
assert.notStrictEqual(encrypted1, encrypted2,
171+
'Different partitions should produce different ciphertexts');
172+
173+
// Should not be able to decrypt with wrong partition
174+
await assert_throws_async(async () => {
175+
await decrypt_string_async('partition2', encrypted1);
176+
}, 'Should not decrypt data from different partition');
177+
});
178+
});
179+
180+
describe('Error Handling Behavior', function() {
181+
beforeEach(async function() {
182+
await asherah_setup_static_memory_async();
183+
});
184+
185+
afterEach(async function() {
186+
await asherah_shutdown_async();
187+
});
188+
189+
it('should handle invalid encrypted data gracefully', async function() {
190+
const invalidInputs = [
191+
'not-json',
192+
'{}', // Valid JSON but not valid envelope
193+
'{"wrong": "format"}',
194+
JSON.stringify({ ParentKeyMeta: {} }), // Missing required fields
195+
'', // Empty string already tested elsewhere
196+
'null',
197+
'undefined',
198+
'[1,2,3]', // Array instead of object
199+
];
200+
201+
for (const invalid of invalidInputs) {
202+
await assert_throws_async(async () => {
203+
await decrypt_string_async('partition', invalid);
204+
}, `Should reject invalid encrypted data: ${invalid}`);
205+
}
206+
});
207+
208+
it('should handle buffer/string type mismatches', async function() {
209+
// Encrypt as string, try to decrypt as buffer
210+
const stringEncrypted = await encrypt_string_async('partition', 'test');
211+
const bufferResult = await decrypt_async('partition', stringEncrypted);
212+
assert(Buffer.isBuffer(bufferResult), 'Should return buffer from decrypt');
213+
assert.strictEqual(bufferResult.toString(), 'test', 'Should decrypt correctly');
214+
215+
// Encrypt as buffer, try to decrypt as string
216+
const bufferEncrypted = await encrypt_async('partition', Buffer.from('test'));
217+
const stringResult = await decrypt_string_async('partition', bufferEncrypted);
218+
assert.strictEqual(typeof stringResult, 'string', 'Should return string from decrypt_string');
219+
assert.strictEqual(stringResult, 'test', 'Should decrypt correctly');
220+
});
221+
});
222+
223+
describe('Stack Allocation Behavior', function() {
224+
it('should handle different stack allocation sizes', async function() {
225+
const testSizes = [0, 1, 1024, 4096, 65536];
226+
227+
for (const size of testSizes) {
228+
await asherah_setup_static_memory_async(false, true, size);
229+
230+
try {
231+
// Test with data that might use stack allocation
232+
const data = 'x'.repeat(Math.min(size / 2, 100));
233+
const encrypted = await encrypt_string_async('partition', data);
234+
const decrypted = await decrypt_string_async('partition', encrypted);
235+
assert.strictEqual(decrypted, data,
236+
`Should work with stack size ${size}`);
237+
} finally {
238+
await asherah_shutdown_async();
239+
}
240+
}
241+
});
242+
243+
it('should handle negative stack allocation size', async function() {
244+
await asherah_setup_static_memory_async();
245+
246+
// Should clamp negative values to 0 (force heap allocation)
247+
set_max_stack_alloc_item_size(-1);
248+
249+
const data = 'test data';
250+
const encrypted = await encrypt_string_async('partition', data);
251+
const decrypted = await decrypt_string_async('partition', encrypted);
252+
assert.strictEqual(decrypted, data,
253+
'Should handle negative stack size by using heap');
254+
255+
await asherah_shutdown_async();
256+
});
257+
258+
it('should handle very large stack allocation size', async function() {
259+
await asherah_setup_static_memory_async();
260+
261+
// Should clamp to reasonable maximum (1MB)
262+
set_max_stack_alloc_item_size(2147483647); // INT32_MAX
263+
264+
const data = 'test data';
265+
const encrypted = await encrypt_string_async('partition', data);
266+
const decrypted = await decrypt_string_async('partition', encrypted);
267+
assert.strictEqual(decrypted, data,
268+
'Should handle very large stack size request');
269+
270+
await asherah_shutdown_async();
271+
});
272+
});
273+
274+
describe('Sync vs Async Behavior Consistency', function() {
275+
beforeEach(async function() {
276+
await asherah_setup_static_memory_async();
277+
});
278+
279+
afterEach(async function() {
280+
await asherah_shutdown_async();
281+
});
282+
283+
it('should produce compatible results between sync and async', async function() {
284+
const testData = 'test data for compatibility';
285+
286+
// Encrypt sync, decrypt async
287+
const encryptedSync = encrypt_string('partition', testData);
288+
const decryptedAsync = await decrypt_string_async('partition', encryptedSync);
289+
assert.strictEqual(decryptedAsync, testData,
290+
'Sync encrypted data should decrypt correctly with async');
291+
292+
// Encrypt async, decrypt sync
293+
const encryptedAsync = await encrypt_string_async('partition', testData);
294+
const decryptedSync = decrypt_string('partition', encryptedAsync);
295+
assert.strictEqual(decryptedSync, testData,
296+
'Async encrypted data should decrypt correctly with sync');
297+
});
298+
299+
it('should handle errors consistently between sync and async', function() {
300+
// Both should throw for empty partition - just verify they throw, not the exact message
301+
assert.throws(() => {
302+
encrypt_string('', 'data');
303+
}, 'Sync should throw for empty partition');
304+
305+
return assert_throws_async(async () => {
306+
await encrypt_string_async('', 'data');
307+
}, 'Async should throw for empty partition');
308+
});
309+
});
310+
});

test/asherah.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,10 @@ function assert_buffers_equal(a: Buffer, b: Buffer, message?: string) {
201201

202202
function assert_encrypt_string(partition: string, input: string, encrypted: string) {
203203
//Ensure that the secret data isn't anywhere in the output of encrypt
204-
assert(encrypted.indexOf(input) == -1, "Encrypted data should not contain secret input data");
204+
// Note: for empty string, indexOf('') always returns 0, so skip this check
205+
if (input.length > 0) {
206+
assert(encrypted.indexOf(input) == -1, "Encrypted data should not contain secret input data");
207+
}
205208

206209
//Ensure that the partition name hasn't been corrupted / truncated
207210
assert(encrypted.indexOf(partition) != -1, "Encrypted data should contain partition name");

0 commit comments

Comments
 (0)