Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/internal/runner/lazy_test.go
4538 views
1
package runner
2
3
import (
4
"fmt"
5
"testing"
6
7
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
8
"github.com/projectdiscovery/nuclei/v3/pkg/output"
9
"github.com/stretchr/testify/require"
10
)
11
12
// simulateOnResultCallback reproduces the OnResult callback from lazy.go.
13
// It silently skips events without operator results and only errors post-execution
14
// if no data was extracted at all.
15
func simulateOnResultCallback(templatePath string, events []*output.InternalWrappedEvent) (map[string]interface{}, error) {
16
data := map[string]interface{}{}
17
18
for _, e := range events {
19
if e == nil {
20
continue
21
}
22
if !e.HasOperatorResult() {
23
continue
24
}
25
for k, v := range e.OperatorsResult.DynamicValues {
26
for _, value := range v {
27
oldVal, ok := data[k]
28
if !ok || len(value) > len(oldVal.(string)) {
29
data[k] = value
30
}
31
}
32
}
33
for k, v := range e.OperatorsResult.Extracts {
34
if len(v) > 0 {
35
data[k] = v[0]
36
}
37
}
38
}
39
40
var finalErr error
41
if len(data) == 0 {
42
finalErr = fmt.Errorf("no extracted values found for template: %s", templatePath)
43
}
44
return data, finalErr
45
}
46
47
// flowEvents returns a pair of events simulating a flow: http(1) && http(2)
48
// where http(1) has no matchers and http(2) has extractors.
49
func flowEvents() []*output.InternalWrappedEvent {
50
return []*output.InternalWrappedEvent{
51
{InternalEvent: map[string]interface{}{"status_code": 200}},
52
{
53
OperatorsResult: &operators.Result{
54
Matched: true,
55
Extracts: map[string][]string{
56
"userid": {"USER-042"},
57
"customerid": {"CUST-001"},
58
},
59
},
60
InternalEvent: map[string]interface{}{"status_code": 200},
61
},
62
}
63
}
64
65
// TestCallbackWorksWithFlowTemplate verifies that the OnResult callback
66
// correctly handles flow templates (e.g., flow: http(1) && http(2)) where
67
// the first request has no matchers/extractors and the second has extractors.
68
// Regression test for #7295.
69
func TestCallbackWorksWithFlowTemplate(t *testing.T) {
70
data, err := simulateOnResultCallback("login.yaml", flowEvents())
71
72
require.Equal(t, "USER-042", data["userid"])
73
require.Equal(t, "CUST-001", data["customerid"])
74
require.NoError(t, err, "should not error when data is extracted from a later event")
75
}
76
77
// TestCallbackSingleRequest verifies single-request templates still work.
78
func TestCallbackSingleRequest(t *testing.T) {
79
events := []*output.InternalWrappedEvent{
80
{
81
OperatorsResult: &operators.Result{
82
Matched: true,
83
Extracts: map[string][]string{
84
"token": {"abc123"},
85
},
86
},
87
InternalEvent: map[string]interface{}{},
88
},
89
}
90
91
data, err := simulateOnResultCallback("auth.yaml", events)
92
93
require.NoError(t, err)
94
require.Equal(t, "abc123", data["token"])
95
}
96
97
// TestCallbackNoExtraction verifies error is returned when nothing is extracted.
98
func TestCallbackNoExtraction(t *testing.T) {
99
events := []*output.InternalWrappedEvent{
100
{InternalEvent: map[string]interface{}{"status_code": 404}},
101
}
102
103
data, err := simulateOnResultCallback("auth.yaml", events)
104
105
require.Error(t, err)
106
require.Contains(t, err.Error(), "no extracted values")
107
require.Empty(t, data)
108
}
109
110
// TestCallbackNilEvent verifies nil events are safely ignored.
111
func TestCallbackNilEvent(t *testing.T) {
112
events := []*output.InternalWrappedEvent{nil}
113
114
data, err := simulateOnResultCallback("auth.yaml", events)
115
116
require.Error(t, err)
117
require.Contains(t, err.Error(), "no extracted values")
118
require.Empty(t, data)
119
}
120
121
// TestCallbackDynamicValues verifies dynamic values (internal extractors) are captured.
122
func TestCallbackDynamicValues(t *testing.T) {
123
events := []*output.InternalWrappedEvent{
124
{
125
OperatorsResult: &operators.Result{
126
Matched: true,
127
DynamicValues: map[string][]string{
128
"session": {"sess-xyz-123", "sess-a"},
129
},
130
},
131
InternalEvent: map[string]interface{}{},
132
},
133
}
134
135
data, err := simulateOnResultCallback("auth.yaml", events)
136
137
require.NoError(t, err)
138
require.Equal(t, "sess-xyz-123", data["session"], "should pick the longest value")
139
}
140
141