@anonympins/fingerprint 0.4.3 → 0.4.5

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,78 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DynamicWasmGenerator } from '../dynamic-wasm.js';
3
+
4
+ // Helper function to decode ULEB128 (for verification)
5
+ function decodeULEB128(bytes) {
6
+ let result = 0;
7
+ let shift = 0;
8
+ for (const byte of bytes) {
9
+ result |= (byte & 0x7f) << shift;
10
+ if (!(byte & 0x80)) {
11
+ break;
12
+ }
13
+ shift += 7;
14
+ }
15
+ return result;
16
+ }
17
+
18
+ describe('DynamicWasmGenerator', () => {
19
+ it('should generate a valid WASM module buffer', () => {
20
+ const constants = { seed: 123, multiplier: 33, adder: 7 };
21
+ const wasmBuffer = DynamicWasmGenerator.generate(constants);
22
+
23
+ // Basic check: ensure it's a Buffer and starts with WASM magic number
24
+ expect(wasmBuffer).toBeInstanceOf(Buffer);
25
+ expect(wasmBuffer.slice(0, 4).toString('hex')).toBe('0061736d'); // \0asm
26
+ expect(wasmBuffer.slice(4, 8).toString('hex')).toBe('01000000'); // Version 1
27
+
28
+ // Attempt to compile and instantiate the module to ensure validity
29
+ let instance;
30
+ try {
31
+ const module = new WebAssembly.Module(wasmBuffer);
32
+ instance = new WebAssembly.Instance(module, {});
33
+ } catch (e) {
34
+ // If compilation/instantiation fails, it's an invalid WASM module
35
+ expect.fail(`Generated WASM module is invalid: ${e.message}`);
36
+ }
37
+
38
+ // Verify the exported hash function exists
39
+ expect(instance.exports.hash).toBeInstanceOf(Function);
40
+ expect(instance.exports.memory).toBeInstanceOf(WebAssembly.Memory);
41
+ });
42
+
43
+ it('should produce a consistent hash result with the JS fallback', () => {
44
+ const constants = { seed: 42, multiplier: 1597334677, adder: 12345 };
45
+ const testString = "hello world";
46
+
47
+ const wasmBuffer = DynamicWasmGenerator.generate(constants);
48
+ const module = new WebAssembly.Module(wasmBuffer);
49
+ const instance = new WebAssembly.Instance(module, {});
50
+ const exports = instance.exports;
51
+ const memory = exports.memory;
52
+
53
+ // Write string to WASM memory
54
+ const encoder = new TextEncoder();
55
+ const bytes = encoder.encode(testString);
56
+ const view = new Uint8Array(memory.buffer);
57
+ view.set(bytes, 0); // Assuming hash function expects string at address 0
58
+
59
+ // Calculate hash using WASM
60
+ const wasmHash = exports.hash(0, bytes.length);
61
+
62
+ // Calculate hash using JS fallback
63
+ const jsHash = DynamicWasmGenerator.hashJs(testString, constants);
64
+
65
+ // The WASM and JS implementations should yield the same result
66
+ expect(wasmHash).toBe(jsHash);
67
+ });
68
+
69
+ it('should generate different WASM modules for different constants', () => {
70
+ const constants1 = { seed: 1, multiplier: 2, adder: 3 };
71
+ const constants2 = { seed: 4, multiplier: 5, adder: 6 };
72
+
73
+ const wasmBuffer1 = DynamicWasmGenerator.generate(constants1);
74
+ const wasmBuffer2 = DynamicWasmGenerator.generate(constants2);
75
+
76
+ expect(wasmBuffer1.toString('hex')).not.toBe(wasmBuffer2.toString('hex'));
77
+ });
78
+ });
@@ -1,120 +1,141 @@
1
- import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2
- import {JSDOM} from 'jsdom';
3
- import ClientLibrary from '../fingerprint.client.js';
4
-
5
- // --- Setup JSDOM Environment ---
6
- // Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
7
- // ici rend le test explicite et portable.
8
- const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
9
- url: 'http://localhost',
10
- });
11
-
12
- // In recent Node.js versions, some global properties like 'navigator', 'performance',
13
- // and 'screen' are read-only. To ensure our JSDOM environment works correctly across
14
- // all versions, we use Object.defineProperty to make these properties writable.
15
- Object.defineProperty(global, 'window', { value: dom.window, writable: true });
16
- Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
17
- Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
- Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
- Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
-
21
- global.fetch = vi.fn(); // Mock global fetch
22
- global.Headers = dom.window.Headers;
23
- global.Request = dom.window.Request;
24
- global.URL = dom.window.URL;
25
- global.TextEncoder = dom.window.TextEncoder;
26
-
27
-
28
- describe('ClientLibrary.initializeClient', () => {
29
-
30
- // On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
31
- let startMouseSpy, startKeystrokeSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy;
32
-
33
- beforeEach(() => {
34
- // Réinitialiser l'état du client avant chaque test
35
- ClientLibrary._resetCache();
36
-
37
- // Créer les espions sur les méthodes internes
38
- startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
39
- startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
40
- initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
41
- injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
42
- initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
43
- });
44
-
45
- afterEach(() => {
46
- // Restaurer les espions après chaque test pour ne pas affecter les autres tests
47
- vi.restoreAllMocks();
48
- });
49
-
50
- it('should enable all trackers by default', () => {
51
- ClientLibrary.initializeClient();
52
-
53
- expect(startMouseSpy).toHaveBeenCalled();
54
- expect(startKeystrokeSpy).toHaveBeenCalled();
55
- });
56
-
57
- it('should disable mouse and keystroke trackers when configured', () => {
58
- ClientLibrary.initializeClient({ mouse: false, keystrokes: false });
59
-
60
- expect(startMouseSpy).not.toHaveBeenCalled();
61
- expect(startKeystrokeSpy).not.toHaveBeenCalled();
62
- });
63
-
64
- it('should initialize honeypots with the provided field names', () => {
65
- const honeypotFields = ['email_confirm', 'user_nickname'];
66
- ClientLibrary.initializeClient({ honeypots: honeypotFields });
67
-
68
- expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
69
- });
70
-
71
- it('should not initialize honeypots if the array is empty', () => {
72
- ClientLibrary.initializeClient({ honeypots: [] });
73
-
74
- expect(initHoneypotsSpy).not.toHaveBeenCalled();
75
- });
76
-
77
- it('should inject trap URLs when provided', () => {
78
- const urls = ['/trap1?sig=123', '/trap2?sig=456'];
79
- ClientLibrary.initializeClient({ trapUrls: urls });
80
-
81
- expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
82
- });
83
-
84
- it('should not inject trap URLs if the array is empty', () => {
85
- ClientLibrary.initializeClient({ trapUrls: [] });
86
-
87
- expect(injectTrapsSpy).not.toHaveBeenCalled();
88
- });
89
-
90
- it('should initialize fetch interception when fetch config is present', () => {
91
- const targetDomains = ['api.example.com'];
92
- ClientLibrary.initializeClient({ fetch: { targetDomains } });
93
-
94
- expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
95
- });
96
-
97
- it('should not initialize fetch interception if fetch config is absent', () => {
98
- ClientLibrary.initializeClient(); // No fetch config
99
-
100
- expect(initFetchSpy).not.toHaveBeenCalled();
101
- });
102
-
103
- it('should call all initializers correctly when a full config is provided', () => {
104
- const config = {
105
- mouse: true,
106
- keystrokes: true,
107
- honeypots: ['field1'],
108
- trapUrls: ['/trap1'],
109
- fetch: { targetDomains: ['api.com'] }
110
- };
111
- ClientLibrary.initializeClient(config);
112
-
113
- expect(startMouseSpy).toHaveBeenCalled();
114
- expect(startKeystrokeSpy).toHaveBeenCalled();
115
- expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
116
- expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
117
- expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
118
- });
119
-
1
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2
+ import {JSDOM} from 'jsdom';
3
+ import ClientLibrary from '../fingerprint.client.js';
4
+
5
+ // --- Setup JSDOM Environment ---
6
+ // Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
7
+ // ici rend le test explicite et portable.
8
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
9
+ url: 'http://localhost',
10
+ });
11
+
12
+ // In recent Node.js versions, some global properties like 'navigator', 'performance',
13
+ // and 'screen' are read-only. To ensure our JSDOM environment works correctly across
14
+ // all versions, we use Object.defineProperty to make these properties writable.
15
+ Object.defineProperty(global, 'window', { value: dom.window, writable: true });
16
+ Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
17
+ Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
+ Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
+ Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
+
21
+ global.fetch = vi.fn(); // Mock global fetch
22
+ global.Headers = dom.window.Headers;
23
+ global.Request = dom.window.Request;
24
+ global.URL = dom.window.URL;
25
+ global.TextEncoder = dom.window.TextEncoder;
26
+
27
+
28
+ describe('ClientLibrary.initializeClient', () => {
29
+
30
+ // On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
31
+ let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
32
+
33
+ beforeEach(() => {
34
+ // Réinitialiser l'état du client avant chaque test
35
+ ClientLibrary._resetCache();
36
+
37
+ // Créer les espions sur les méthodes internes
38
+ startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
39
+ startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
40
+ startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
41
+ startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
42
+ initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
43
+ initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
44
+ injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
45
+ initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
46
+ injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
47
+ });
48
+
49
+ afterEach(() => {
50
+ // Restaurer les espions après chaque test pour ne pas affecter les autres tests
51
+ vi.restoreAllMocks();
52
+ });
53
+
54
+ it('should enable all trackers by default', () => {
55
+ ClientLibrary.initializeClient();
56
+
57
+ expect(startMouseSpy).toHaveBeenCalled();
58
+ expect(startKeystrokeSpy).toHaveBeenCalled();
59
+ expect(startClickSpy).toHaveBeenCalled();
60
+ expect(startTouchSpy).toHaveBeenCalled();
61
+ expect(injectPhantomTrapsSpy).toHaveBeenCalled();
62
+ });
63
+
64
+ it('should disable trackers when configured', () => {
65
+ ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false });
66
+
67
+ expect(startMouseSpy).not.toHaveBeenCalled();
68
+ expect(startKeystrokeSpy).not.toHaveBeenCalled();
69
+ expect(startClickSpy).not.toHaveBeenCalled();
70
+ expect(startTouchSpy).not.toHaveBeenCalled();
71
+ });
72
+
73
+ it('should initialize WASM if wasmPath is configured', () => {
74
+ ClientLibrary.initializeClient({ wasmPath: '/fp.js' });
75
+
76
+ expect(initWasmSpy).toHaveBeenCalledWith('/fp.js');
77
+ });
78
+
79
+ it('should disable phantom traps when configured', () => {
80
+ ClientLibrary.initializeClient({ phantomTraps: false });
81
+
82
+ expect(injectPhantomTrapsSpy).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it('should initialize honeypots with the provided field names', () => {
86
+ const honeypotFields = ['email_confirm', 'user_nickname'];
87
+ ClientLibrary.initializeClient({ honeypots: honeypotFields });
88
+
89
+ expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
90
+ });
91
+
92
+ it('should not initialize honeypots if the array is empty', () => {
93
+ ClientLibrary.initializeClient({ honeypots: [] });
94
+
95
+ expect(initHoneypotsSpy).not.toHaveBeenCalled();
96
+ });
97
+
98
+ it('should inject trap URLs when provided', () => {
99
+ const urls = ['/trap1?sig=123', '/trap2?sig=456'];
100
+ ClientLibrary.initializeClient({ trapUrls: urls });
101
+
102
+ expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
103
+ });
104
+
105
+ it('should not inject trap URLs if the array is empty', () => {
106
+ ClientLibrary.initializeClient({ trapUrls: [] });
107
+
108
+ expect(injectTrapsSpy).not.toHaveBeenCalled();
109
+ });
110
+
111
+ it('should initialize fetch interception when fetch config is present', () => {
112
+ const targetDomains = ['api.example.com'];
113
+ ClientLibrary.initializeClient({ fetch: { targetDomains } });
114
+
115
+ expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
116
+ });
117
+
118
+ it('should not initialize fetch interception if fetch config is absent', () => {
119
+ ClientLibrary.initializeClient(); // No fetch config
120
+
121
+ expect(initFetchSpy).not.toHaveBeenCalled();
122
+ });
123
+
124
+ it('should call all initializers correctly when a full config is provided', () => {
125
+ const config = {
126
+ mouse: true,
127
+ keystrokes: true,
128
+ honeypots: ['field1'],
129
+ trapUrls: ['/trap1'],
130
+ fetch: { targetDomains: ['api.com'] }
131
+ };
132
+ ClientLibrary.initializeClient(config);
133
+
134
+ expect(startMouseSpy).toHaveBeenCalled();
135
+ expect(startKeystrokeSpy).toHaveBeenCalled();
136
+ expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
137
+ expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
138
+ expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
139
+ });
140
+
120
141
  });
