@egen-civitas/esm-platform 1.1.0 → 1.2.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,1264 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
2
+ try {
3
+ var info = gen[key](arg);
4
+ var value = info.value;
5
+ } catch (error) {
6
+ reject(error);
7
+ return;
8
+ }
9
+ if (info.done) {
10
+ resolve(value);
11
+ } else {
12
+ Promise.resolve(value).then(_next, _throw);
13
+ }
14
+ }
15
+ function _async_to_generator(fn) {
16
+ return function() {
17
+ var self = this, args = arguments;
18
+ return new Promise(function(resolve, reject) {
19
+ var gen = fn.apply(self, args);
20
+ function _next(value) {
21
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
22
+ }
23
+ function _throw(err) {
24
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
25
+ }
26
+ _next(undefined);
27
+ });
28
+ };
29
+ }
30
+ function _define_property(obj, key, value) {
31
+ if (key in obj) {
32
+ Object.defineProperty(obj, key, {
33
+ value: value,
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true
37
+ });
38
+ } else {
39
+ obj[key] = value;
40
+ }
41
+ return obj;
42
+ }
43
+ function _object_spread(target) {
44
+ for(var i = 1; i < arguments.length; i++){
45
+ var source = arguments[i] != null ? arguments[i] : {};
46
+ var ownKeys = Object.keys(source);
47
+ if (typeof Object.getOwnPropertySymbols === "function") {
48
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
49
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
50
+ }));
51
+ }
52
+ ownKeys.forEach(function(key) {
53
+ _define_property(target, key, source[key]);
54
+ });
55
+ }
56
+ return target;
57
+ }
58
+ function _ts_generator(thisArg, body) {
59
+ var f, y, t, _ = {
60
+ label: 0,
61
+ sent: function() {
62
+ if (t[0] & 1) throw t[1];
63
+ return t[1];
64
+ },
65
+ trys: [],
66
+ ops: []
67
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
68
+ return d(g, "next", {
69
+ value: verb(0)
70
+ }), d(g, "throw", {
71
+ value: verb(1)
72
+ }), d(g, "return", {
73
+ value: verb(2)
74
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
75
+ value: function() {
76
+ return this;
77
+ }
78
+ }), g;
79
+ function verb(n) {
80
+ return function(v) {
81
+ return step([
82
+ n,
83
+ v
84
+ ]);
85
+ };
86
+ }
87
+ function step(op) {
88
+ if (f) throw new TypeError("Generator is already executing.");
89
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
90
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
91
+ if (y = 0, t) op = [
92
+ op[0] & 2,
93
+ t.value
94
+ ];
95
+ switch(op[0]){
96
+ case 0:
97
+ case 1:
98
+ t = op;
99
+ break;
100
+ case 4:
101
+ _.label++;
102
+ return {
103
+ value: op[1],
104
+ done: false
105
+ };
106
+ case 5:
107
+ _.label++;
108
+ y = op[1];
109
+ op = [
110
+ 0
111
+ ];
112
+ continue;
113
+ case 7:
114
+ op = _.ops.pop();
115
+ _.trys.pop();
116
+ continue;
117
+ default:
118
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
119
+ _ = 0;
120
+ continue;
121
+ }
122
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
123
+ _.label = op[1];
124
+ break;
125
+ }
126
+ if (op[0] === 6 && _.label < t[1]) {
127
+ _.label = t[1];
128
+ t = op;
129
+ break;
130
+ }
131
+ if (t && _.label < t[2]) {
132
+ _.label = t[2];
133
+ _.ops.push(op);
134
+ break;
135
+ }
136
+ if (t[2]) _.ops.pop();
137
+ _.trys.pop();
138
+ continue;
139
+ }
140
+ op = body.call(thisArg, _);
141
+ } catch (e) {
142
+ op = [
143
+ 6,
144
+ e
145
+ ];
146
+ y = 0;
147
+ } finally{
148
+ f = t = 0;
149
+ }
150
+ if (op[0] & 5) throw op[1];
151
+ return {
152
+ value: op[0] ? op[1] : void 0,
153
+ done: true
154
+ };
155
+ }
156
+ }
157
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
158
+ import { PlatformError } from '../errors';
159
+ import { createMockHost } from '../testing';
160
+ import { createBridgeClient } from './client';
161
+ import { MAX_MESSAGE_LENGTH } from './protocol';
162
+ var CAPS = {
163
+ secureStorage: {
164
+ v: 1
165
+ },
166
+ lifecycle: {
167
+ v: 1
168
+ }
169
+ };
170
+ function setup() {
171
+ var hostOptions = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, clientOptions = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
172
+ var host = createMockHost(_object_spread({
173
+ capabilities: CAPS
174
+ }, hostOptions));
175
+ var client = createBridgeClient(_object_spread({
176
+ bridge: host.bridge,
177
+ environment: 'android',
178
+ framework: '3.2.0'
179
+ }, clientOptions));
180
+ return {
181
+ host: host,
182
+ client: client
183
+ };
184
+ }
185
+ function rejection(promise) {
186
+ return _async_to_generator(function() {
187
+ var error;
188
+ return _ts_generator(this, function(_state) {
189
+ switch(_state.label){
190
+ case 0:
191
+ return [
192
+ 4,
193
+ promise.then(function() {
194
+ return null;
195
+ }, function(e) {
196
+ return e;
197
+ })
198
+ ];
199
+ case 1:
200
+ error = _state.sent();
201
+ expect(error).toBeInstanceOf(PlatformError);
202
+ return [
203
+ 2,
204
+ error
205
+ ];
206
+ }
207
+ });
208
+ })();
209
+ }
210
+ describe('handshake', function() {
211
+ it('négocie la version et expose ce que le Host annonce (capacités inconnues ou invalides ignorées)', function() {
212
+ return _async_to_generator(function() {
213
+ var _setup, host, client, info;
214
+ return _ts_generator(this, function(_state) {
215
+ switch(_state.label){
216
+ case 0:
217
+ _setup = setup({
218
+ host: '2.1.0',
219
+ capabilities: {
220
+ secureStorage: {
221
+ v: 1
222
+ },
223
+ lifecycle: {
224
+ v: 3
225
+ },
226
+ futur: {
227
+ v: 1
228
+ },
229
+ files: {
230
+ v: 0
231
+ },
232
+ camera: 'x'
233
+ }
234
+ }), host = _setup.host, client = _setup.client;
235
+ return [
236
+ 4,
237
+ client.hello()
238
+ ];
239
+ case 1:
240
+ info = _state.sent();
241
+ expect(info).toEqual({
242
+ protocol: 1,
243
+ host: '2.1.0',
244
+ platform: 'android',
245
+ capabilities: {
246
+ secureStorage: {
247
+ v: 1
248
+ },
249
+ lifecycle: {
250
+ v: 3
251
+ }
252
+ }
253
+ });
254
+ expect(client.hostInfo).toBe(info);
255
+ expect(host.sent[0]).toMatchObject({
256
+ p: 'egen-host',
257
+ v: 1,
258
+ ns: 'host',
259
+ m: 'hello',
260
+ a: {
261
+ protocol: {
262
+ min: 1,
263
+ max: 1
264
+ },
265
+ framework: '3.2.0'
266
+ }
267
+ });
268
+ return [
269
+ 2
270
+ ];
271
+ }
272
+ });
273
+ })();
274
+ });
275
+ it('est mémorisé : un seul message envoyé même si hello() est appelé plusieurs fois', function() {
276
+ return _async_to_generator(function() {
277
+ var _setup, host, client;
278
+ return _ts_generator(this, function(_state) {
279
+ switch(_state.label){
280
+ case 0:
281
+ _setup = setup(), host = _setup.host, client = _setup.client;
282
+ return [
283
+ 4,
284
+ Promise.all([
285
+ client.hello(),
286
+ client.hello(),
287
+ client.hello()
288
+ ])
289
+ ];
290
+ case 1:
291
+ _state.sent();
292
+ return [
293
+ 4,
294
+ client.hello()
295
+ ];
296
+ case 2:
297
+ _state.sent();
298
+ expect(host.sent.filter(function(r) {
299
+ return r.m === 'hello';
300
+ })).toHaveLength(1);
301
+ expect(host.subscribeCount).toBe(1);
302
+ return [
303
+ 2
304
+ ];
305
+ }
306
+ });
307
+ })();
308
+ });
309
+ it('protocole du Host non supporté ⇒ INCOMPATIBLE', function() {
310
+ return _async_to_generator(function() {
311
+ var client;
312
+ return _ts_generator(this, function(_state) {
313
+ switch(_state.label){
314
+ case 0:
315
+ client = setup({
316
+ protocol: 2,
317
+ replyVersion: 1
318
+ }).client;
319
+ return [
320
+ 4,
321
+ rejection(client.hello())
322
+ ];
323
+ case 1:
324
+ expect.apply(void 0, [
325
+ _state.sent().code
326
+ ]).toBe('INCOMPATIBLE');
327
+ return [
328
+ 2
329
+ ];
330
+ }
331
+ });
332
+ })();
333
+ });
334
+ it('Host qui répond dans une version de message inconnue ⇒ INCOMPATIBLE immédiat (pas de timeout)', function() {
335
+ return _async_to_generator(function() {
336
+ var client;
337
+ return _ts_generator(this, function(_state) {
338
+ switch(_state.label){
339
+ case 0:
340
+ client = setup({
341
+ protocol: 1,
342
+ replyVersion: 7
343
+ }).client;
344
+ return [
345
+ 4,
346
+ rejection(client.hello())
347
+ ];
348
+ case 1:
349
+ expect.apply(void 0, [
350
+ _state.sent().code
351
+ ]).toBe('INCOMPATIBLE');
352
+ return [
353
+ 2
354
+ ];
355
+ }
356
+ });
357
+ })();
358
+ });
359
+ it('plateforme annoncée différente de la cible du build ⇒ INCOMPATIBLE', function() {
360
+ return _async_to_generator(function() {
361
+ var client, error;
362
+ return _ts_generator(this, function(_state) {
363
+ switch(_state.label){
364
+ case 0:
365
+ client = setup({
366
+ platform: 'ios'
367
+ }).client;
368
+ return [
369
+ 4,
370
+ rejection(client.hello())
371
+ ];
372
+ case 1:
373
+ error = _state.sent();
374
+ expect(error.code).toBe('INCOMPATIBLE');
375
+ expect(error.message).toContain('ios');
376
+ return [
377
+ 2
378
+ ];
379
+ }
380
+ });
381
+ })();
382
+ });
383
+ it.each([
384
+ [
385
+ 'null',
386
+ null
387
+ ],
388
+ [
389
+ 'tableau',
390
+ []
391
+ ],
392
+ [
393
+ 'sans version de Host',
394
+ {
395
+ protocol: 1,
396
+ platform: 'android',
397
+ capabilities: {}
398
+ }
399
+ ],
400
+ [
401
+ 'Host trop long',
402
+ {
403
+ protocol: 1,
404
+ platform: 'android',
405
+ host: 'x'.repeat(65),
406
+ capabilities: {}
407
+ }
408
+ ]
409
+ ])('réponse hello invalide (%s) ⇒ INCOMPATIBLE', function(_label, helloResult) {
410
+ return _async_to_generator(function() {
411
+ var client;
412
+ return _ts_generator(this, function(_state) {
413
+ switch(_state.label){
414
+ case 0:
415
+ client = setup({
416
+ helloResult: helloResult
417
+ }).client;
418
+ return [
419
+ 4,
420
+ rejection(client.hello())
421
+ ];
422
+ case 1:
423
+ expect.apply(void 0, [
424
+ _state.sent().code
425
+ ]).toBe('INCOMPATIBLE');
426
+ return [
427
+ 2
428
+ ];
429
+ }
430
+ });
431
+ })();
432
+ });
433
+ it('une incompatibilité est définitive : pas de nouvel envoi', function() {
434
+ return _async_to_generator(function() {
435
+ var _setup, host, client;
436
+ return _ts_generator(this, function(_state) {
437
+ switch(_state.label){
438
+ case 0:
439
+ _setup = setup({
440
+ protocol: 2
441
+ }), host = _setup.host, client = _setup.client;
442
+ return [
443
+ 4,
444
+ rejection(client.hello())
445
+ ];
446
+ case 1:
447
+ _state.sent();
448
+ return [
449
+ 4,
450
+ rejection(client.hello())
451
+ ];
452
+ case 2:
453
+ _state.sent();
454
+ expect(host.sent.filter(function(r) {
455
+ return r.m === 'hello';
456
+ })).toHaveLength(1);
457
+ return [
458
+ 2
459
+ ];
460
+ }
461
+ });
462
+ })();
463
+ });
464
+ });
465
+ describe('appels de capacités', function() {
466
+ it('exige un handshake réussi (règle 1)', function() {
467
+ return _async_to_generator(function() {
468
+ var _setup, host, client, error;
469
+ return _ts_generator(this, function(_state) {
470
+ switch(_state.label){
471
+ case 0:
472
+ _setup = setup(), host = _setup.host, client = _setup.client;
473
+ return [
474
+ 4,
475
+ rejection(client.call('secureStorage', 'get', {
476
+ key: 'k'
477
+ }))
478
+ ];
479
+ case 1:
480
+ error = _state.sent();
481
+ expect(error.code).toBe('INCOMPATIBLE');
482
+ expect(host.sent).toHaveLength(0);
483
+ return [
484
+ 2
485
+ ];
486
+ }
487
+ });
488
+ })();
489
+ });
490
+ it("refuse une capacité que le Host n'annonce pas, sans rien envoyer", function() {
491
+ return _async_to_generator(function() {
492
+ var _setup, host, client, error;
493
+ return _ts_generator(this, function(_state) {
494
+ switch(_state.label){
495
+ case 0:
496
+ _setup = setup(), host = _setup.host, client = _setup.client;
497
+ return [
498
+ 4,
499
+ client.hello()
500
+ ];
501
+ case 1:
502
+ _state.sent();
503
+ return [
504
+ 4,
505
+ rejection(client.call('camera', 'capture'))
506
+ ];
507
+ case 2:
508
+ error = _state.sent();
509
+ expect(error.code).toBe('UNAVAILABLE');
510
+ expect(host.sent).toHaveLength(1); // le hello seul
511
+ return [
512
+ 2
513
+ ];
514
+ }
515
+ });
516
+ })();
517
+ });
518
+ it('réserve le namespace "host" au handshake', function() {
519
+ return _async_to_generator(function() {
520
+ var client;
521
+ return _ts_generator(this, function(_state) {
522
+ switch(_state.label){
523
+ case 0:
524
+ client = setup().client;
525
+ return [
526
+ 4,
527
+ client.hello()
528
+ ];
529
+ case 1:
530
+ _state.sent();
531
+ return [
532
+ 4,
533
+ rejection(client.call('host', 'hello'))
534
+ ];
535
+ case 2:
536
+ expect.apply(void 0, [
537
+ _state.sent().code
538
+ ]).toBe('INVALID_ARGS');
539
+ return [
540
+ 2
541
+ ];
542
+ }
543
+ });
544
+ })();
545
+ });
546
+ it('transmet les arguments et retourne le résultat', function() {
547
+ return _async_to_generator(function() {
548
+ var _setup, host, client;
549
+ return _ts_generator(this, function(_state) {
550
+ switch(_state.label){
551
+ case 0:
552
+ _setup = setup({
553
+ handlers: {
554
+ 'secureStorage.get': function(a) {
555
+ return "valeur-de-".concat(a.key);
556
+ }
557
+ }
558
+ }), host = _setup.host, client = _setup.client;
559
+ return [
560
+ 4,
561
+ client.hello()
562
+ ];
563
+ case 1:
564
+ _state.sent();
565
+ return [
566
+ 4,
567
+ expect(client.call('secureStorage', 'get', {
568
+ key: 'jeton'
569
+ })).resolves.toBe('valeur-de-jeton')
570
+ ];
571
+ case 2:
572
+ _state.sent();
573
+ expect(host.sent[1]).toMatchObject({
574
+ ns: 'secureStorage',
575
+ m: 'get',
576
+ a: {
577
+ key: 'jeton'
578
+ }
579
+ });
580
+ return [
581
+ 2
582
+ ];
583
+ }
584
+ });
585
+ })();
586
+ });
587
+ it('un appel sans argument n’envoie pas de champ `a`', function() {
588
+ return _async_to_generator(function() {
589
+ var _setup, host, client;
590
+ return _ts_generator(this, function(_state) {
591
+ switch(_state.label){
592
+ case 0:
593
+ _setup = setup({
594
+ handlers: {
595
+ 'secureStorage.get': function() {
596
+ return null;
597
+ }
598
+ }
599
+ }), host = _setup.host, client = _setup.client;
600
+ return [
601
+ 4,
602
+ client.hello()
603
+ ];
604
+ case 1:
605
+ _state.sent();
606
+ return [
607
+ 4,
608
+ client.call('secureStorage', 'get')
609
+ ];
610
+ case 2:
611
+ _state.sent();
612
+ expect(host.sent[1]).not.toHaveProperty('a');
613
+ return [
614
+ 2
615
+ ];
616
+ }
617
+ });
618
+ })();
619
+ });
620
+ it('convertit une erreur du Host en PlatformError (code connu conservé, retryable repris)', function() {
621
+ return _async_to_generator(function() {
622
+ var client, error;
623
+ return _ts_generator(this, function(_state) {
624
+ switch(_state.label){
625
+ case 0:
626
+ client = setup({
627
+ handlers: {
628
+ 'secureStorage.get': function() {
629
+ throw Object.assign(new Error('refusé par l’utilisateur'), {
630
+ code: 'DENIED'
631
+ });
632
+ }
633
+ }
634
+ }).client;
635
+ return [
636
+ 4,
637
+ client.hello()
638
+ ];
639
+ case 1:
640
+ _state.sent();
641
+ return [
642
+ 4,
643
+ rejection(client.call('secureStorage', 'get', {}))
644
+ ];
645
+ case 2:
646
+ error = _state.sent();
647
+ expect(error).toMatchObject({
648
+ code: 'DENIED',
649
+ message: 'refusé par l’utilisateur',
650
+ capability: 'secureStorage',
651
+ method: 'get',
652
+ retryable: false
653
+ });
654
+ return [
655
+ 2
656
+ ];
657
+ }
658
+ });
659
+ })();
660
+ });
661
+ it('code d’erreur inconnu ⇒ HOST_ERROR ; message tronqué ; contenu non fiable jamais propagé tel quel', function() {
662
+ return _async_to_generator(function() {
663
+ var client, error;
664
+ return _ts_generator(this, function(_state) {
665
+ switch(_state.label){
666
+ case 0:
667
+ client = setup({
668
+ handlers: {
669
+ 'secureStorage.get': function() {
670
+ throw Object.assign(new Error('x'.repeat(2000)), {
671
+ code: 'PIRATE'
672
+ });
673
+ }
674
+ }
675
+ }).client;
676
+ return [
677
+ 4,
678
+ client.hello()
679
+ ];
680
+ case 1:
681
+ _state.sent();
682
+ return [
683
+ 4,
684
+ rejection(client.call('secureStorage', 'get', {}))
685
+ ];
686
+ case 2:
687
+ error = _state.sent();
688
+ expect(error.code).toBe('HOST_ERROR');
689
+ expect(error.message.length).toBeLessThanOrEqual(500);
690
+ return [
691
+ 2
692
+ ];
693
+ }
694
+ });
695
+ })();
696
+ });
697
+ it('les réponses arrivant dans le désordre sont associées au bon appel', function() {
698
+ return _async_to_generator(function() {
699
+ var releases, gate, client, first, second;
700
+ return _ts_generator(this, function(_state) {
701
+ switch(_state.label){
702
+ case 0:
703
+ releases = {};
704
+ gate = function gate(name) {
705
+ return new Promise(function(resolve) {
706
+ releases[name] = function() {
707
+ return resolve(name);
708
+ };
709
+ });
710
+ };
711
+ client = setup({
712
+ handlers: {
713
+ 'secureStorage.get': function(a) {
714
+ return gate(a.key);
715
+ }
716
+ }
717
+ }).client;
718
+ return [
719
+ 4,
720
+ client.hello()
721
+ ];
722
+ case 1:
723
+ _state.sent();
724
+ first = client.call('secureStorage', 'get', {
725
+ key: 'premier'
726
+ });
727
+ second = client.call('secureStorage', 'get', {
728
+ key: 'second'
729
+ });
730
+ return [
731
+ 4,
732
+ vi.waitFor(function() {
733
+ return expect(Object.keys(releases)).toHaveLength(2);
734
+ })
735
+ ];
736
+ case 2:
737
+ _state.sent();
738
+ releases.second();
739
+ releases.premier();
740
+ return [
741
+ 4,
742
+ expect(Promise.all([
743
+ first,
744
+ second
745
+ ])).resolves.toEqual([
746
+ 'premier',
747
+ 'second'
748
+ ])
749
+ ];
750
+ case 3:
751
+ _state.sent();
752
+ return [
753
+ 2
754
+ ];
755
+ }
756
+ });
757
+ })();
758
+ });
759
+ it('64 appels simultanés : chacun reçoit SA réponse, plus rien en vol', function() {
760
+ return _async_to_generator(function() {
761
+ var client, results;
762
+ return _ts_generator(this, function(_state) {
763
+ switch(_state.label){
764
+ case 0:
765
+ client = setup({
766
+ handlers: {
767
+ 'secureStorage.get': function(a) {
768
+ return a.n * 2;
769
+ }
770
+ }
771
+ }).client;
772
+ return [
773
+ 4,
774
+ client.hello()
775
+ ];
776
+ case 1:
777
+ _state.sent();
778
+ return [
779
+ 4,
780
+ Promise.all(Array.from({
781
+ length: 64
782
+ }, function(_, n) {
783
+ return client.call('secureStorage', 'get', {
784
+ n: n
785
+ });
786
+ }))
787
+ ];
788
+ case 2:
789
+ results = _state.sent();
790
+ expect(results).toEqual(Array.from({
791
+ length: 64
792
+ }, function(_, n) {
793
+ return n * 2;
794
+ }));
795
+ expect(client.stats().inFlight).toBe(0);
796
+ return [
797
+ 2
798
+ ];
799
+ }
800
+ });
801
+ })();
802
+ });
803
+ it('les identifiants de requête sont uniques', function() {
804
+ return _async_to_generator(function() {
805
+ var _setup, host, client, ids;
806
+ return _ts_generator(this, function(_state) {
807
+ switch(_state.label){
808
+ case 0:
809
+ _setup = setup({
810
+ handlers: {
811
+ 'secureStorage.get': function() {
812
+ return 1;
813
+ }
814
+ }
815
+ }), host = _setup.host, client = _setup.client;
816
+ return [
817
+ 4,
818
+ client.hello()
819
+ ];
820
+ case 1:
821
+ _state.sent();
822
+ return [
823
+ 4,
824
+ Promise.all(Array.from({
825
+ length: 50
826
+ }, function() {
827
+ return client.call('secureStorage', 'get');
828
+ }))
829
+ ];
830
+ case 2:
831
+ _state.sent();
832
+ ids = host.sent.map(function(r) {
833
+ return r.id;
834
+ });
835
+ expect(new Set(ids).size).toBe(ids.length);
836
+ return [
837
+ 2
838
+ ];
839
+ }
840
+ });
841
+ })();
842
+ });
843
+ });
844
+ describe('validation des arguments (avant tout envoi)', function() {
845
+ it.each([
846
+ [
847
+ 'namespace invalide',
848
+ 'secure-storage',
849
+ 'get'
850
+ ],
851
+ [
852
+ 'méthode invalide',
853
+ 'secureStorage',
854
+ 'get;drop'
855
+ ],
856
+ [
857
+ 'méthode vide',
858
+ 'secureStorage',
859
+ ''
860
+ ],
861
+ [
862
+ 'commence par un chiffre',
863
+ 'secureStorage',
864
+ '1get'
865
+ ]
866
+ ])('%s ⇒ INVALID_ARGS', function(_label, ns, m) {
867
+ return _async_to_generator(function() {
868
+ var _setup, host, client;
869
+ return _ts_generator(this, function(_state) {
870
+ switch(_state.label){
871
+ case 0:
872
+ _setup = setup(), host = _setup.host, client = _setup.client;
873
+ return [
874
+ 4,
875
+ client.hello()
876
+ ];
877
+ case 1:
878
+ _state.sent();
879
+ return [
880
+ 4,
881
+ rejection(client.call(ns, m))
882
+ ];
883
+ case 2:
884
+ expect.apply(void 0, [
885
+ _state.sent().code
886
+ ]).toBe('INVALID_ARGS');
887
+ expect(host.sent).toHaveLength(1);
888
+ return [
889
+ 2
890
+ ];
891
+ }
892
+ });
893
+ })();
894
+ });
895
+ it('arguments non sérialisables (référence circulaire) ⇒ INVALID_ARGS', function() {
896
+ return _async_to_generator(function() {
897
+ var _setup, host, client, circular;
898
+ return _ts_generator(this, function(_state) {
899
+ switch(_state.label){
900
+ case 0:
901
+ _setup = setup(), host = _setup.host, client = _setup.client;
902
+ return [
903
+ 4,
904
+ client.hello()
905
+ ];
906
+ case 1:
907
+ _state.sent();
908
+ circular = {};
909
+ circular.self = circular;
910
+ return [
911
+ 4,
912
+ rejection(client.call('secureStorage', 'set', circular))
913
+ ];
914
+ case 2:
915
+ expect.apply(void 0, [
916
+ _state.sent().code
917
+ ]).toBe('INVALID_ARGS');
918
+ expect(host.sent).toHaveLength(1);
919
+ return [
920
+ 2
921
+ ];
922
+ }
923
+ });
924
+ })();
925
+ });
926
+ it('message trop volumineux ⇒ INVALID_ARGS (référence de fichier attendue)', function() {
927
+ return _async_to_generator(function() {
928
+ var _setup, host, client, error;
929
+ return _ts_generator(this, function(_state) {
930
+ switch(_state.label){
931
+ case 0:
932
+ _setup = setup(), host = _setup.host, client = _setup.client;
933
+ return [
934
+ 4,
935
+ client.hello()
936
+ ];
937
+ case 1:
938
+ _state.sent();
939
+ return [
940
+ 4,
941
+ rejection(client.call('secureStorage', 'set', {
942
+ v: 'x'.repeat(MAX_MESSAGE_LENGTH)
943
+ }))
944
+ ];
945
+ case 2:
946
+ error = _state.sent();
947
+ expect(error.code).toBe('INVALID_ARGS');
948
+ expect(error.message).toContain('référence de fichier');
949
+ expect(host.sent).toHaveLength(1);
950
+ return [
951
+ 2
952
+ ];
953
+ }
954
+ });
955
+ })();
956
+ });
957
+ it('échec de bridge.send ⇒ HOST_ERROR, sans appel en vol résiduel', function() {
958
+ return _async_to_generator(function() {
959
+ var host, client, bridge, failing, error;
960
+ return _ts_generator(this, function(_state) {
961
+ switch(_state.label){
962
+ case 0:
963
+ host = createMockHost({
964
+ capabilities: CAPS
965
+ });
966
+ client = createBridgeClient({
967
+ bridge: host.bridge,
968
+ environment: 'android'
969
+ });
970
+ return [
971
+ 4,
972
+ client.hello()
973
+ ];
974
+ case 1:
975
+ _state.sent();
976
+ bridge = {
977
+ send: function send() {
978
+ throw new Error('WebView détruite');
979
+ },
980
+ subscribe: host.bridge.subscribe
981
+ };
982
+ failing = createBridgeClient({
983
+ bridge: bridge,
984
+ environment: 'android'
985
+ });
986
+ return [
987
+ 4,
988
+ rejection(failing.hello())
989
+ ];
990
+ case 2:
991
+ error = _state.sent();
992
+ expect(error.code).toBe('HOST_ERROR');
993
+ expect(error.message).toContain('WebView détruite');
994
+ expect(failing.stats().inFlight).toBe(0);
995
+ return [
996
+ 2
997
+ ];
998
+ }
999
+ });
1000
+ })();
1001
+ });
1002
+ });
1003
+ describe('délais', function() {
1004
+ beforeEach(function() {
1005
+ return vi.useFakeTimers();
1006
+ });
1007
+ afterEach(function() {
1008
+ return vi.useRealTimers();
1009
+ });
1010
+ it('hello : TIMEOUT rejouable, puis un second essai peut réussir', function() {
1011
+ return _async_to_generator(function() {
1012
+ var host, client, first, error, retry;
1013
+ return _ts_generator(this, function(_state) {
1014
+ switch(_state.label){
1015
+ case 0:
1016
+ host = createMockHost({
1017
+ capabilities: CAPS,
1018
+ respond: false
1019
+ });
1020
+ client = createBridgeClient({
1021
+ bridge: host.bridge,
1022
+ environment: 'android',
1023
+ timeoutMs: 1000
1024
+ });
1025
+ first = rejection(client.hello());
1026
+ return [
1027
+ 4,
1028
+ vi.advanceTimersByTimeAsync(1000)
1029
+ ];
1030
+ case 1:
1031
+ _state.sent();
1032
+ return [
1033
+ 4,
1034
+ first
1035
+ ];
1036
+ case 2:
1037
+ error = _state.sent();
1038
+ expect(error).toMatchObject({
1039
+ code: 'TIMEOUT',
1040
+ retryable: true
1041
+ });
1042
+ // Le Host « se réveille » : un nouvel appel doit renvoyer un hello (échec passager non mémorisé).
1043
+ retry = client.hello();
1044
+ retry.catch(function() {});
1045
+ expect(host.sent.filter(function(r) {
1046
+ return r.m === 'hello';
1047
+ })).toHaveLength(2);
1048
+ return [
1049
+ 4,
1050
+ vi.advanceTimersByTimeAsync(1000)
1051
+ ];
1052
+ case 3:
1053
+ _state.sent();
1054
+ return [
1055
+ 2
1056
+ ];
1057
+ }
1058
+ });
1059
+ })();
1060
+ });
1061
+ it('appel : TIMEOUT retryable, délai par appel prioritaire, réponse tardive ignorée', function() {
1062
+ return _async_to_generator(function() {
1063
+ var host, client, hello, pendingCall;
1064
+ return _ts_generator(this, function(_state) {
1065
+ switch(_state.label){
1066
+ case 0:
1067
+ host = createMockHost({
1068
+ capabilities: CAPS,
1069
+ latencyMs: 5000,
1070
+ handlers: {
1071
+ 'secureStorage.get': function() {
1072
+ return 'tard';
1073
+ }
1074
+ }
1075
+ });
1076
+ client = createBridgeClient({
1077
+ bridge: host.bridge,
1078
+ environment: 'android',
1079
+ timeoutMs: 60000
1080
+ });
1081
+ hello = client.hello();
1082
+ return [
1083
+ 4,
1084
+ vi.advanceTimersByTimeAsync(5000)
1085
+ ];
1086
+ case 1:
1087
+ _state.sent();
1088
+ return [
1089
+ 4,
1090
+ hello
1091
+ ];
1092
+ case 2:
1093
+ _state.sent();
1094
+ pendingCall = rejection(client.call('secureStorage', 'get', {}, {
1095
+ timeoutMs: 100
1096
+ }));
1097
+ return [
1098
+ 4,
1099
+ vi.advanceTimersByTimeAsync(100)
1100
+ ];
1101
+ case 3:
1102
+ _state.sent();
1103
+ return [
1104
+ 4,
1105
+ pendingCall
1106
+ ];
1107
+ case 4:
1108
+ expect.apply(void 0, [
1109
+ _state.sent()
1110
+ ]).toMatchObject({
1111
+ code: 'TIMEOUT',
1112
+ retryable: true,
1113
+ capability: 'secureStorage',
1114
+ method: 'get'
1115
+ });
1116
+ expect(client.stats().inFlight).toBe(0);
1117
+ return [
1118
+ 4,
1119
+ vi.advanceTimersByTimeAsync(5000)
1120
+ ];
1121
+ case 5:
1122
+ _state.sent(); // la réponse arrive après expiration
1123
+ expect(client.stats().ignoredMessages).toBe(1);
1124
+ return [
1125
+ 2
1126
+ ];
1127
+ }
1128
+ });
1129
+ })();
1130
+ });
1131
+ });
1132
+ describe('messages entrants', function() {
1133
+ it('ignore et compte tout ce qui est invalide, orphelin ou de version inconnue — sans effet de bord', function() {
1134
+ return _async_to_generator(function() {
1135
+ var _setup, host, client, before;
1136
+ return _ts_generator(this, function(_state) {
1137
+ switch(_state.label){
1138
+ case 0:
1139
+ _setup = setup(), host = _setup.host, client = _setup.client;
1140
+ return [
1141
+ 4,
1142
+ client.hello()
1143
+ ];
1144
+ case 1:
1145
+ _state.sent();
1146
+ before = client.stats().ignoredMessages;
1147
+ host.inject('{pas du json');
1148
+ host.inject(JSON.stringify({
1149
+ p: 'autre',
1150
+ v: 1,
1151
+ id: 'x',
1152
+ ok: true
1153
+ }));
1154
+ host.inject(JSON.stringify({
1155
+ p: 'egen-host',
1156
+ v: 1,
1157
+ id: 'inconnu',
1158
+ ok: true,
1159
+ r: 1
1160
+ })); // orpheline
1161
+ host.inject(JSON.stringify({
1162
+ p: 'egen-host',
1163
+ v: 9,
1164
+ ev: 'lifecycle.resume'
1165
+ })); // événement de version inconnue
1166
+ host.inject('x'.repeat(MAX_MESSAGE_LENGTH + 1));
1167
+ expect(client.stats().ignoredMessages - before).toBe(5);
1168
+ expect(client.stats().inFlight).toBe(0);
1169
+ return [
1170
+ 2
1171
+ ];
1172
+ }
1173
+ });
1174
+ })();
1175
+ });
1176
+ });
1177
+ describe('événements', function() {
1178
+ it('route un événement à ses abonnés, avec sa donnée', function() {
1179
+ return _async_to_generator(function() {
1180
+ var _setup, host, client, resume, other;
1181
+ return _ts_generator(this, function(_state) {
1182
+ switch(_state.label){
1183
+ case 0:
1184
+ _setup = setup(), host = _setup.host, client = _setup.client;
1185
+ return [
1186
+ 4,
1187
+ client.hello()
1188
+ ];
1189
+ case 1:
1190
+ _state.sent();
1191
+ resume = vi.fn();
1192
+ other = vi.fn();
1193
+ client.onEvent('lifecycle.resume', resume);
1194
+ client.onEvent('lifecycle.pause', other);
1195
+ host.emit('lifecycle.resume', {
1196
+ at: 1
1197
+ });
1198
+ expect(resume).toHaveBeenCalledWith({
1199
+ at: 1
1200
+ });
1201
+ expect(other).not.toHaveBeenCalled();
1202
+ return [
1203
+ 2
1204
+ ];
1205
+ }
1206
+ });
1207
+ })();
1208
+ });
1209
+ it('se désabonne proprement', function() {
1210
+ return _async_to_generator(function() {
1211
+ var _setup, host, client, callback, unsubscribe;
1212
+ return _ts_generator(this, function(_state) {
1213
+ switch(_state.label){
1214
+ case 0:
1215
+ _setup = setup(), host = _setup.host, client = _setup.client;
1216
+ return [
1217
+ 4,
1218
+ client.hello()
1219
+ ];
1220
+ case 1:
1221
+ _state.sent();
1222
+ callback = vi.fn();
1223
+ unsubscribe = client.onEvent('lifecycle.resume', callback);
1224
+ unsubscribe();
1225
+ host.emit('lifecycle.resume');
1226
+ expect(callback).not.toHaveBeenCalled();
1227
+ return [
1228
+ 2
1229
+ ];
1230
+ }
1231
+ });
1232
+ })();
1233
+ });
1234
+ it("un abonné qui lève n'empêche ni les autres abonnés ni la suite", function() {
1235
+ return _async_to_generator(function() {
1236
+ var _setup, host, client, error, healthy;
1237
+ return _ts_generator(this, function(_state) {
1238
+ switch(_state.label){
1239
+ case 0:
1240
+ _setup = setup(), host = _setup.host, client = _setup.client;
1241
+ return [
1242
+ 4,
1243
+ client.hello()
1244
+ ];
1245
+ case 1:
1246
+ _state.sent();
1247
+ error = vi.spyOn(console, 'error').mockImplementation(function() {});
1248
+ healthy = vi.fn();
1249
+ client.onEvent('lifecycle.resume', function() {
1250
+ throw new Error('bug applicatif');
1251
+ });
1252
+ client.onEvent('lifecycle.resume', healthy);
1253
+ host.emit('lifecycle.resume');
1254
+ expect(healthy).toHaveBeenCalledTimes(1);
1255
+ expect(error).toHaveBeenCalled();
1256
+ error.mockRestore();
1257
+ return [
1258
+ 2
1259
+ ];
1260
+ }
1261
+ });
1262
+ })();
1263
+ });
1264
+ });