Skip to content

Commit 23d3890

Browse files
committed
test(fuzz): add fuzz tests for query object parsing and validation
Introduces fuzz tests for various query parameters and JSON structures to ensure robust handling of edge cases and malformed inputs.
1 parent 807130d commit 23d3890

File tree

1 file changed

+294
-0
lines changed

1 file changed

+294
-0
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
package ship
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/bsv-blockchain/go-overlay-discovery-services/pkg/types"
8+
)
9+
10+
// FuzzParseQueryObjectJSON tests the parseQueryObject method with random JSON inputs
11+
// to ensure it handles malformed and edge-case JSON gracefully.
12+
func FuzzParseQueryObjectJSON(f *testing.F) {
13+
// Seed corpus with valid query JSON examples
14+
f.Add(`{"findAll": true}`)
15+
f.Add(`{"domain": "example.com"}`)
16+
f.Add(`{"topics": ["tm_payments", "tm_chat"]}`)
17+
f.Add(`{"identityKey": "abc123"}`)
18+
f.Add(`{"limit": 10, "skip": 5}`)
19+
f.Add(`{"sortOrder": "asc"}`)
20+
f.Add(`{"sortOrder": "desc"}`)
21+
f.Add(`{"domain": "example.com", "topics": ["tm_payments"], "limit": 10}`)
22+
23+
// Seed corpus with invalid/edge-case JSON
24+
f.Add(`{}`)
25+
f.Add(`null`)
26+
f.Add(`"findAll"`)
27+
f.Add(`{"domain": 123}`)
28+
f.Add(`{"topics": "not_an_array"}`)
29+
f.Add(`{"topics": [123, 456]}`)
30+
f.Add(`{"limit": -1}`)
31+
f.Add(`{"skip": -1}`)
32+
f.Add(`{"sortOrder": "invalid"}`)
33+
f.Add(`{"unknown_field": "value"}`)
34+
35+
// Seed corpus with edge cases
36+
f.Add(`{"limit": 0}`)
37+
f.Add(`{"skip": 0}`)
38+
f.Add(`{"topics": []}`)
39+
f.Add(`{"domain": ""}`)
40+
f.Add(`{"findAll": false}`)
41+
f.Add(`[1, 2, 3]`)
42+
f.Add(`true`)
43+
f.Add(`123`)
44+
45+
// Create a service instance with a mock storage
46+
service := &LookupService{
47+
storage: nil, // We don't need actual storage for this test
48+
}
49+
50+
f.Fuzz(func(t *testing.T, jsonStr string) {
51+
// First, try to unmarshal to ensure it's valid JSON
52+
var queryInterface interface{}
53+
err := json.Unmarshal([]byte(jsonStr), &queryInterface)
54+
if err != nil {
55+
// Invalid JSON should be rejected, but shouldn't panic
56+
return
57+
}
58+
59+
// Function should not panic on any input
60+
_, err = service.parseQueryObject(queryInterface)
61+
62+
// We don't validate the result or error, just ensure no panic occurs
63+
// Errors are expected for invalid query structures
64+
_ = err
65+
})
66+
}
67+
68+
// FuzzValidateQuerySHIP tests the validateQuery method with random query parameters.
69+
func FuzzValidateQuerySHIP(f *testing.F) {
70+
// Helper to create string pointer
71+
strPtr := func(s string) *string { return &s }
72+
intPtr := func(i int) *int { return &i }
73+
boolPtr := func(b bool) *bool { return &b }
74+
sortOrderPtr := func(s types.SortOrder) *types.SortOrder { return &s }
75+
76+
// Seed corpus with valid queries
77+
f.Add(true, "example.com", "tm_payments", "key123", 10, 0, "asc")
78+
f.Add(false, "", "", "", 0, 0, "desc")
79+
f.Add(false, "test.com", "tm_chat", "", 100, 50, "asc")
80+
81+
// Seed corpus with invalid queries
82+
f.Add(false, "", "", "", -1, 0, "asc") // negative limit
83+
f.Add(false, "", "", "", 0, -1, "asc") // negative skip
84+
f.Add(false, "", "", "", 0, 0, "invalid") // invalid sort order
85+
86+
// Create a service instance
87+
service := &LookupService{
88+
storage: nil,
89+
}
90+
91+
f.Fuzz(func(t *testing.T, findAll bool, domain, topic, identityKey string, limit, skip int, sortOrder string) {
92+
// Build a query object
93+
query := &types.SHIPQuery{}
94+
95+
if findAll {
96+
query.FindAll = boolPtr(findAll)
97+
}
98+
99+
if domain != "" {
100+
query.Domain = strPtr(domain)
101+
}
102+
103+
if topic != "" {
104+
query.Topics = []string{topic}
105+
}
106+
107+
if identityKey != "" {
108+
query.IdentityKey = strPtr(identityKey)
109+
}
110+
111+
if limit != 0 {
112+
query.Limit = intPtr(limit)
113+
}
114+
115+
if skip != 0 {
116+
query.Skip = intPtr(skip)
117+
}
118+
119+
if sortOrder != "" {
120+
so := types.SortOrder(sortOrder)
121+
query.SortOrder = sortOrderPtr(so)
122+
}
123+
124+
// Function should not panic on any input
125+
err := service.validateQuery(query)
126+
127+
// We don't validate the error, just ensure no panic occurs
128+
_ = err
129+
})
130+
}
131+
132+
// FuzzQueryObjectRoundTrip tests JSON marshaling/unmarshaling of query objects.
133+
func FuzzQueryObjectRoundTrip(f *testing.F) {
134+
// Seed corpus with various JSON structures
135+
f.Add(`{"findAll": true}`)
136+
f.Add(`{"domain": "example.com", "topics": ["tm_payments"]}`)
137+
f.Add(`{"limit": 10, "skip": 5, "sortOrder": "asc"}`)
138+
139+
f.Fuzz(func(t *testing.T, jsonStr string) {
140+
// Try to unmarshal into interface
141+
var queryInterface interface{}
142+
err := json.Unmarshal([]byte(jsonStr), &queryInterface)
143+
if err != nil {
144+
return
145+
}
146+
147+
// Try to marshal back to JSON
148+
jsonBytes, err := json.Marshal(queryInterface)
149+
if err != nil {
150+
t.Errorf("Failed to marshal query interface: %v", err)
151+
return
152+
}
153+
154+
// Try to unmarshal into SHIPQuery
155+
var shipQuery types.SHIPQuery
156+
err = json.Unmarshal(jsonBytes, &shipQuery)
157+
158+
// Function should not panic, errors are acceptable
159+
_ = err
160+
})
161+
}
162+
163+
// FuzzDomainString tests domain string validation edge cases.
164+
func FuzzDomainString(f *testing.F) {
165+
// Seed corpus with various domain formats
166+
f.Add("example.com")
167+
f.Add("sub.example.com")
168+
f.Add("example.com:8080")
169+
f.Add("192.168.1.1")
170+
f.Add("localhost")
171+
f.Add("[::1]")
172+
f.Add("")
173+
f.Add(".")
174+
f.Add(".example.com")
175+
f.Add("example.com.")
176+
f.Add("ex ample.com")
177+
f.Add("example..com")
178+
f.Add("example$.com")
179+
// Test very long domain name
180+
longDomain := ""
181+
for i := 0; i < 255; i++ {
182+
longDomain += "a"
183+
}
184+
f.Add(longDomain)
185+
186+
f.Fuzz(func(t *testing.T, domain string) {
187+
// Create a query with the fuzzed domain
188+
domainPtr := &domain
189+
query := &types.SHIPQuery{
190+
Domain: domainPtr,
191+
}
192+
193+
service := &LookupService{storage: nil}
194+
195+
// Function should not panic on any input
196+
err := service.validateQuery(query)
197+
198+
// We don't validate the error, just ensure no panic occurs
199+
_ = err
200+
})
201+
}
202+
203+
// FuzzTopicsList tests topics list validation with various array structures.
204+
func FuzzTopicsList(f *testing.F) {
205+
// Seed corpus with various topic lists
206+
f.Add("tm_payments", "tm_chat", "tm_identity")
207+
f.Add("", "", "")
208+
f.Add("tm_a", "ls_b", "invalid")
209+
f.Add("tm_"+string(make([]byte, 100)), "", "")
210+
211+
f.Fuzz(func(t *testing.T, topic1, topic2, topic3 string) {
212+
// Build topics list
213+
topics := []string{}
214+
if topic1 != "" {
215+
topics = append(topics, topic1)
216+
}
217+
if topic2 != "" {
218+
topics = append(topics, topic2)
219+
}
220+
if topic3 != "" {
221+
topics = append(topics, topic3)
222+
}
223+
224+
query := &types.SHIPQuery{
225+
Topics: topics,
226+
}
227+
228+
service := &LookupService{storage: nil}
229+
230+
// Function should not panic on any input
231+
err := service.validateQuery(query)
232+
233+
// We don't validate the error, just ensure no panic occurs
234+
_ = err
235+
})
236+
}
237+
238+
// FuzzIdentityKeyString tests identity key string validation.
239+
func FuzzIdentityKeyString(f *testing.F) {
240+
// Seed corpus with various identity key formats
241+
f.Add("0123456789abcdef")
242+
f.Add("deadbeef")
243+
f.Add("")
244+
f.Add("not_hex")
245+
f.Add("0x1234")
246+
f.Add(string(make([]byte, 1000)))
247+
248+
f.Fuzz(func(t *testing.T, identityKey string) {
249+
// Create a query with the fuzzed identity key
250+
identityKeyPtr := &identityKey
251+
query := &types.SHIPQuery{
252+
IdentityKey: identityKeyPtr,
253+
}
254+
255+
service := &LookupService{storage: nil}
256+
257+
// Function should not panic on any input
258+
err := service.validateQuery(query)
259+
260+
// We don't validate the error, just ensure no panic occurs
261+
_ = err
262+
})
263+
}
264+
265+
// FuzzPaginationParameters tests pagination parameter validation.
266+
func FuzzPaginationParameters(f *testing.F) {
267+
// Seed corpus with various pagination values
268+
f.Add(0, 0)
269+
f.Add(10, 5)
270+
f.Add(100, 0)
271+
f.Add(1, 1000000)
272+
f.Add(-1, 0)
273+
f.Add(0, -1)
274+
f.Add(-100, -100)
275+
f.Add(2147483647, 2147483647) // Max int32
276+
277+
f.Fuzz(func(t *testing.T, limit, skip int) {
278+
// Create a query with the fuzzed pagination parameters
279+
limitPtr := &limit
280+
skipPtr := &skip
281+
query := &types.SHIPQuery{
282+
Limit: limitPtr,
283+
Skip: skipPtr,
284+
}
285+
286+
service := &LookupService{storage: nil}
287+
288+
// Function should not panic on any input
289+
err := service.validateQuery(query)
290+
291+
// We expect errors for negative values, but no panics
292+
_ = err
293+
})
294+
}

0 commit comments

Comments
 (0)