@@ -1,105 +1,129 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
4
-
5
- import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
6
- import ClientLibrary from '../fingerprint.client.js';
7
-
8
- describe('ClientLibrary WASM Integration', () => {
9
- beforeEach(() => {
10
- // Réinitialise le cache et les mocks avant chaque test
11
- ClientLibrary._resetCache();
12
- vi.spyOn(console, 'log').mockImplementation(() => {});
13
- vi.spyOn(console, 'warn').mockImplementation(() => {});
14
-
15
- // Supprime les mocks globaux pour éviter les fuites entre les tests
16
- delete window.createFingerprintModule;
17
- });
18
-
19
- afterEach(() => {
20
- vi.restoreAllMocks();
21
- });
22
-
23
- it('should successfully load WASM module and switch hasher', async () => {
24
- // 1. Simuler un module WASM fonctionnel
25
- const mockWasmModule = {
26
- _hash_string: vi.fn((str) => 99999), // Un mock qui retourne une valeur distincte
27
- };
28
-
29
- // 2. Simuler le chargement du script et l'initialisation du module
30
- // On attache les fonctions de simulation à `window` car c'est ce que le code cherche
31
- window.createFingerprintModule = vi.fn().mockResolvedValue(mockWasmModule);
32
-
33
- // On simule l'injection du script en appelant directement `onload`
34
- vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
35
- // Simule le chargement réussi du script
36
- script.onload();
37
- return script;
38
- });
39
-
40
- // 3. Appeler la fonction d'initialisation
41
- await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
42
-
43
- // 4. Vérifier que le hasher a été remplacé
44
- const wasmHash = ClientLibrary._hasher("test");
45
- expect(wasmHash).toBe(99999);
46
- expect(mockWasmModule._hash_string).toHaveBeenCalledWith("test");
47
- expect(console.log).toHaveBeenCalledWith(expect.stringContaining('WASM module loaded successfully'));
48
- });
49
-
50
- it('should gracefully fall back to JS hasher if WASM module fails to load', async () => {
51
- // 1. Simuler un échec de chargement du script
52
- vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
53
- script.onerror(new Error('Script loading failed'));
54
- return script;
55
- });
56
-
57
- // 2. Appeler la fonction d'initialisation
58
- await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
59
-
60
- // 3. Vérifier que le hasher est toujours l'implémentation JS
61
- const jsHash = ClientLibrary._hasher("test");
62
- const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("test");
63
-
64
- expect(jsHash).toBe(originalJsHash);
65
- expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('WASM module failed to load'), expect.any(Error));
66
- });
67
-
68
- it('should gracefully fall back if WASM module does not export _hash_string', async () => {
69
- // 1. Simuler un module WASM malformé (sans la fonction attendue)
70
- const mockWasmModule = {};
71
- window.createFingerprintModule = vi.fn().mockResolvedValue(mockWasmModule);
72
-
73
- vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
74
- script.onload();
75
- return script;
76
- });
77
-
78
- // 2. Appeler la fonction d'initialisation
79
- await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
80
-
81
- // 3. Vérifier le fallback
82
- const jsHash = ClientLibrary._hasher("test");
83
- const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("test");
84
- expect(jsHash).toBe(originalJsHash);
85
- // L'assertion est maintenant plus précise : elle vérifie le message générique ET le message d'erreur spécifique.
86
- expect(console.warn).toHaveBeenCalledWith(
87
- expect.stringContaining('WASM module failed to load'),
88
- expect.objectContaining({ message: 'WASM module did not export _hash_string.' })
89
- );
90
- });
91
-
92
- it('should use the default JS hasher if WASM is not configured', async () => {
93
- // Ensure no WASM path is provided in the config
94
- ClientLibrary.initializeClient({});
95
-
96
- // Verify that the active hasher is the original JS implementation
97
- const jsHash = ClientLibrary._hasher("another test string");
98
- const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("another test string");
99
-
100
- expect(jsHash).toBe(originalJsHash);
101
- // Ensure no WASM-related console logs or warnings were made
102
- expect(console.log).not.toHaveBeenCalledWith(expect.stringContaining('WASM module loaded successfully'));
103
- expect(console.warn).not.toHaveBeenCalledWith(expect.stringContaining('WASM module failed to load'));
104
- });
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
6
+ import ClientLibrary from '../fingerprint.client.js';
7
+
8
+ describe('ClientLibrary WASM Integration', () => {
9
+ beforeEach(() => {
10
+ // Réinitialise le cache et les mocks avant chaque test
11
+ ClientLibrary._resetCache();
12
+ vi.spyOn(console, 'log').mockImplementation(() => {});
13
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
14
+
15
+ // Supprime les mocks globaux pour éviter les fuites entre les tests
16
+ delete window.createFingerprintModule;
17
+ });
18
+
19
+ afterEach(() => {
20
+ vi.restoreAllMocks();
21
+ });
22
+
23
+ it('should successfully load WASM module and switch hasher', async () => {
24
+ // 1. Simuler un module WASM fonctionnel
25
+ const mockWasmModule = {
26
+ _hash_string: vi.fn((str) => 99999), // Un mock qui retourne une valeur distincte
27
+ };
28
+
29
+ // 2. Simuler le chargement du script et l'initialisation du module
30
+ // On attache les fonctions de simulation à `window` car c'est ce que le code cherche
31
+ window.createFingerprintModule = vi.fn().mockResolvedValue(mockWasmModule);
32
+
33
+ // On simule l'injection du script en appelant directement `onload`
34
+ vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
35
+ // Simule le chargement réussi du script
36
+ script.onload();
37
+ return script;
38
+ });
39
+
40
+ // 3. Appeler la fonction d'initialisation
41
+ await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
42
+
43
+ // 4. Vérifier que le hasher a été remplacé
44
+ const wasmHash = ClientLibrary._hasher("test");
45
+ expect(wasmHash).toBe(99999);
46
+ expect(mockWasmModule._hash_string).toHaveBeenCalledWith("test");
47
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining('WASM module loaded successfully'));
48
+ });
49
+
50
+ it('should gracefully fall back to JS hasher if WASM module fails to load', async () => {
51
+ // 1. Simuler un échec de chargement du script
52
+ vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
53
+ script.onerror(new Error('Script loading failed'));
54
+ return script;
55
+ });
56
+
57
+ // 2. Appeler la fonction d'initialisation
58
+ await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
59
+
60
+ // 3. Vérifier que le hasher est toujours l'implémentation JS
61
+ const jsHash = ClientLibrary._hasher("test");
62
+ const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("test");
63
+
64
+ expect(jsHash).toBe(originalJsHash);
65
+ expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('WASM module failed to load'), expect.any(Error));
66
+ });
67
+
68
+ it('should gracefully fall back if WASM module does not export _hash_string', async () => {
69
+ // 1. Simuler un module WASM malformé (sans la fonction attendue)
70
+ const mockWasmModule = {};
71
+ window.createFingerprintModule = vi.fn().mockResolvedValue(mockWasmModule);
72
+
73
+ vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
74
+ script.onload();
75
+ return script;
76
+ });
77
+
78
+ // 2. Appeler la fonction d'initialisation
79
+ await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
80
+
81
+ // 3. Vérifier le fallback
82
+ const jsHash = ClientLibrary._hasher("test");
83
+ const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("test");
84
+ expect(jsHash).toBe(originalJsHash);
85
+ // L'assertion est maintenant plus précise : elle vérifie le message générique ET le message d'erreur spécifique.
86
+ expect(console.warn).toHaveBeenCalledWith(
87
+ expect.stringContaining('WASM module failed to load'),
88
+ expect.objectContaining({ message: 'WASM module did not export _hash_string.' })
89
+ );
90
+ });
91
+
92
+ it('should successfully load raw WASM and use standalone polymorphic hasher', async () => {
93
+ const mockWasmInstance = {
94
+ exports: {
95
+ memory: { buffer: new ArrayBuffer(65536) },
96
+ hash: vi.fn().mockReturnValue(123456)
97
+ }
98
+ };
99
+
100
+ global.WebAssembly = {
101
+ compile: vi.fn().mockResolvedValue({}),
102
+ instantiate: vi.fn().mockResolvedValue(mockWasmInstance)
103
+ };
104
+
105
+ global.fetch = vi.fn().mockResolvedValue({
106
+ arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(100))
107
+ });
108
+
109
+ await ClientLibrary.initializeWasm('/fp.wasm');
110
+
111
+ const hash = ClientLibrary._hasher("test");
112
+ expect(hash).toBe(123456);
113
+ expect(mockWasmInstance.exports.hash).toHaveBeenCalledWith(0, 4);
114
+ });
115
+
116
+ it('should use the default JS hasher if WASM is not configured', async () => {
117
+ // Ensure no WASM path is provided in the config
118
+ ClientLibrary.initializeClient({});
119
+
120
+ // Verify that the active hasher is the original JS implementation
121
+ const jsHash = ClientLibrary._hasher("another test string");
122
+ const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("another test string");
123
+
124
+ expect(jsHash).toBe(originalJsHash);
125
+ // Ensure no WASM-related console logs or warnings were made
126
+ expect(console.log).not.toHaveBeenCalledWith(expect.stringContaining('WASM module loaded successfully'));
127
+ expect(console.warn).not.toHaveBeenCalledWith(expect.stringContaining('WASM module failed to load'));
128
+ });
105
129
  });