-
Notifications
You must be signed in to change notification settings - Fork 1
/
expectation.messagecommon.go
191 lines (163 loc) · 5.17 KB
/
expectation.messagecommon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package testkit
import (
"fmt"
"strings"
"github.com/dogmatiq/configkit"
"github.com/dogmatiq/enginekit/message"
"github.com/dogmatiq/testkit/envelope"
"github.com/dogmatiq/testkit/fact"
"github.com/dogmatiq/testkit/internal/inflect"
)
// reportNoMatch is used by message-related predicates to build a test report
// when no "best-match" message is found.
func reportNoMatch(rep *Report, t *tracker) {
s := rep.Section(suggestionsSection)
allDisabled := true
var relevant []string
if t.cycleBegun {
for _, ht := range configkit.HandlerTypes {
e := t.enabled[ht]
if ht.IsProducerOf(t.kind) {
relevant = append(relevant, ht.String())
if e {
allDisabled = false
} else {
s.AppendListItem(
fmt.Sprintf("enable %s handlers using the EnableHandlerType() option", ht),
)
}
}
}
if !t.options.MatchDispatchCycleStartedFacts {
if allDisabled {
rep.Explanation = "no relevant handler types were enabled"
return
}
if len(t.engagedOrder) == 0 {
rep.Explanation = fmt.Sprintf(
"no relevant handlers (%s) were engaged",
strings.Join(relevant, " or "),
)
s.AppendListItem("check the application's routing configuration")
return
}
}
}
if t.total == 0 {
rep.Explanation = "no messages were produced at all"
} else if t.produced == 0 {
rep.Explanation = inflect.Sprint(t.kind, "no <messages> were <produced> at all")
} else if t.options.MatchDispatchCycleStartedFacts {
rep.Explanation = inflect.Sprint(t.kind, "nothing <produced> a matching <message>")
} else {
rep.Explanation = inflect.Sprint(t.kind, "none of the engaged handlers <produced> a matching <message>")
}
for _, n := range t.engagedOrder {
s.AppendListItem("verify the logic within the '%s' %s message handler", n, t.engagedType[n])
}
if t.options.MatchDispatchCycleStartedFacts {
s.AppendListItem(inflect.Sprint(t.kind, "verify the logic within the code that uses the <dispatcher>"))
}
}
// guardAgainstExpectationOnImpossibleType returns an error if the predicate
// with scope s cannot possible match a message of type t.
func guardAgainstExpectationOnImpossibleType(
s PredicateScope,
t message.Type,
) error {
// TODO: These checks should result in information being added to the
// report, not just returning an error.
//
// See https://github.com/dogmatiq/testkit/issues/162
em, ok := s.App.MessageTypes()[t]
if !ok {
return inflect.Errorf(
t.Kind(),
"a <message> of type %s can never be <produced>, the application does not use this message type",
t,
)
}
if !s.Options.MatchDispatchCycleStartedFacts {
// If we're NOT matching messages from DispatchCycleStarted facts that
// means this expectation can only ever pass if the message is produced
// by a handler.
if !em.IsProduced {
return inflect.Errorf(
t.Kind(),
"no handlers <produce> <messages> of type %s, it is only ever consumed",
t,
)
}
}
return nil
}
// tracker is a fact.Observer used by expectations that need to keep track of
// information about handlers and the messages they produce.
type tracker struct {
// kind is the kind of message the tracker is expecting to find.
kind message.Kind
// options is the set of options passed to the predicate.
options PredicateOptions
// cycleBegun is true if at least one dispatch or tick cycle was started.
cycleBegun bool
// total is the total number of messages that were produced.
total int
// produced is the number of messages of the expected kind that were
// produced.
produced int
// engagedOrder and engagedType track the set of handlers that *could* have
// produced the expected message.
engagedOrder []string
engagedType map[string]configkit.HandlerType
// enabled is the set of handler types that are enabled during the test.
enabled map[configkit.HandlerType]bool
}
// Notify updates the tracker's state in response to a new fact.
//
// It returns the envelope containing the message that was tracked.
func (t *tracker) Notify(f fact.Fact) (*envelope.Envelope, bool) {
switch x := f.(type) {
case fact.DispatchCycleBegun:
t.cycleBegun = true
t.enabled = x.EnabledHandlerTypes
if t.options.MatchDispatchCycleStartedFacts {
t.messageProduced(x.Envelope)
return x.Envelope, true
}
case fact.HandlingBegun:
t.updateEngaged(
x.Handler.Identity().Name,
x.Handler.HandlerType(),
)
case fact.EventRecordedByAggregate:
t.messageProduced(x.EventEnvelope)
return x.EventEnvelope, true
case fact.EventRecordedByIntegration:
t.messageProduced(x.EventEnvelope)
return x.EventEnvelope, true
case fact.CommandExecutedByProcess:
t.messageProduced(x.CommandEnvelope)
return x.CommandEnvelope, true
case fact.TimeoutScheduledByProcess:
t.messageProduced(x.TimeoutEnvelope)
return x.TimeoutEnvelope, true
}
return nil, false
}
func (t *tracker) updateEngaged(n string, ht configkit.HandlerType) {
if ht.IsProducerOf(t.kind) {
if t.engagedType == nil {
t.engagedType = map[string]configkit.HandlerType{}
}
if _, ok := t.engagedType[n]; !ok {
t.engagedOrder = append(t.engagedOrder, n)
t.engagedType[n] = ht
}
}
}
func (t *tracker) messageProduced(env *envelope.Envelope) {
t.total++
if message.KindOf(env.Message) == t.kind {
t.produced++
}
}