@synet/encoder 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Pure Functions Tests - Serverless Ready Operations
3
+ *
4
+ * Tests all pure encoding/decoding functions with edge cases
5
+ * and concurrent execution validation
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import {
10
+ encode,
11
+ decode,
12
+ encodeBase64,
13
+ decodeBase64,
14
+ encodeBase64URL,
15
+ decodeBase64URL,
16
+ encodeHex,
17
+ decodeHex,
18
+ encodeURIString,
19
+ decodeURIString,
20
+ encodeASCII,
21
+ decodeASCII,
22
+ detectFormat,
23
+ validateFormat,
24
+ chain,
25
+ reverseChain,
26
+ type EncodingFormat
27
+ } from '../src/functions.js';
28
+
29
+ describe('Pure Functions - Core Operations', () => {
30
+ describe('Base64 Functions', () => {
31
+ it('should encode/decode base64 correctly', () => {
32
+ const text = 'Hello World';
33
+ const encoded = encodeBase64(text);
34
+ expect(encoded).toBe('SGVsbG8gV29ybGQ=');
35
+
36
+ const decoded = decodeBase64(encoded);
37
+ expect(decoded).toBe(text);
38
+ });
39
+
40
+ it('should handle empty strings', () => {
41
+ expect(encodeBase64('')).toBe('');
42
+ expect(decodeBase64('')).toBe('');
43
+ });
44
+
45
+ it('should handle unicode characters', () => {
46
+ const unicode = 'πŸš€ Hello δΈ–η•Œ';
47
+ const encoded = encodeBase64(unicode);
48
+ const decoded = decodeBase64(encoded);
49
+ expect(decoded).toBe(unicode);
50
+ });
51
+
52
+ it('should be reversible for all printable ASCII', () => {
53
+ const ascii = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
54
+ const encoded = encodeBase64(ascii);
55
+ const decoded = decodeBase64(encoded);
56
+ expect(decoded).toBe(ascii);
57
+ });
58
+ });
59
+
60
+ describe('Base64URL Functions', () => {
61
+ it('should encode/decode base64url correctly', () => {
62
+ const text = 'Hello World!';
63
+ const encoded = encodeBase64URL(text);
64
+ expect(encoded).toBe('SGVsbG8gV29ybGQh');
65
+ expect(encoded).not.toContain('=');
66
+
67
+ const decoded = decodeBase64URL(encoded);
68
+ expect(decoded).toBe(text);
69
+ });
70
+
71
+ it('should produce URL-safe output', () => {
72
+ const text = 'Hello+World/Test=';
73
+ const encoded = encodeBase64URL(text);
74
+ expect(encoded).not.toContain('+');
75
+ expect(encoded).not.toContain('/');
76
+ expect(encoded).not.toContain('=');
77
+
78
+ const decoded = decodeBase64URL(encoded);
79
+ expect(decoded).toBe(text);
80
+ });
81
+
82
+ it('should handle padding correctly', () => {
83
+ const tests = ['A', 'AB', 'ABC', 'ABCD'];
84
+
85
+ for (const test of tests) {
86
+ const encoded = encodeBase64URL(test);
87
+ const decoded = decodeBase64URL(encoded);
88
+ expect(decoded).toBe(test);
89
+ }
90
+ });
91
+ });
92
+
93
+ describe('Hexadecimal Functions', () => {
94
+ it('should encode/decode hex correctly', () => {
95
+ const text = 'Hello';
96
+ const encoded = encodeHex(text);
97
+ expect(encoded).toBe('48656c6c6f');
98
+
99
+ const decoded = decodeHex(encoded);
100
+ expect(decoded).toBe(text);
101
+ });
102
+
103
+ it('should handle mixed case hex decoding', () => {
104
+ expect(decodeHex('48656c6c6f')).toBe('Hello');
105
+ expect(decodeHex('48656C6C6F')).toBe('Hello');
106
+ expect(decodeHex('48656c6C6f')).toBe('Hello');
107
+ });
108
+
109
+ it('should throw on invalid hex', () => {
110
+ expect(() => decodeHex('48656c6c6g')).toThrow('contains non-hex characters');
111
+ expect(() => decodeHex('48656c6c6')).toThrow('length must be even');
112
+ });
113
+
114
+ it('should handle control characters', () => {
115
+ const text = '\x00\x01\x02\x1f\x7f';
116
+ const encoded = encodeHex(text);
117
+ const decoded = decodeHex(encoded);
118
+ expect(decoded).toBe(text);
119
+ });
120
+ });
121
+
122
+ describe('URI Functions', () => {
123
+ it('should encode/decode URI correctly', () => {
124
+ const text = 'Hello World!';
125
+ const encoded = encodeURIString(text);
126
+ expect(encoded).toBe('Hello%20World!');
127
+
128
+ const decoded = decodeURIString(encoded);
129
+ expect(decoded).toBe(text);
130
+ });
131
+
132
+ it('should handle special characters', () => {
133
+ const special = 'test@example.com?param=value&other=test#anchor';
134
+ const encoded = encodeURIString(special);
135
+ const decoded = decodeURIString(encoded);
136
+ expect(decoded).toBe(special);
137
+ });
138
+
139
+ it('should handle unicode in URIs', () => {
140
+ const unicode = 'Hello δΈ–η•Œ';
141
+ const encoded = encodeURIString(unicode);
142
+ const decoded = decodeURIString(encoded);
143
+ expect(decoded).toBe(unicode);
144
+ });
145
+ });
146
+
147
+ describe('ASCII Functions', () => {
148
+ it('should encode ASCII text unchanged', () => {
149
+ const text = 'Hello World';
150
+ const encoded = encodeASCII(text);
151
+ expect(encoded).toBe(text);
152
+ });
153
+
154
+ it('should throw on non-ASCII characters', () => {
155
+ expect(() => encodeASCII('Hello δΈ–η•Œ')).toThrow('Non-ASCII character found');
156
+ expect(() => encodeASCII('Hello\u0080')).toThrow('Non-ASCII character found');
157
+ });
158
+
159
+ it('should validate printable ASCII on decode', () => {
160
+ expect(decodeASCII('Hello World')).toBe('Hello World');
161
+ expect(() => decodeASCII('Hello\x00World')).toThrow('Non-printable ASCII');
162
+ expect(() => decodeASCII('Hello\x1fWorld')).toThrow('Non-printable ASCII');
163
+ });
164
+
165
+ it('should handle all printable ASCII characters', () => {
166
+ const printable = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
167
+ expect(encodeASCII(printable)).toBe(printable);
168
+ expect(decodeASCII(printable)).toBe(printable);
169
+ });
170
+ });
171
+ });
172
+
173
+ describe('Pure Functions - Generic Operations', () => {
174
+ describe('Generic Encode/Decode', () => {
175
+ const testData = 'Hello World';
176
+ const formats: EncodingFormat[] = ['base64', 'base64url', 'hex', 'uri', 'ascii'];
177
+
178
+ it('should encode to all formats', () => {
179
+ for (const format of formats) {
180
+ const encoded = encode(testData, format);
181
+ expect(typeof encoded).toBe('string');
182
+ expect(encoded.length).toBeGreaterThan(0);
183
+ }
184
+ });
185
+
186
+ it('should decode from all formats', () => {
187
+ for (const format of formats) {
188
+ const encoded = encode(testData, format);
189
+ const decoded = decode(encoded, format);
190
+ expect(decoded).toBe(testData);
191
+ }
192
+ });
193
+
194
+ it('should throw on unknown formats', () => {
195
+ expect(() => encode('test', 'unknown' as EncodingFormat)).toThrow('Unsupported encoding format');
196
+ expect(() => decode('test', 'unknown' as EncodingFormat)).toThrow('Unsupported decoding format');
197
+ });
198
+ });
199
+
200
+ describe('Format Detection', () => {
201
+ it('should detect base64', () => {
202
+ expect(detectFormat('SGVsbG8gV29ybGQ=')).toBe('base64');
203
+ expect(detectFormat('QQ==')).toBe('base64');
204
+ });
205
+
206
+ it('should detect base64url', () => {
207
+ expect(detectFormat('SGVsbG8gV29ybGQh')).toBe('base64url');
208
+ expect(detectFormat('QQ')).toBe('base64url');
209
+ });
210
+
211
+ it('should detect hex', () => {
212
+ expect(detectFormat('48656c6c6f')).toBe('hex');
213
+ expect(detectFormat('DEADBEEF')).toBe('hex');
214
+ });
215
+
216
+ it('should detect URI encoding', () => {
217
+ expect(detectFormat('Hello%20World')).toBe('uri');
218
+ expect(detectFormat('test%21%40%23')).toBe('uri');
219
+ });
220
+
221
+ it('should detect ASCII', () => {
222
+ expect(detectFormat('Hello World')).toBe('ascii');
223
+ expect(detectFormat('Plain text')).toBe('ascii');
224
+ });
225
+
226
+ it('should throw for undetectable data', () => {
227
+ expect(() => detectFormat('πŸš€πŸ’«πŸŒŸ')).toThrow('Cannot detect encoding format');
228
+ expect(() => detectFormat('\x00\x01\x02')).toThrow('Cannot detect encoding format');
229
+ });
230
+ });
231
+
232
+ describe('Format Validation', () => {
233
+ it('should validate correct formats', () => {
234
+ expect(validateFormat('SGVsbG8gV29ybGQ=', 'base64')).toBe(true);
235
+ expect(validateFormat('SGVsbG8gV29ybGQh', 'base64url')).toBe(true);
236
+ expect(validateFormat('48656c6c6f', 'hex')).toBe(true);
237
+ expect(validateFormat('Hello%20World', 'uri')).toBe(true);
238
+ expect(validateFormat('Hello World', 'ascii')).toBe(true);
239
+ });
240
+
241
+ it('should invalidate incorrect formats', () => {
242
+ expect(validateFormat('SGVsbG8gV29ybGQ', 'base64')).toBe(false); // missing padding
243
+ expect(validateFormat('SGVsbG8+V29ybGQ=', 'base64url')).toBe(false); // contains +
244
+ expect(validateFormat('48656c6c6g', 'hex')).toBe(false); // invalid char
245
+ expect(validateFormat('Hello%', 'uri')).toBe(false); // incomplete encoding
246
+ expect(validateFormat('Hello δΈ–η•Œ', 'ascii')).toBe(false); // non-ASCII
247
+ });
248
+ });
249
+ });
250
+
251
+ describe('Pure Functions - Advanced Operations', () => {
252
+ describe('Chain Operations', () => {
253
+ it('should chain encodings correctly', () => {
254
+ const original = 'Hello';
255
+
256
+ // Chain: Hello -> hex -> base64
257
+ const chained = chain(original, ['hex', 'base64']);
258
+ expect(chained).toBe('NDg2NTZjNmM2Zg==');
259
+
260
+ // Verify by manual steps
261
+ const step1 = encodeHex(original); // '48656c6c6f'
262
+ const step2 = encodeBase64(step1); // 'NDg2NTZjNmM2Zg=='
263
+ expect(chained).toBe(step2);
264
+ });
265
+
266
+ it('should reverse chain correctly', () => {
267
+ const original = 'Hello World';
268
+ const formats: EncodingFormat[] = ['hex', 'base64', 'uri'];
269
+
270
+ const chained = chain(original, formats);
271
+ const reversed = reverseChain(chained, formats);
272
+
273
+ expect(reversed).toBe(original);
274
+ });
275
+
276
+ it('should handle single operation chains', () => {
277
+ const original = 'Hello';
278
+ const chained = chain(original, ['base64']);
279
+ const reversed = reverseChain(chained, ['base64']);
280
+
281
+ expect(reversed).toBe(original);
282
+ });
283
+
284
+ it('should handle empty chains', () => {
285
+ const original = 'Hello';
286
+ const chained = chain(original, []);
287
+ const reversed = reverseChain(original, []);
288
+
289
+ expect(chained).toBe(original);
290
+ expect(reversed).toBe(original);
291
+ });
292
+
293
+ it('should propagate errors in chains', () => {
294
+ expect(() => chain('Hello δΈ–η•Œ', ['ascii', 'base64'])).toThrow('Non-ASCII character found');
295
+ expect(() => reverseChain('invalid', ['base64', 'hex'])).toThrow();
296
+ });
297
+ });
298
+
299
+ describe('Edge Cases & Error Handling', () => {
300
+ it('should handle very long strings', () => {
301
+ const longString = 'A'.repeat(10000);
302
+ const encoded = encodeBase64(longString);
303
+ const decoded = decodeBase64(encoded);
304
+ expect(decoded).toBe(longString);
305
+ });
306
+
307
+ it('should handle special characters in all formats', () => {
308
+ const special = '!@#$%^&*()_+-=[]{}|;:,.<>?';
309
+
310
+ const base64Encoded = encodeBase64(special);
311
+ expect(decodeBase64(base64Encoded)).toBe(special);
312
+
313
+ const hexEncoded = encodeHex(special);
314
+ expect(decodeHex(hexEncoded)).toBe(special);
315
+
316
+ const uriEncoded = encodeURIString(special);
317
+ expect(decodeURIString(uriEncoded)).toBe(special);
318
+ });
319
+
320
+ it('should handle binary data correctly', () => {
321
+ // Simulate binary data as string with control characters
322
+ const binaryLike = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f';
323
+
324
+ const base64Encoded = encodeBase64(binaryLike);
325
+ expect(decodeBase64(base64Encoded)).toBe(binaryLike);
326
+
327
+ const hexEncoded = encodeHex(binaryLike);
328
+ expect(decodeHex(hexEncoded)).toBe(binaryLike);
329
+ });
330
+
331
+ it('should preserve exact byte sequences', () => {
332
+ // Test all possible byte values (0-255) as string
333
+ const allBytes = Array.from({ length: 256 }, (_, i) => String.fromCharCode(i)).join('');
334
+
335
+ const base64Result = decodeBase64(encodeBase64(allBytes));
336
+ expect(base64Result).toBe(allBytes);
337
+
338
+ const hexResult = decodeHex(encodeHex(allBytes));
339
+ expect(hexResult).toBe(allBytes);
340
+ });
341
+ });
342
+
343
+ describe('Performance & Concurrency', () => {
344
+ it('should handle concurrent operations', async () => {
345
+ const testData = 'Hello World';
346
+ const promises: Promise<string>[] = [];
347
+
348
+ // Run 100 concurrent encoding operations
349
+ for (let i = 0; i < 100; i++) {
350
+ promises.push(Promise.resolve(encodeBase64(testData + i)));
351
+ }
352
+
353
+ const results = await Promise.all(promises);
354
+
355
+ // Verify all results
356
+ for (let i = 0; i < 100; i++) {
357
+ const decoded = decodeBase64(results[i]);
358
+ expect(decoded).toBe(testData + i);
359
+ }
360
+ });
361
+
362
+ it('should be stateless across calls', () => {
363
+ const data1 = 'First call';
364
+ const data2 = 'Second call';
365
+
366
+ const encoded1a = encodeBase64(data1);
367
+ const encoded2 = encodeBase64(data2);
368
+ const encoded1b = encodeBase64(data1);
369
+
370
+ // Same input should always produce same output
371
+ expect(encoded1a).toBe(encoded1b);
372
+ expect(encoded1a).not.toBe(encoded2);
373
+ });
374
+
375
+ it('should handle rapid sequential operations', () => {
376
+ const testData = 'Performance test';
377
+
378
+ // Rapid fire operations
379
+ for (let i = 0; i < 1000; i++) {
380
+ const encoded = encodeBase64(testData);
381
+ const decoded = decodeBase64(encoded);
382
+ expect(decoded).toBe(testData);
383
+ }
384
+ });
385
+ });
386
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "NodeNext", // Changed from "commonjs"
5
+ "moduleResolution": "NodeNext", // Added this line
6
+ "lib": ["ES2020"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "baseUrl": ".",
14
+ "preserveSymlinks": true,
15
+ "declaration": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "test", "**/*.test.ts", "units/**/*"]
19
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['test/*.test.ts'],
8
+ coverage: {
9
+ reporter: ['text', 'json', 'html'],
10
+ },
11
+ },
12
+ });