@auto-engineer/narrative 1.88.0 → 1.90.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,850 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { Model } from './schema';
3
+ import { validateSliceRequests } from './validate-slice-requests';
4
+
5
+ function emptyModel(overrides: Partial<Model> = {}): Model {
6
+ return {
7
+ variant: 'specs',
8
+ narratives: [],
9
+ messages: [],
10
+ modules: [],
11
+ ...overrides,
12
+ };
13
+ }
14
+
15
+ describe('validateSliceRequests', () => {
16
+ describe('burst 1: parse safety', () => {
17
+ it('returns empty array for empty model', () => {
18
+ expect(validateSliceRequests(emptyModel())).toEqual([]);
19
+ });
20
+
21
+ it('skips slices without request', () => {
22
+ const model = emptyModel({
23
+ narratives: [
24
+ {
25
+ name: 'TestFlow',
26
+ slices: [
27
+ {
28
+ type: 'command',
29
+ name: 'DoThing',
30
+ client: { specs: [] },
31
+ server: { description: 'desc', specs: [] },
32
+ },
33
+ ],
34
+ },
35
+ ],
36
+ });
37
+ expect(validateSliceRequests(model)).toEqual([]);
38
+ });
39
+
40
+ it('skips slices with empty string request', () => {
41
+ const model = emptyModel({
42
+ narratives: [
43
+ {
44
+ name: 'TestFlow',
45
+ slices: [
46
+ {
47
+ type: 'command',
48
+ name: 'DoThing',
49
+ request: '',
50
+ client: { specs: [] },
51
+ server: { description: 'desc', specs: [] },
52
+ },
53
+ ],
54
+ },
55
+ ],
56
+ });
57
+ expect(validateSliceRequests(model)).toEqual([]);
58
+ });
59
+
60
+ it('returns request_parse_error for invalid syntax', () => {
61
+ const model = emptyModel({
62
+ narratives: [
63
+ {
64
+ name: 'TestFlow',
65
+ slices: [
66
+ {
67
+ type: 'command',
68
+ name: 'DoThing',
69
+ request: 'not valid graphql {{{',
70
+ client: { specs: [] },
71
+ server: { description: 'desc', specs: [] },
72
+ },
73
+ ],
74
+ },
75
+ ],
76
+ });
77
+ expect(validateSliceRequests(model)).toEqual([
78
+ {
79
+ type: 'request_parse_error',
80
+ message: expect.any(String),
81
+ flowName: 'TestFlow',
82
+ sliceName: 'DoThing',
83
+ },
84
+ ]);
85
+ });
86
+
87
+ it('returns request_parse_error for anonymous operation', () => {
88
+ const model = emptyModel({
89
+ narratives: [
90
+ {
91
+ name: 'TestFlow',
92
+ slices: [
93
+ {
94
+ type: 'command',
95
+ name: 'DoThing',
96
+ request: 'mutation { doThing { success } }',
97
+ client: { specs: [] },
98
+ server: { description: 'desc', specs: [] },
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ });
104
+ expect(validateSliceRequests(model)).toEqual([
105
+ {
106
+ type: 'request_parse_error',
107
+ message: 'Operation must have a name',
108
+ flowName: 'TestFlow',
109
+ sliceName: 'DoThing',
110
+ },
111
+ ]);
112
+ });
113
+
114
+ it('returns request_parse_error when no operation definition found', () => {
115
+ const model = emptyModel({
116
+ narratives: [
117
+ {
118
+ name: 'TestFlow',
119
+ slices: [
120
+ {
121
+ type: 'command',
122
+ name: 'DoThing',
123
+ request: 'fragment F on User { id }',
124
+ client: { specs: [] },
125
+ server: { description: 'desc', specs: [] },
126
+ },
127
+ ],
128
+ },
129
+ ],
130
+ });
131
+ expect(validateSliceRequests(model)).toEqual([
132
+ {
133
+ type: 'request_parse_error',
134
+ message: 'No operation found in request',
135
+ flowName: 'TestFlow',
136
+ sliceName: 'DoThing',
137
+ },
138
+ ]);
139
+ });
140
+
141
+ it('parses JSON AST format without error', () => {
142
+ const sdl = 'mutation DoThing($input: DoThingInput!) { doThing(input: $input) { success } }';
143
+ const { parse } = require('graphql');
144
+ const ast = parse(sdl);
145
+ const jsonAst = JSON.stringify(ast);
146
+
147
+ const model = emptyModel({
148
+ narratives: [
149
+ {
150
+ name: 'TestFlow',
151
+ slices: [
152
+ {
153
+ type: 'command',
154
+ name: 'DoThing',
155
+ request: jsonAst,
156
+ client: { specs: [] },
157
+ server: { description: 'desc', specs: [] },
158
+ },
159
+ ],
160
+ messages: [],
161
+ },
162
+ ],
163
+ messages: [
164
+ {
165
+ type: 'command',
166
+ name: 'DoThing',
167
+ fields: [],
168
+ },
169
+ ],
170
+ });
171
+ const errors = validateSliceRequests(model);
172
+ const parseErrors = errors.filter((e) => e.type === 'request_parse_error');
173
+ expect(parseErrors).toEqual([]);
174
+ });
175
+ });
176
+
177
+ describe('burst 2: mutation validation', () => {
178
+ it('returns mutation_wrong_operation_type when command slice uses query', () => {
179
+ const model = emptyModel({
180
+ narratives: [
181
+ {
182
+ name: 'TestFlow',
183
+ slices: [
184
+ {
185
+ type: 'command',
186
+ name: 'DoThing',
187
+ request: 'query DoThing { doThing { id } }',
188
+ client: { specs: [] },
189
+ server: { description: 'desc', specs: [] },
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ });
195
+ expect(validateSliceRequests(model)).toEqual([
196
+ {
197
+ type: 'mutation_wrong_operation_type',
198
+ message: "Command slice 'DoThing' request should be a mutation, but found query",
199
+ flowName: 'TestFlow',
200
+ sliceName: 'DoThing',
201
+ },
202
+ ]);
203
+ });
204
+
205
+ it('returns mutation_missing_input_arg when no $input variable', () => {
206
+ const model = emptyModel({
207
+ narratives: [
208
+ {
209
+ name: 'TestFlow',
210
+ slices: [
211
+ {
212
+ type: 'command',
213
+ name: 'DoThing',
214
+ request: 'mutation DoThing($name: String!) { doThing(name: $name) { success } }',
215
+ client: { specs: [] },
216
+ server: { description: 'desc', specs: [] },
217
+ },
218
+ ],
219
+ },
220
+ ],
221
+ });
222
+ expect(validateSliceRequests(model)).toEqual([
223
+ {
224
+ type: 'mutation_missing_input_arg',
225
+ message: "Mutation 'DoThing' is missing required $input variable",
226
+ flowName: 'TestFlow',
227
+ sliceName: 'DoThing',
228
+ },
229
+ ]);
230
+ });
231
+
232
+ it('returns mutation_input_type_mismatch when input type does not match', () => {
233
+ const model = emptyModel({
234
+ narratives: [
235
+ {
236
+ name: 'TestFlow',
237
+ slices: [
238
+ {
239
+ type: 'command',
240
+ name: 'DoThing',
241
+ request: 'mutation DoThing($input: WrongInput!) { doThing(input: $input) { success } }',
242
+ client: { specs: [] },
243
+ server: { description: 'desc', specs: [] },
244
+ },
245
+ ],
246
+ },
247
+ ],
248
+ });
249
+ expect(validateSliceRequests(model)).toEqual([
250
+ {
251
+ type: 'mutation_input_type_mismatch',
252
+ message: "Mutation 'DoThing' input type should be 'DoThingInput', but found 'WrongInput'",
253
+ flowName: 'TestFlow',
254
+ sliceName: 'DoThing',
255
+ },
256
+ ]);
257
+ });
258
+
259
+ it('returns mutation_message_not_found when command not in messages', () => {
260
+ const model = emptyModel({
261
+ narratives: [
262
+ {
263
+ name: 'TestFlow',
264
+ slices: [
265
+ {
266
+ type: 'command',
267
+ name: 'DoThing',
268
+ request: 'mutation DoThing($input: DoThingInput!) { doThing(input: $input) { success } }',
269
+ client: { specs: [] },
270
+ server: { description: 'desc', specs: [] },
271
+ },
272
+ ],
273
+ },
274
+ ],
275
+ messages: [],
276
+ });
277
+ expect(validateSliceRequests(model)).toEqual([
278
+ {
279
+ type: 'mutation_message_not_found',
280
+ message: "No command message 'DoThing' found in model.messages",
281
+ flowName: 'TestFlow',
282
+ sliceName: 'DoThing',
283
+ },
284
+ ]);
285
+ });
286
+
287
+ it('returns no errors for valid mutation', () => {
288
+ const model = emptyModel({
289
+ narratives: [
290
+ {
291
+ name: 'TestFlow',
292
+ slices: [
293
+ {
294
+ type: 'command',
295
+ name: 'DoThing',
296
+ request: 'mutation DoThing($input: DoThingInput!) { doThing(input: $input) { success } }',
297
+ client: { specs: [] },
298
+ server: { description: 'desc', specs: [] },
299
+ },
300
+ ],
301
+ },
302
+ ],
303
+ messages: [
304
+ {
305
+ type: 'command',
306
+ name: 'DoThing',
307
+ fields: [{ name: 'value', type: 'string', required: true }],
308
+ },
309
+ ],
310
+ });
311
+ expect(validateSliceRequests(model)).toEqual([]);
312
+ });
313
+ });
314
+
315
+ describe('burst 3: query validation — operation type, state, top-level fields', () => {
316
+ it('returns query_wrong_operation_type when query slice uses mutation', () => {
317
+ const model = emptyModel({
318
+ narratives: [
319
+ {
320
+ name: 'TestFlow',
321
+ slices: [
322
+ {
323
+ type: 'query',
324
+ name: 'GetThing',
325
+ request: 'mutation GetThing($input: GetThingInput!) { getThing(input: $input) { success } }',
326
+ client: { specs: [] },
327
+ server: { description: 'desc', specs: [] },
328
+ },
329
+ ],
330
+ },
331
+ ],
332
+ });
333
+ expect(validateSliceRequests(model)).toEqual([
334
+ {
335
+ type: 'query_wrong_operation_type',
336
+ message: "Query slice 'GetThing' request should be a query, but found mutation",
337
+ flowName: 'TestFlow',
338
+ sliceName: 'GetThing',
339
+ },
340
+ ]);
341
+ });
342
+
343
+ it('returns query_state_not_found when target state not in messages', () => {
344
+ const model = emptyModel({
345
+ narratives: [
346
+ {
347
+ name: 'TestFlow',
348
+ slices: [
349
+ {
350
+ type: 'query',
351
+ name: 'GetThing',
352
+ request: 'query GetThing { getThing { id name } }',
353
+ client: { specs: [] },
354
+ server: {
355
+ description: 'desc',
356
+ data: {
357
+ items: [
358
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
359
+ ],
360
+ },
361
+ specs: [],
362
+ },
363
+ },
364
+ ],
365
+ },
366
+ ],
367
+ messages: [],
368
+ });
369
+ expect(validateSliceRequests(model)).toEqual([
370
+ {
371
+ type: 'query_state_not_found',
372
+ message: "State 'ThingState' referenced by query 'GetThing' not found in model.messages",
373
+ flowName: 'TestFlow',
374
+ sliceName: 'GetThing',
375
+ },
376
+ ]);
377
+ });
378
+
379
+ it('returns query_field_not_found when selecting field not on state', () => {
380
+ const model = emptyModel({
381
+ narratives: [
382
+ {
383
+ name: 'TestFlow',
384
+ slices: [
385
+ {
386
+ type: 'query',
387
+ name: 'GetThing',
388
+ request: 'query GetThing { getThing { id name missing } }',
389
+ client: { specs: [] },
390
+ server: {
391
+ description: 'desc',
392
+ data: {
393
+ items: [
394
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
395
+ ],
396
+ },
397
+ specs: [],
398
+ },
399
+ },
400
+ ],
401
+ },
402
+ ],
403
+ messages: [
404
+ {
405
+ type: 'state',
406
+ name: 'ThingState',
407
+ fields: [
408
+ { name: 'id', type: 'string', required: true },
409
+ { name: 'name', type: 'string', required: true },
410
+ ],
411
+ },
412
+ ],
413
+ });
414
+ expect(validateSliceRequests(model)).toEqual([
415
+ {
416
+ type: 'query_field_not_found',
417
+ message: "Field 'missing' in query 'GetThing' not found on state 'ThingState'",
418
+ flowName: 'TestFlow',
419
+ sliceName: 'GetThing',
420
+ },
421
+ ]);
422
+ });
423
+
424
+ it('skips query slice without data gracefully', () => {
425
+ const model = emptyModel({
426
+ narratives: [
427
+ {
428
+ name: 'TestFlow',
429
+ slices: [
430
+ {
431
+ type: 'query',
432
+ name: 'GetThing',
433
+ request: 'query GetThing { getThing { id } }',
434
+ client: { specs: [] },
435
+ server: { description: 'desc', specs: [] },
436
+ },
437
+ ],
438
+ },
439
+ ],
440
+ });
441
+ expect(validateSliceRequests(model)).toEqual([]);
442
+ });
443
+
444
+ it('skips __typename in selection', () => {
445
+ const model = emptyModel({
446
+ narratives: [
447
+ {
448
+ name: 'TestFlow',
449
+ slices: [
450
+ {
451
+ type: 'query',
452
+ name: 'GetThing',
453
+ request: 'query GetThing { getThing { __typename id } }',
454
+ client: { specs: [] },
455
+ server: {
456
+ description: 'desc',
457
+ data: {
458
+ items: [
459
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
460
+ ],
461
+ },
462
+ specs: [],
463
+ },
464
+ },
465
+ ],
466
+ },
467
+ ],
468
+ messages: [
469
+ {
470
+ type: 'state',
471
+ name: 'ThingState',
472
+ fields: [{ name: 'id', type: 'string', required: true }],
473
+ },
474
+ ],
475
+ });
476
+ expect(validateSliceRequests(model)).toEqual([]);
477
+ });
478
+
479
+ it('returns no errors for valid query', () => {
480
+ const model = emptyModel({
481
+ narratives: [
482
+ {
483
+ name: 'TestFlow',
484
+ slices: [
485
+ {
486
+ type: 'query',
487
+ name: 'GetThing',
488
+ request: 'query GetThing { getThing { id name } }',
489
+ client: { specs: [] },
490
+ server: {
491
+ description: 'desc',
492
+ data: {
493
+ items: [
494
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
495
+ ],
496
+ },
497
+ specs: [],
498
+ },
499
+ },
500
+ ],
501
+ },
502
+ ],
503
+ messages: [
504
+ {
505
+ type: 'state',
506
+ name: 'ThingState',
507
+ fields: [
508
+ { name: 'id', type: 'string', required: true },
509
+ { name: 'name', type: 'string', required: true },
510
+ ],
511
+ },
512
+ ],
513
+ });
514
+ expect(validateSliceRequests(model)).toEqual([]);
515
+ });
516
+ });
517
+
518
+ describe('burst 4: query nested field validation', () => {
519
+ it('returns query_nested_field_not_found for nested field not in inline object', () => {
520
+ const model = emptyModel({
521
+ narratives: [
522
+ {
523
+ name: 'TestFlow',
524
+ slices: [
525
+ {
526
+ type: 'query',
527
+ name: 'GetThing',
528
+ request: 'query GetThing { getThing { id address { street missing } } }',
529
+ client: { specs: [] },
530
+ server: {
531
+ description: 'desc',
532
+ data: {
533
+ items: [
534
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
535
+ ],
536
+ },
537
+ specs: [],
538
+ },
539
+ },
540
+ ],
541
+ },
542
+ ],
543
+ messages: [
544
+ {
545
+ type: 'state',
546
+ name: 'ThingState',
547
+ fields: [
548
+ { name: 'id', type: 'string', required: true },
549
+ { name: 'address', type: '{ street: string; city: string }', required: true },
550
+ ],
551
+ },
552
+ ],
553
+ });
554
+ expect(validateSliceRequests(model)).toEqual([
555
+ {
556
+ type: 'query_nested_field_not_found',
557
+ message: "Nested field 'missing' in query 'GetThing' not found on type of 'address'",
558
+ flowName: 'TestFlow',
559
+ sliceName: 'GetThing',
560
+ },
561
+ ]);
562
+ });
563
+
564
+ it('resolves nested field on referenced message type', () => {
565
+ const model = emptyModel({
566
+ narratives: [
567
+ {
568
+ name: 'TestFlow',
569
+ slices: [
570
+ {
571
+ type: 'query',
572
+ name: 'GetThing',
573
+ request: 'query GetThing { getThing { id detail { value } } }',
574
+ client: { specs: [] },
575
+ server: {
576
+ description: 'desc',
577
+ data: {
578
+ items: [
579
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
580
+ ],
581
+ },
582
+ specs: [],
583
+ },
584
+ },
585
+ ],
586
+ },
587
+ ],
588
+ messages: [
589
+ {
590
+ type: 'state',
591
+ name: 'ThingState',
592
+ fields: [
593
+ { name: 'id', type: 'string', required: true },
594
+ { name: 'detail', type: 'DetailInfo', required: true },
595
+ ],
596
+ },
597
+ {
598
+ type: 'state',
599
+ name: 'DetailInfo',
600
+ fields: [{ name: 'value', type: 'string', required: true }],
601
+ },
602
+ ],
603
+ });
604
+ expect(validateSliceRequests(model)).toEqual([]);
605
+ });
606
+
607
+ it('skips nested field on unresolvable type without error', () => {
608
+ const model = emptyModel({
609
+ narratives: [
610
+ {
611
+ name: 'TestFlow',
612
+ slices: [
613
+ {
614
+ type: 'query',
615
+ name: 'GetThing',
616
+ request: 'query GetThing { getThing { id metadata { anything } } }',
617
+ client: { specs: [] },
618
+ server: {
619
+ description: 'desc',
620
+ data: {
621
+ items: [
622
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
623
+ ],
624
+ },
625
+ specs: [],
626
+ },
627
+ },
628
+ ],
629
+ },
630
+ ],
631
+ messages: [
632
+ {
633
+ type: 'state',
634
+ name: 'ThingState',
635
+ fields: [
636
+ { name: 'id', type: 'string', required: true },
637
+ { name: 'metadata', type: 'JSON', required: true },
638
+ ],
639
+ },
640
+ ],
641
+ });
642
+ expect(validateSliceRequests(model)).toEqual([]);
643
+ });
644
+
645
+ it('resolves nested fields on bracket array syntax type', () => {
646
+ const model = emptyModel({
647
+ narratives: [
648
+ {
649
+ name: 'TestFlow',
650
+ slices: [
651
+ {
652
+ type: 'query',
653
+ name: 'GetThing',
654
+ request: 'query GetThing { getThing { id items { name } } }',
655
+ client: { specs: [] },
656
+ server: {
657
+ description: 'desc',
658
+ data: {
659
+ items: [
660
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
661
+ ],
662
+ },
663
+ specs: [],
664
+ },
665
+ },
666
+ ],
667
+ },
668
+ ],
669
+ messages: [
670
+ {
671
+ type: 'state',
672
+ name: 'ThingState',
673
+ fields: [
674
+ { name: 'id', type: 'string', required: true },
675
+ { name: 'items', type: '{ name: string; value: number }[]', required: true },
676
+ ],
677
+ },
678
+ ],
679
+ });
680
+ expect(validateSliceRequests(model)).toEqual([]);
681
+ });
682
+
683
+ it('returns no errors for valid query with nested selections', () => {
684
+ const model = emptyModel({
685
+ narratives: [
686
+ {
687
+ name: 'TestFlow',
688
+ slices: [
689
+ {
690
+ type: 'query',
691
+ name: 'GetThing',
692
+ request: 'query GetThing { getThing { id address { street city } } }',
693
+ client: { specs: [] },
694
+ server: {
695
+ description: 'desc',
696
+ data: {
697
+ items: [
698
+ { target: { type: 'State', name: 'ThingState' }, origin: { type: 'projection', name: 'thing' } },
699
+ ],
700
+ },
701
+ specs: [],
702
+ },
703
+ },
704
+ ],
705
+ },
706
+ ],
707
+ messages: [
708
+ {
709
+ type: 'state',
710
+ name: 'ThingState',
711
+ fields: [
712
+ { name: 'id', type: 'string', required: true },
713
+ { name: 'address', type: '{ street: string; city: string }', required: true },
714
+ ],
715
+ },
716
+ ],
717
+ });
718
+ expect(validateSliceRequests(model)).toEqual([]);
719
+ });
720
+ });
721
+
722
+ describe('burst 5: integration', () => {
723
+ it('returns no false positives on a questionnaires-style model', () => {
724
+ const model = emptyModel({
725
+ narratives: [
726
+ {
727
+ name: 'Questionnaire Management',
728
+ slices: [
729
+ {
730
+ type: 'command',
731
+ name: 'SubmitAnswer',
732
+ request:
733
+ 'mutation SubmitAnswer($input: SubmitAnswerInput!) { submitAnswer(input: $input) { success } }',
734
+ client: { specs: [] },
735
+ server: { description: 'Submit an answer', specs: [] },
736
+ },
737
+ {
738
+ type: 'query',
739
+ name: 'GetQuestionnaire',
740
+ request: 'query GetQuestionnaire { getQuestionnaire { id title questions { text options } } }',
741
+ client: { specs: [] },
742
+ server: {
743
+ description: 'Get questionnaire',
744
+ data: {
745
+ items: [
746
+ {
747
+ target: { type: 'State', name: 'QuestionnaireState' },
748
+ origin: { type: 'projection', name: 'questionnaire' },
749
+ },
750
+ ],
751
+ },
752
+ specs: [],
753
+ },
754
+ },
755
+ {
756
+ type: 'command',
757
+ name: 'CreateQuestionnaire',
758
+ request:
759
+ 'mutation CreateQuestionnaire($input: CreateQuestionnaireInput!) { createQuestionnaire(input: $input) { success } }',
760
+ client: { specs: [] },
761
+ server: { description: 'Create questionnaire', specs: [] },
762
+ },
763
+ ],
764
+ },
765
+ ],
766
+ messages: [
767
+ {
768
+ type: 'command',
769
+ name: 'SubmitAnswer',
770
+ fields: [
771
+ { name: 'questionId', type: 'string', required: true },
772
+ { name: 'answer', type: 'string', required: true },
773
+ ],
774
+ },
775
+ {
776
+ type: 'command',
777
+ name: 'CreateQuestionnaire',
778
+ fields: [
779
+ { name: 'title', type: 'string', required: true },
780
+ { name: 'questions', type: 'Array<{ text: string; options: string[] }>', required: true },
781
+ ],
782
+ },
783
+ {
784
+ type: 'state',
785
+ name: 'QuestionnaireState',
786
+ fields: [
787
+ { name: 'id', type: 'string', required: true },
788
+ { name: 'title', type: 'string', required: true },
789
+ { name: 'questions', type: 'Array<{ text: string; options: string[] }>', required: true },
790
+ ],
791
+ },
792
+ {
793
+ type: 'event',
794
+ name: 'AnswerSubmitted',
795
+ fields: [
796
+ { name: 'questionId', type: 'string', required: true },
797
+ { name: 'answer', type: 'string', required: true },
798
+ ],
799
+ },
800
+ ],
801
+ });
802
+ expect(validateSliceRequests(model)).toEqual([]);
803
+ });
804
+
805
+ it('catches multiple errors across narratives', () => {
806
+ const model = emptyModel({
807
+ narratives: [
808
+ {
809
+ name: 'Flow1',
810
+ slices: [
811
+ {
812
+ type: 'command',
813
+ name: 'BadCmd',
814
+ request: 'query BadCmd { bad { id } }',
815
+ client: { specs: [] },
816
+ server: { description: 'desc', specs: [] },
817
+ },
818
+ ],
819
+ },
820
+ {
821
+ name: 'Flow2',
822
+ slices: [
823
+ {
824
+ type: 'query',
825
+ name: 'BadQuery',
826
+ request: 'mutation BadQuery($input: BadQueryInput!) { bad(input: $input) { id } }',
827
+ client: { specs: [] },
828
+ server: { description: 'desc', specs: [] },
829
+ },
830
+ ],
831
+ },
832
+ ],
833
+ });
834
+ expect(validateSliceRequests(model)).toEqual([
835
+ {
836
+ type: 'mutation_wrong_operation_type',
837
+ message: "Command slice 'BadCmd' request should be a mutation, but found query",
838
+ flowName: 'Flow1',
839
+ sliceName: 'BadCmd',
840
+ },
841
+ {
842
+ type: 'query_wrong_operation_type',
843
+ message: "Query slice 'BadQuery' request should be a query, but found mutation",
844
+ flowName: 'Flow2',
845
+ sliceName: 'BadQuery',
846
+ },
847
+ ]);
848
+ });
849
+ });
850
+ });