@auto-engineer/server-generator-apollo-emmett 1.155.0 → 1.156.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +39 -0
- package/dist/src/codegen/extract/data-sink.d.ts +1 -0
- package/dist/src/codegen/extract/data-sink.d.ts.map +1 -1
- package/dist/src/codegen/extract/data-sink.js +3 -0
- package/dist/src/codegen/extract/data-sink.js.map +1 -1
- package/dist/src/codegen/extract/messages.d.ts +1 -0
- package/dist/src/codegen/extract/messages.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.js +1 -1
- package/dist/src/codegen/extract/messages.js.map +1 -1
- package/dist/src/codegen/extract/sibling-events.d.ts +8 -0
- package/dist/src/codegen/extract/sibling-events.d.ts.map +1 -0
- package/dist/src/codegen/extract/sibling-events.js +58 -0
- package/dist/src/codegen/extract/sibling-events.js.map +1 -0
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +37 -2
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/decide.specs.specs.ts +125 -0
- package/dist/src/codegen/templates/command/decide.specs.ts +258 -3
- package/dist/src/codegen/templates/command/decide.specs.ts.ejs +42 -14
- package/dist/src/codegen/templates/command/decide.ts.ejs +63 -16
- package/dist/src/codegen/templates/command/evolve.specs.ts +217 -39
- package/dist/src/codegen/templates/command/evolve.ts.ejs +7 -7
- package/dist/src/codegen/templates/command/handle.specs.ts +7 -4
- package/dist/src/codegen/templates/command/handle.ts.ejs +17 -5
- package/dist/src/codegen/templates/command/state.specs.ts +125 -0
- package/dist/src/codegen/templates/command/state.ts.ejs +10 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +3 -3
- package/package.json +4 -4
- package/src/codegen/extract/data-sink.specs.ts +23 -1
- package/src/codegen/extract/data-sink.ts +4 -0
- package/src/codegen/extract/messages.ts +1 -1
- package/src/codegen/extract/sibling-events.specs.ts +206 -0
- package/src/codegen/extract/sibling-events.ts +76 -0
- package/src/codegen/ketchup-plan.md +12 -0
- package/src/codegen/scaffoldFromSchema.ts +50 -0
- package/src/codegen/templates/command/decide.specs.specs.ts +125 -0
- package/src/codegen/templates/command/decide.specs.ts +258 -3
- package/src/codegen/templates/command/decide.specs.ts.ejs +42 -14
- package/src/codegen/templates/command/decide.ts.ejs +63 -16
- package/src/codegen/templates/command/evolve.specs.ts +217 -39
- package/src/codegen/templates/command/evolve.ts.ejs +7 -7
- package/src/codegen/templates/command/handle.specs.ts +7 -4
- package/src/codegen/templates/command/handle.ts.ejs +17 -5
- package/src/codegen/templates/command/state.specs.ts +125 -0
- package/src/codegen/templates/command/state.ts.ejs +10 -2
|
@@ -2793,4 +2793,129 @@ describe('spec.ts.ejs', () => {
|
|
|
2793
2793
|
"
|
|
2794
2794
|
`);
|
|
2795
2795
|
});
|
|
2796
|
+
|
|
2797
|
+
it('should use preceding sibling events for state-ref Given on shared stream', async () => {
|
|
2798
|
+
const spec: SpecsSchema = {
|
|
2799
|
+
variant: 'specs',
|
|
2800
|
+
scenes: [
|
|
2801
|
+
{
|
|
2802
|
+
name: 'Gym session',
|
|
2803
|
+
moments: [
|
|
2804
|
+
{
|
|
2805
|
+
type: 'command',
|
|
2806
|
+
name: 'Start session',
|
|
2807
|
+
client: { specs: [] },
|
|
2808
|
+
server: {
|
|
2809
|
+
description: '',
|
|
2810
|
+
data: {
|
|
2811
|
+
items: [
|
|
2812
|
+
{
|
|
2813
|
+
target: { type: 'Event', name: 'SessionStarted' },
|
|
2814
|
+
destination: { type: 'stream', pattern: 'sessions-${id}' },
|
|
2815
|
+
},
|
|
2816
|
+
],
|
|
2817
|
+
},
|
|
2818
|
+
specs: [
|
|
2819
|
+
{
|
|
2820
|
+
type: 'gherkin',
|
|
2821
|
+
feature: 'Start session',
|
|
2822
|
+
rules: [
|
|
2823
|
+
{
|
|
2824
|
+
name: 'Start session',
|
|
2825
|
+
examples: [
|
|
2826
|
+
{
|
|
2827
|
+
name: 'Start session example',
|
|
2828
|
+
steps: [
|
|
2829
|
+
{ keyword: 'When', text: 'StartSession', docString: { userId: 'u1' } },
|
|
2830
|
+
{ keyword: 'Then', text: 'SessionStarted', docString: { id: 's1', userId: 'u1' } },
|
|
2831
|
+
],
|
|
2832
|
+
},
|
|
2833
|
+
],
|
|
2834
|
+
},
|
|
2835
|
+
],
|
|
2836
|
+
},
|
|
2837
|
+
],
|
|
2838
|
+
},
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
type: 'command',
|
|
2842
|
+
name: 'Complete session',
|
|
2843
|
+
client: { specs: [] },
|
|
2844
|
+
server: {
|
|
2845
|
+
description: '',
|
|
2846
|
+
data: {
|
|
2847
|
+
items: [
|
|
2848
|
+
{
|
|
2849
|
+
target: { type: 'Event', name: 'SessionCompleted' },
|
|
2850
|
+
destination: { type: 'stream', pattern: 'sessions-${sessionId}' },
|
|
2851
|
+
},
|
|
2852
|
+
],
|
|
2853
|
+
},
|
|
2854
|
+
specs: [
|
|
2855
|
+
{
|
|
2856
|
+
type: 'gherkin',
|
|
2857
|
+
feature: 'Complete session',
|
|
2858
|
+
rules: [
|
|
2859
|
+
{
|
|
2860
|
+
name: 'Complete session',
|
|
2861
|
+
examples: [
|
|
2862
|
+
{
|
|
2863
|
+
name: 'Complete session example',
|
|
2864
|
+
steps: [
|
|
2865
|
+
{ keyword: 'Given', text: 'ActiveSession', docString: { status: 'active' } },
|
|
2866
|
+
{ keyword: 'When', text: 'CompleteSession', docString: { sessionId: 's1' } },
|
|
2867
|
+
{
|
|
2868
|
+
keyword: 'Then',
|
|
2869
|
+
text: 'SessionCompleted',
|
|
2870
|
+
docString: { sessionId: 's1', completedAt: '2024-01-15' },
|
|
2871
|
+
},
|
|
2872
|
+
],
|
|
2873
|
+
},
|
|
2874
|
+
],
|
|
2875
|
+
},
|
|
2876
|
+
],
|
|
2877
|
+
},
|
|
2878
|
+
],
|
|
2879
|
+
},
|
|
2880
|
+
},
|
|
2881
|
+
],
|
|
2882
|
+
},
|
|
2883
|
+
],
|
|
2884
|
+
messages: [
|
|
2885
|
+
{ type: 'command', name: 'StartSession', fields: [{ name: 'userId', type: 'string', required: true }] },
|
|
2886
|
+
{ type: 'command', name: 'CompleteSession', fields: [{ name: 'sessionId', type: 'string', required: true }] },
|
|
2887
|
+
{
|
|
2888
|
+
type: 'event',
|
|
2889
|
+
name: 'SessionStarted',
|
|
2890
|
+
source: 'internal',
|
|
2891
|
+
fields: [
|
|
2892
|
+
{ name: 'id', type: 'string', required: true },
|
|
2893
|
+
{ name: 'userId', type: 'string', required: true },
|
|
2894
|
+
],
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
type: 'event',
|
|
2898
|
+
name: 'SessionCompleted',
|
|
2899
|
+
source: 'internal',
|
|
2900
|
+
fields: [
|
|
2901
|
+
{ name: 'sessionId', type: 'string', required: true },
|
|
2902
|
+
{ name: 'completedAt', type: 'string', required: true },
|
|
2903
|
+
],
|
|
2904
|
+
},
|
|
2905
|
+
{ type: 'state', name: 'ActiveSession', fields: [{ name: 'status', type: 'string', required: true }] },
|
|
2906
|
+
],
|
|
2907
|
+
};
|
|
2908
|
+
|
|
2909
|
+
const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
|
|
2910
|
+
|
|
2911
|
+
const completeSpecs = plans.find(
|
|
2912
|
+
(p) => p.outputPath.includes('complete-session') && p.outputPath.endsWith('decide.specs.ts'),
|
|
2913
|
+
);
|
|
2914
|
+
const contents = completeSpecs?.contents ?? '';
|
|
2915
|
+
|
|
2916
|
+
expect(contents).toContain("import type { SessionStarted } from '../start-session/events';");
|
|
2917
|
+
expect(contents).toContain("type: 'SessionStarted'");
|
|
2918
|
+
expect(contents).toContain("id: 's1'");
|
|
2919
|
+
expect(contents).toContain("userId: 'u1'");
|
|
2920
|
+
});
|
|
2796
2921
|
});
|
|
@@ -593,7 +593,7 @@ describe('decide.ts.ejs', () => {
|
|
|
593
593
|
import type { ItemsSuggested } from './events';
|
|
594
594
|
import type { Products } from '@auto-engineer/product-catalogue-integration';
|
|
595
595
|
|
|
596
|
-
export const decide = (command: SuggestItems, _state: State, products?: Products): ItemsSuggested => {
|
|
596
|
+
export const decide = (command: SuggestItems, _state: State, context?: { products?: Products }): ItemsSuggested => {
|
|
597
597
|
switch (command.type) {
|
|
598
598
|
case 'SuggestItems': {
|
|
599
599
|
/**
|
|
@@ -604,7 +604,7 @@ describe('decide.ts.ejs', () => {
|
|
|
604
604
|
* You should:
|
|
605
605
|
* - Validate the command input fields
|
|
606
606
|
* - NEVER use \`as SomeType\` type assertions — not \`as any\`, not \`as EventType\`, no casts at all. Use typed variable declarations.
|
|
607
|
-
* - Use \`products\` (integration result) to enrich or filter the output
|
|
607
|
+
* - Use \`context?.products\` (integration result) to enrich or filter the output
|
|
608
608
|
* - If invalid, throw one of the following domain errors: \`IllegalStateError\`
|
|
609
609
|
* ⚠️ Error constructors: IllegalStateError takes a string message
|
|
610
610
|
* - If valid, return one or more events with the correct structure
|
|
@@ -613,7 +613,7 @@ describe('decide.ts.ejs', () => {
|
|
|
613
613
|
* ⚠️ Only read from inputs — never mutate them. \`evolve.ts\` handles state updates.
|
|
614
614
|
*
|
|
615
615
|
* Integration result shape (Products):
|
|
616
|
-
* products?.data = {
|
|
616
|
+
* context?.products?.data = {
|
|
617
617
|
* products: Array<{
|
|
618
618
|
* id: string;
|
|
619
619
|
* name: string;
|
|
@@ -648,4 +648,259 @@ describe('decide.ts.ejs', () => {
|
|
|
648
648
|
"
|
|
649
649
|
`);
|
|
650
650
|
});
|
|
651
|
+
|
|
652
|
+
it('should generate context parameter and uuid assignment for stream uuid vars that are event fields', async () => {
|
|
653
|
+
const spec: SpecsSchema = {
|
|
654
|
+
variant: 'specs',
|
|
655
|
+
scenes: [
|
|
656
|
+
{
|
|
657
|
+
name: 'Fitness tracker',
|
|
658
|
+
moments: [
|
|
659
|
+
{
|
|
660
|
+
type: 'command',
|
|
661
|
+
name: 'Log workout',
|
|
662
|
+
stream: 'workouts-${workoutId}',
|
|
663
|
+
client: { specs: [] },
|
|
664
|
+
server: {
|
|
665
|
+
description: 'test',
|
|
666
|
+
specs: [
|
|
667
|
+
{
|
|
668
|
+
type: 'gherkin',
|
|
669
|
+
feature: 'Log workout',
|
|
670
|
+
rules: [
|
|
671
|
+
{
|
|
672
|
+
name: 'Should log workout',
|
|
673
|
+
examples: [
|
|
674
|
+
{
|
|
675
|
+
name: 'Workout logged',
|
|
676
|
+
steps: [
|
|
677
|
+
{
|
|
678
|
+
keyword: 'When',
|
|
679
|
+
text: 'LogWorkout',
|
|
680
|
+
docString: { exercise: 'squat', reps: 10 },
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
keyword: 'Then',
|
|
684
|
+
text: 'WorkoutLogged',
|
|
685
|
+
docString: { workoutId: 'wk-1', exercise: 'squat', reps: 10 },
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
},
|
|
689
|
+
],
|
|
690
|
+
},
|
|
691
|
+
],
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
data: {
|
|
695
|
+
items: [
|
|
696
|
+
{
|
|
697
|
+
target: { type: 'Event', name: 'WorkoutLogged' },
|
|
698
|
+
destination: { type: 'stream', pattern: 'workouts-${workoutId}' },
|
|
699
|
+
},
|
|
700
|
+
],
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
},
|
|
706
|
+
],
|
|
707
|
+
messages: [
|
|
708
|
+
{
|
|
709
|
+
type: 'command',
|
|
710
|
+
name: 'LogWorkout',
|
|
711
|
+
fields: [
|
|
712
|
+
{ name: 'exercise', type: 'string', required: true },
|
|
713
|
+
{ name: 'reps', type: 'number', required: true },
|
|
714
|
+
],
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
type: 'event',
|
|
718
|
+
name: 'WorkoutLogged',
|
|
719
|
+
source: 'internal',
|
|
720
|
+
fields: [
|
|
721
|
+
{ name: 'workoutId', type: 'string', required: true },
|
|
722
|
+
{ name: 'exercise', type: 'string', required: true },
|
|
723
|
+
{ name: 'reps', type: 'number', required: true },
|
|
724
|
+
],
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
|
|
730
|
+
const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
|
|
731
|
+
|
|
732
|
+
expect(decideFile?.contents).toMatchInlineSnapshot(`
|
|
733
|
+
"import { randomUUID } from 'node:crypto';
|
|
734
|
+
import { IllegalStateError } from '@event-driven-io/emmett';
|
|
735
|
+
import type { State } from './state';
|
|
736
|
+
import type { LogWorkout } from './commands';
|
|
737
|
+
import type { WorkoutLogged } from './events';
|
|
738
|
+
|
|
739
|
+
export const decide = (
|
|
740
|
+
command: LogWorkout,
|
|
741
|
+
_state: State,
|
|
742
|
+
context?: { generated?: { workoutId: string } },
|
|
743
|
+
): WorkoutLogged => {
|
|
744
|
+
switch (command.type) {
|
|
745
|
+
case 'LogWorkout': {
|
|
746
|
+
/**
|
|
747
|
+
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
748
|
+
*
|
|
749
|
+
* This command can directly emit one or more events based on the input.
|
|
750
|
+
*
|
|
751
|
+
* You should:
|
|
752
|
+
* - Validate the command input fields
|
|
753
|
+
* - NEVER use \`as SomeType\` type assertions — not \`as any\`, not \`as EventType\`, no casts at all. Use typed variable declarations.
|
|
754
|
+
* - If invalid, throw one of the following domain errors: \`IllegalStateError\`
|
|
755
|
+
* ⚠️ Error constructors: IllegalStateError takes a string message
|
|
756
|
+
* - If valid, return one or more events with the correct structure
|
|
757
|
+
*
|
|
758
|
+
* - Only destructure/reference command.data fields that exist in the command type (imported from ./commands). Do NOT access fields not declared in the type, even if example data includes them.
|
|
759
|
+
* ⚠️ Only read from inputs — never mutate them. \`evolve.ts\` handles state updates.
|
|
760
|
+
|
|
761
|
+
* Business rules:
|
|
762
|
+
* - Should log workout
|
|
763
|
+
*/
|
|
764
|
+
|
|
765
|
+
const workoutId = context?.generated?.workoutId ?? randomUUID();
|
|
766
|
+
|
|
767
|
+
// IMPLEMENT: Use a typed variable to prevent type widening:
|
|
768
|
+
// const result: WorkoutLogged = {
|
|
769
|
+
// type: 'WorkoutLogged',
|
|
770
|
+
// data: { ...command.data, workoutId },
|
|
771
|
+
// };
|
|
772
|
+
// return result;
|
|
773
|
+
|
|
774
|
+
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
775
|
+
}
|
|
776
|
+
default:
|
|
777
|
+
throw new IllegalStateError('Unexpected command type');
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
"
|
|
781
|
+
`);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it('should say Inspect _state for state-ref Given with preceding siblings', async () => {
|
|
785
|
+
const spec: SpecsSchema = {
|
|
786
|
+
variant: 'specs',
|
|
787
|
+
scenes: [
|
|
788
|
+
{
|
|
789
|
+
name: 'Gym session',
|
|
790
|
+
moments: [
|
|
791
|
+
{
|
|
792
|
+
type: 'command',
|
|
793
|
+
name: 'Start session',
|
|
794
|
+
client: { specs: [] },
|
|
795
|
+
server: {
|
|
796
|
+
description: '',
|
|
797
|
+
data: {
|
|
798
|
+
items: [
|
|
799
|
+
{
|
|
800
|
+
target: { type: 'Event', name: 'SessionStarted' },
|
|
801
|
+
destination: { type: 'stream', pattern: 'sessions-${id}' },
|
|
802
|
+
},
|
|
803
|
+
],
|
|
804
|
+
},
|
|
805
|
+
specs: [
|
|
806
|
+
{
|
|
807
|
+
type: 'gherkin',
|
|
808
|
+
feature: 'Start',
|
|
809
|
+
rules: [
|
|
810
|
+
{
|
|
811
|
+
name: 'Start',
|
|
812
|
+
examples: [
|
|
813
|
+
{
|
|
814
|
+
name: 'Start',
|
|
815
|
+
steps: [
|
|
816
|
+
{ keyword: 'When', text: 'StartSession', docString: { userId: 'u1' } },
|
|
817
|
+
{ keyword: 'Then', text: 'SessionStarted', docString: { id: 's1', userId: 'u1' } },
|
|
818
|
+
],
|
|
819
|
+
},
|
|
820
|
+
],
|
|
821
|
+
},
|
|
822
|
+
],
|
|
823
|
+
},
|
|
824
|
+
],
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
type: 'command',
|
|
829
|
+
name: 'Complete session',
|
|
830
|
+
client: { specs: [] },
|
|
831
|
+
server: {
|
|
832
|
+
description: '',
|
|
833
|
+
data: {
|
|
834
|
+
items: [
|
|
835
|
+
{
|
|
836
|
+
target: { type: 'Event', name: 'SessionCompleted' },
|
|
837
|
+
destination: { type: 'stream', pattern: 'sessions-${sessionId}' },
|
|
838
|
+
},
|
|
839
|
+
],
|
|
840
|
+
},
|
|
841
|
+
specs: [
|
|
842
|
+
{
|
|
843
|
+
type: 'gherkin',
|
|
844
|
+
feature: 'Complete',
|
|
845
|
+
rules: [
|
|
846
|
+
{
|
|
847
|
+
name: 'Complete',
|
|
848
|
+
examples: [
|
|
849
|
+
{
|
|
850
|
+
name: 'Complete',
|
|
851
|
+
steps: [
|
|
852
|
+
{ keyword: 'Given', text: 'ActiveSession', docString: { status: 'active' } },
|
|
853
|
+
{ keyword: 'When', text: 'CompleteSession', docString: { sessionId: 's1' } },
|
|
854
|
+
{
|
|
855
|
+
keyword: 'Then',
|
|
856
|
+
text: 'SessionCompleted',
|
|
857
|
+
docString: { sessionId: 's1', completedAt: '2024-01-15' },
|
|
858
|
+
},
|
|
859
|
+
],
|
|
860
|
+
},
|
|
861
|
+
],
|
|
862
|
+
},
|
|
863
|
+
],
|
|
864
|
+
},
|
|
865
|
+
],
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
],
|
|
869
|
+
},
|
|
870
|
+
],
|
|
871
|
+
messages: [
|
|
872
|
+
{ type: 'command', name: 'StartSession', fields: [{ name: 'userId', type: 'string', required: true }] },
|
|
873
|
+
{ type: 'command', name: 'CompleteSession', fields: [{ name: 'sessionId', type: 'string', required: true }] },
|
|
874
|
+
{
|
|
875
|
+
type: 'event',
|
|
876
|
+
name: 'SessionStarted',
|
|
877
|
+
source: 'internal',
|
|
878
|
+
fields: [
|
|
879
|
+
{ name: 'id', type: 'string', required: true },
|
|
880
|
+
{ name: 'userId', type: 'string', required: true },
|
|
881
|
+
],
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
type: 'event',
|
|
885
|
+
name: 'SessionCompleted',
|
|
886
|
+
source: 'internal',
|
|
887
|
+
fields: [
|
|
888
|
+
{ name: 'sessionId', type: 'string', required: true },
|
|
889
|
+
{ name: 'completedAt', type: 'string', required: true },
|
|
890
|
+
],
|
|
891
|
+
},
|
|
892
|
+
{ type: 'state', name: 'ActiveSession', fields: [{ name: 'status', type: 'string', required: true }] },
|
|
893
|
+
],
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
|
|
897
|
+
const decideFile = plans.find(
|
|
898
|
+
(p) => p.outputPath.includes('complete-session') && p.outputPath.endsWith('decide.ts'),
|
|
899
|
+
);
|
|
900
|
+
const contents = decideFile?.contents ?? '';
|
|
901
|
+
|
|
902
|
+
expect(contents).toContain('requires evaluating prior state');
|
|
903
|
+
expect(contents).toContain('Inspect the current domain');
|
|
904
|
+
expect(contents).not.toContain('succeed from initial state');
|
|
905
|
+
});
|
|
651
906
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<%
|
|
2
2
|
const allEvents = [];
|
|
3
3
|
const ruleGroups = new Map();
|
|
4
|
+
const _stateRefGivenSeen = new Set();
|
|
4
5
|
for (const commandName in gwtMapping) {
|
|
5
6
|
const cases = gwtMapping[commandName];
|
|
6
7
|
for (const gwt of cases) {
|
|
@@ -12,8 +13,17 @@ for (const commandName in gwtMapping) {
|
|
|
12
13
|
if (gwt.given && gwt.given.length) {
|
|
13
14
|
for (const g of gwt.given) {
|
|
14
15
|
if (g.eventRef) {
|
|
15
|
-
const event =
|
|
16
|
-
if (event)
|
|
16
|
+
const event = allStreamEvents.find(e => e.type === g.eventRef);
|
|
17
|
+
if (event) {
|
|
18
|
+
allEvents.push(event);
|
|
19
|
+
} else if (precedingSiblingEvents.length > 0 && !_stateRefGivenSeen.has(g.eventRef)) {
|
|
20
|
+
_stateRefGivenSeen.add(g.eventRef);
|
|
21
|
+
for (const se of precedingSiblingEvents) {
|
|
22
|
+
if (!allEvents.some(e => e.type === se.type)) {
|
|
23
|
+
allEvents.push(se);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
29
|
}
|
|
@@ -33,7 +43,7 @@ const testEventsByPath = new Map();
|
|
|
33
43
|
|
|
34
44
|
for (const event of allEvents) {
|
|
35
45
|
if (!event.type) continue;
|
|
36
|
-
const importGroup =
|
|
46
|
+
const importGroup = allStreamEventImportGroups.find(group =>
|
|
37
47
|
group.eventTypes.includes(event.type)
|
|
38
48
|
);
|
|
39
49
|
|
|
@@ -64,7 +74,7 @@ for (const commandName in gwtMapping) {
|
|
|
64
74
|
const eventResults = gwt.then.filter(t => 'eventRef' in t);
|
|
65
75
|
const errorResult = gwt.then.find(t => 'errorType' in t);
|
|
66
76
|
if (errorResult) continue;
|
|
67
|
-
const givenEventRefs = (gwt.given || []).filter(g =>
|
|
77
|
+
const givenEventRefs = (gwt.given || []).filter(g => allStreamEvents.some(ev => ev.type === g.eventRef));
|
|
68
78
|
for (const e of eventResults) {
|
|
69
79
|
const dedupeKey = `${commandName}::${e.eventRef}`;
|
|
70
80
|
if (seenCommandEvents.has(dedupeKey)) continue;
|
|
@@ -116,12 +126,22 @@ describe('<%= escapeJsString(ruleDescription) %>', () => {
|
|
|
116
126
|
%>
|
|
117
127
|
it('<%= escapeJsString(testDescription) %>', () => {
|
|
118
128
|
given([
|
|
119
|
-
<%_
|
|
129
|
+
<%_
|
|
130
|
+
let givenEvents = (gwt.given || []).filter(g => allStreamEvents.some(e => e.type === g.eventRef));
|
|
131
|
+
const hasStateRefGiven = (gwt.given || []).some(g => !allStreamEvents.some(e => e.type === g.eventRef));
|
|
132
|
+
if (givenEvents.length === 0 && hasStateRefGiven && precedingSiblingEvents.length > 0) {
|
|
133
|
+
givenEvents = precedingSiblingEvents;
|
|
134
|
+
}
|
|
135
|
+
_%>
|
|
120
136
|
<%_ if (givenEvents.length) { _%>
|
|
121
|
-
<%- givenEvents.map(g =>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
<%- givenEvents.map(g => {
|
|
138
|
+
const schema = allStreamEvents.find(e => e.type === (g.eventRef || g.type));
|
|
139
|
+
const data = g.exampleData || siblingExampleData[g.type] || {};
|
|
140
|
+
return `{
|
|
141
|
+
type: '${g.eventRef || g.type}',
|
|
142
|
+
data: ${formatDataObject(data, schema)}
|
|
143
|
+
}`;
|
|
144
|
+
}).join(',\n ') %>
|
|
125
145
|
<%_ } _%>
|
|
126
146
|
])
|
|
127
147
|
.when({
|
|
@@ -171,7 +191,11 @@ describe('field completeness', () => {
|
|
|
171
191
|
const firstScenario = scenarios[0];
|
|
172
192
|
const gwt = firstScenario.gwt;
|
|
173
193
|
const schema = commandSchemasByName[cmdName];
|
|
174
|
-
|
|
194
|
+
let givenEvents = (gwt.given || []).filter(g => allStreamEvents.some(e => e.type === g.eventRef));
|
|
195
|
+
const hasStateRefGiven = (gwt.given || []).some(g => !allStreamEvents.some(e => e.type === g.eventRef));
|
|
196
|
+
if (givenEvents.length === 0 && hasStateRefGiven && precedingSiblingEvents.length > 0) {
|
|
197
|
+
givenEvents = precedingSiblingEvents;
|
|
198
|
+
}
|
|
175
199
|
const example = gwt.when;
|
|
176
200
|
const cmdFieldNames = firstScenario.cmdFieldNames;
|
|
177
201
|
const filteredCmdData = Object.fromEntries(
|
|
@@ -181,10 +205,14 @@ describe('field completeness', () => {
|
|
|
181
205
|
it('should include all required fields in <%= scenarios.map(s => s.eventRef).join(', ') %>', () => {
|
|
182
206
|
<%_ if (givenEvents.length) { _%>
|
|
183
207
|
const state = [
|
|
184
|
-
<%- givenEvents.map(g =>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
<%- givenEvents.map(g => {
|
|
209
|
+
const schema = allStreamEvents.find(e => e.type === (g.eventRef || g.type));
|
|
210
|
+
const data = g.exampleData || siblingExampleData[g.type] || {};
|
|
211
|
+
return `{
|
|
212
|
+
type: '${g.eventRef || g.type}' as const,
|
|
213
|
+
data: ${formatDataObject(data, schema)}
|
|
214
|
+
}`;
|
|
215
|
+
}).join(',\n ') %>
|
|
188
216
|
].reduce((s, e) => evolve(s, e), initialState());
|
|
189
217
|
<%_ } else { _%>
|
|
190
218
|
const state = initialState();
|