Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sundowndev
GitHub Repository: sundowndev/phoneinfoga
Path: blob/master/lib/remote/googlecse_scanner_test.go
988 views
1
package remote
2
3
import (
4
"errors"
5
"github.com/stretchr/testify/assert"
6
"github.com/sundowndev/phoneinfoga/v2/lib/filter"
7
"github.com/sundowndev/phoneinfoga/v2/lib/number"
8
"github.com/sundowndev/phoneinfoga/v2/test"
9
"google.golang.org/api/customsearch/v1"
10
"google.golang.org/api/googleapi"
11
"gopkg.in/h2non/gock.v1"
12
"net/http"
13
"os"
14
"testing"
15
)
16
17
func TestGoogleCSEScanner_Metadata(t *testing.T) {
18
scanner := NewGoogleCSEScanner(&http.Client{})
19
assert.Equal(t, GoogleCSE, scanner.Name())
20
assert.NotEmpty(t, scanner.Description())
21
}
22
23
func TestGoogleCSEScanner_Scan_Success(t *testing.T) {
24
testcases := []struct {
25
name string
26
number *number.Number
27
opts ScannerOptions
28
expected map[string]interface{}
29
wantErrors map[string]error
30
mocks func()
31
}{
32
{
33
name: "test with no results",
34
number: test.NewFakeUSNumber(),
35
expected: map[string]interface{}{
36
"googlecse": GoogleCSEScannerResponse{
37
Homepage: "https://cse.google.com/cse?cx=fake_search_engine_id",
38
ResultCount: 0,
39
TotalResultCount: 0,
40
TotalRequestCount: 2,
41
Items: nil,
42
},
43
},
44
wantErrors: map[string]error{},
45
mocks: func() {
46
gock.New("https://customsearch.googleapis.com").
47
Get("/customsearch/v1").
48
MatchParam("cx", "fake_search_engine_id").
49
// TODO: the matcher below doesn't work for some reason
50
//MatchParam("q", "intext:\"14152229670\" OR intext:\"+14152229670\" OR intext:\"4152229670\" OR intext:\"(415) 222-9670\"").
51
MatchParam("start", "0").
52
Reply(200).
53
JSON(&customsearch.Search{
54
ServerResponse: googleapi.ServerResponse{
55
Header: http.Header{},
56
HTTPStatusCode: 200,
57
},
58
SearchInformation: &customsearch.SearchSearchInformation{
59
FormattedSearchTime: "0",
60
FormattedTotalResults: "0",
61
SearchTime: 0,
62
TotalResults: "0",
63
ForceSendFields: nil,
64
NullFields: nil,
65
},
66
Items: []*customsearch.Result{},
67
})
68
69
gock.New("https://customsearch.googleapis.com").
70
Get("/customsearch/v1").
71
MatchParam("cx", "fake_search_engine_id").
72
// TODO: the matcher below doesn't work for some reason
73
//MatchParam("q", "(ext:doc OR ext:docx OR ext:odt OR ext:pdf OR ext:rtf OR ext:sxw OR ext:psw OR ext:ppt OR ext:pptx OR ext:pps OR ext:csv OR ext:txt OR ext:xls) intext:\"14152229670\" OR intext:\"+14152229670\" OR intext:\"4152229670\" OR intext:\"(415)+222-9670\"").
74
MatchParam("start", "0").
75
Reply(200).
76
JSON(&customsearch.Search{
77
ServerResponse: googleapi.ServerResponse{
78
Header: http.Header{},
79
HTTPStatusCode: 200,
80
},
81
SearchInformation: &customsearch.SearchSearchInformation{
82
FormattedSearchTime: "0",
83
FormattedTotalResults: "0",
84
SearchTime: 0,
85
TotalResults: "0",
86
ForceSendFields: nil,
87
NullFields: nil,
88
},
89
Items: []*customsearch.Result{},
90
})
91
},
92
},
93
{
94
name: "test with options and no results",
95
number: test.NewFakeUSNumber(),
96
opts: ScannerOptions{
97
"GOOGLECSE_CX": "custom_cx",
98
"GOOGLE_API_KEY": "secret",
99
},
100
expected: map[string]interface{}{
101
"googlecse": GoogleCSEScannerResponse{
102
Homepage: "https://cse.google.com/cse?cx=custom_cx",
103
ResultCount: 0,
104
TotalResultCount: 0,
105
TotalRequestCount: 2,
106
Items: nil,
107
},
108
},
109
wantErrors: map[string]error{},
110
mocks: func() {
111
gock.New("https://customsearch.googleapis.com").
112
Get("/customsearch/v1").
113
MatchParam("cx", "custom_cx").
114
// TODO: ensure that custom api key is used
115
// MatchHeader("Authorization", "secret").
116
// TODO: the matcher below doesn't work for some reason
117
//MatchParam("q", "intext:\"14152229670\" OR intext:\"+14152229670\" OR intext:\"4152229670\" OR intext:\"(415) 222-9670\"").
118
MatchParam("start", "0").
119
Reply(200).
120
JSON(&customsearch.Search{
121
ServerResponse: googleapi.ServerResponse{
122
Header: http.Header{},
123
HTTPStatusCode: 200,
124
},
125
SearchInformation: &customsearch.SearchSearchInformation{
126
FormattedSearchTime: "0",
127
FormattedTotalResults: "0",
128
SearchTime: 0,
129
TotalResults: "0",
130
ForceSendFields: nil,
131
NullFields: nil,
132
},
133
Items: []*customsearch.Result{},
134
})
135
136
gock.New("https://customsearch.googleapis.com").
137
Get("/customsearch/v1").
138
MatchParam("cx", "custom_cx").
139
// TODO: ensure that custom api key is used
140
// MatchHeader("Authorization", "secret").
141
// TODO: the matcher below doesn't work for some reason
142
//MatchParam("q", "(ext:doc OR ext:docx OR ext:odt OR ext:pdf OR ext:rtf OR ext:sxw OR ext:psw OR ext:ppt OR ext:pptx OR ext:pps OR ext:csv OR ext:txt OR ext:xls) intext:\"14152229670\" OR intext:\"+14152229670\" OR intext:\"4152229670\" OR intext:\"(415)+222-9670\"").
143
MatchParam("start", "0").
144
Reply(200).
145
JSON(&customsearch.Search{
146
ServerResponse: googleapi.ServerResponse{
147
Header: http.Header{},
148
HTTPStatusCode: 200,
149
},
150
SearchInformation: &customsearch.SearchSearchInformation{
151
FormattedSearchTime: "0",
152
FormattedTotalResults: "0",
153
SearchTime: 0,
154
TotalResults: "0",
155
ForceSendFields: nil,
156
NullFields: nil,
157
},
158
Items: []*customsearch.Result{},
159
})
160
},
161
},
162
{
163
name: "test with results",
164
number: test.NewFakeUSNumber(),
165
expected: map[string]interface{}{
166
"googlecse": GoogleCSEScannerResponse{
167
Homepage: "https://cse.google.com/cse?cx=fake_search_engine_id",
168
ResultCount: 2,
169
TotalResultCount: 2,
170
TotalRequestCount: 2,
171
Items: []ResultItem{
172
{
173
Title: "Result 1",
174
URL: "https://result1.com",
175
},
176
{
177
Title: "Result 2",
178
URL: "https://result2.com",
179
},
180
},
181
},
182
},
183
wantErrors: map[string]error{},
184
mocks: func() {
185
gock.New("https://customsearch.googleapis.com").
186
Get("/customsearch/v1").
187
MatchParam("cx", "fake_search_engine_id").
188
// TODO: the matcher below doesn't work for some reason
189
//MatchParam("q", "intext:\"14152229670\" OR intext:\"+14152229670\" OR intext:\"4152229670\" OR intext:\"(415) 222-9670\"").
190
MatchParam("start", "0").
191
Reply(200).
192
JSON(&customsearch.Search{
193
ServerResponse: googleapi.ServerResponse{
194
Header: http.Header{},
195
HTTPStatusCode: 200,
196
},
197
SearchInformation: &customsearch.SearchSearchInformation{
198
FormattedSearchTime: "0",
199
FormattedTotalResults: "2",
200
SearchTime: 0,
201
TotalResults: "2",
202
ForceSendFields: nil,
203
NullFields: nil,
204
},
205
Items: []*customsearch.Result{
206
{
207
Title: "Result 1",
208
Link: "https://result1.com",
209
},
210
{
211
Title: "Result 2",
212
Link: "https://result2.com",
213
},
214
},
215
})
216
217
gock.New("https://customsearch.googleapis.com").
218
Get("/customsearch/v1").
219
MatchParam("cx", "fake_search_engine_id").
220
// TODO: the matcher below doesn't work for some reason
221
//MatchParam("q", "(ext:doc OR ext:docx OR ext:odt OR ext:pdf OR ext:rtf OR ext:sxw OR ext:psw OR ext:ppt OR ext:pptx OR ext:pps OR ext:csv OR ext:txt OR ext:xls) intext:\"14152229670\" OR intext:\"+14152229670\" OR intext:\"4152229670\" OR intext:\"(415)+222-9670\"").
222
MatchParam("start", "0").
223
Reply(200).
224
JSON(&customsearch.Search{
225
ServerResponse: googleapi.ServerResponse{
226
Header: http.Header{},
227
HTTPStatusCode: 200,
228
},
229
SearchInformation: &customsearch.SearchSearchInformation{
230
FormattedSearchTime: "0",
231
FormattedTotalResults: "0",
232
SearchTime: 0,
233
TotalResults: "0",
234
ForceSendFields: nil,
235
NullFields: nil,
236
},
237
Items: []*customsearch.Result{},
238
})
239
},
240
},
241
{
242
name: "test with rate limit error",
243
number: test.NewFakeUSNumber(),
244
expected: map[string]interface{}{},
245
wantErrors: map[string]error{
246
"googlecse": errors.New("rate limit exceeded, see https://developers.google.com/custom-search/v1/overview#pricing"),
247
},
248
mocks: func() {
249
gock.New("https://customsearch.googleapis.com").
250
Get("/customsearch/v1").
251
MatchParam("cx", "fake_search_engine_id").
252
MatchParam("start", "0").
253
Reply(429).
254
JSON(&googleapi.Error{
255
Code: 429,
256
Message: "rate limit exceeded",
257
Details: nil,
258
Body: "rate limit exceeded",
259
Header: http.Header{},
260
Errors: []googleapi.ErrorItem{},
261
})
262
},
263
},
264
{
265
name: "test with basic error",
266
number: test.NewFakeUSNumber(),
267
expected: map[string]interface{}{},
268
wantErrors: map[string]error{
269
"googlecse": &googleapi.Error{
270
Code: 403,
271
Message: "",
272
Details: nil,
273
Body: "{\"code\":403,\"message\":\"dummy error\",\"details\":null,\"Body\":\"dummy error\",\"Header\":{},\"Errors\":[]}\n",
274
Header: http.Header{
275
"Content-Type": []string{"application/json"},
276
},
277
Errors: nil,
278
},
279
},
280
mocks: func() {
281
gock.New("https://customsearch.googleapis.com").
282
Get("/customsearch/v1").
283
MatchParam("cx", "fake_search_engine_id").
284
MatchParam("start", "0").
285
Reply(403).
286
JSON(&googleapi.Error{
287
Code: 403,
288
Message: "dummy error",
289
Details: nil,
290
Body: "dummy error",
291
Header: http.Header{},
292
Errors: []googleapi.ErrorItem{},
293
})
294
},
295
},
296
}
297
298
for _, tt := range testcases {
299
t.Run(tt.name, func(t *testing.T) {
300
_ = os.Setenv("GOOGLECSE_CX", "fake_search_engine_id")
301
_ = os.Setenv("GOOGLE_API_KEY", "fake_api_key")
302
defer os.Unsetenv("GOOGLECSE_CX")
303
defer os.Unsetenv("GOOGLE_API_KEY")
304
305
tt.mocks()
306
defer gock.Off() // Flush pending mocks after test execution
307
308
scanner := NewGoogleCSEScanner(&http.Client{})
309
remote := NewLibrary(filter.NewEngine())
310
remote.AddScanner(scanner)
311
312
if scanner.DryRun(*tt.number, tt.opts) != nil {
313
t.Fatal("DryRun() should return nil")
314
}
315
316
got, errs := remote.Scan(tt.number, tt.opts)
317
if len(tt.wantErrors) > 0 {
318
assert.Equal(t, tt.wantErrors, errs)
319
} else {
320
assert.Len(t, errs, 0)
321
}
322
assert.Equal(t, tt.expected, got)
323
})
324
}
325
}
326
327
func TestGoogleCSEScanner_DryRun(t *testing.T) {
328
_ = os.Setenv("GOOGLECSE_CX", "abc")
329
_ = os.Setenv("GOOGLE_API_KEY", "abc")
330
defer os.Unsetenv("GOOGLECSE_CX")
331
defer os.Unsetenv("GOOGLE_API_KEY")
332
scanner := NewGoogleCSEScanner(&http.Client{})
333
assert.Nil(t, scanner.DryRun(*test.NewFakeUSNumber(), ScannerOptions{}))
334
}
335
336
func TestGoogleCSEScanner_DryRunWithOptions(t *testing.T) {
337
errStr := "search engine ID and/or API key is not defined"
338
339
scanner := NewGoogleCSEScanner(&http.Client{})
340
assert.Nil(t, scanner.DryRun(*test.NewFakeUSNumber(), ScannerOptions{"GOOGLECSE_CX": "test", "GOOGLE_API_KEY": "secret"}))
341
assert.EqualError(t, scanner.DryRun(*test.NewFakeUSNumber(), ScannerOptions{"GOOGLECSE_CX": "", "GOOGLE_API_KEY": ""}), errStr)
342
assert.EqualError(t, scanner.DryRun(*test.NewFakeUSNumber(), ScannerOptions{"GOOGLECSE_CX": "test"}), errStr)
343
assert.EqualError(t, scanner.DryRun(*test.NewFakeUSNumber(), ScannerOptions{"GOOGLE_API_KEY": "test"}), errStr)
344
}
345
346
func TestGoogleCSEScanner_DryRun_Error(t *testing.T) {
347
scanner := NewGoogleCSEScanner(&http.Client{})
348
assert.EqualError(t, scanner.DryRun(*test.NewFakeUSNumber(), ScannerOptions{}), "search engine ID and/or API key is not defined")
349
}
350
351
func TestGoogleCSEScanner_MaxResults(t *testing.T) {
352
testcases := []struct {
353
value string
354
expected int64
355
}{
356
{
357
value: "",
358
expected: 10,
359
},
360
{
361
value: "20",
362
expected: 20,
363
},
364
{
365
value: "120",
366
expected: 100,
367
},
368
}
369
370
defer os.Clearenv()
371
372
for _, tt := range testcases {
373
_ = os.Setenv("GOOGLECSE_MAX_RESULTS", tt.value)
374
375
scanner := NewGoogleCSEScanner(nil)
376
377
assert.Equal(t, tt.expected, scanner.(*googleCSEScanner).MaxResults)
378
}
379
}
380
381