Skip to content

Commit 65a072b

Browse files
h0tak88rclaude
andcommitted
test: track 32 pre-existing *_test.go files so CI runs the full suite
These unit tests existed on disk but were silently excluded by the old `.gitignore` `*test*` rule (fixed in e0fdee4), so CI's `go test ./...` never ran them. Add them to version control. All are hermetic (no network, no external tools, no fixtures — file reads use self-created temp files) and pass locally with CGO_ENABLED=1 go test ./... Packages covered: api, brain, config, envloader, logger, mcp, observability, r2storage, version, and scanner subpackages (asr, backup, depconfusion, gf, s3, scope, urls, wp-confusion, zerodays) plus utils and tools. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7b2f881 commit 65a072b

32 files changed

Lines changed: 4253 additions & 0 deletions

internal/api/api_test.go

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
package api
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestScanResultSizeBytesNil(t *testing.T) {
8+
if got := scanResultSizeBytes(nil); got != 0 {
9+
t.Errorf("scanResultSizeBytes(nil) = %d, want 0", got)
10+
}
11+
}
12+
13+
func TestScanResultSizeBytesEmpty(t *testing.T) {
14+
r := &ScanResult{}
15+
if got := scanResultSizeBytes(r); got != 0 {
16+
t.Errorf("scanResultSizeBytes({}) = %d, want 0", got)
17+
}
18+
}
19+
20+
func TestScanResultSizeBytesNonEmpty(t *testing.T) {
21+
r := &ScanResult{
22+
ScanID: "abc-123",
23+
ScanType: "domain_run",
24+
Status: "completed",
25+
Output: "output data",
26+
Error: "some error",
27+
}
28+
expected := int64(len(r.Output) + len(r.Error) + len(r.ScanID) + len(r.ScanType) + len(r.Status))
29+
if got := scanResultSizeBytes(r); got != expected {
30+
t.Errorf("scanResultSizeBytes() = %d, want %d", got, expected)
31+
}
32+
}
33+
34+
func TestExtractScanTargetFromCommandEmpty(t *testing.T) {
35+
if got := extractScanTargetFromCommand(nil, "domain_run"); got != "" {
36+
t.Errorf("extractScanTargetFromCommand(nil) = %q, want empty", got)
37+
}
38+
if got := extractScanTargetFromCommand([]string{}, "domain_run"); got != "" {
39+
t.Errorf("extractScanTargetFromCommand([]) = %q, want empty", got)
40+
}
41+
}
42+
43+
func TestExtractScanTargetFromCommandDomainShort(t *testing.T) {
44+
got := extractScanTargetFromCommand([]string{"./autorecon", "-d", "example.com"}, "domain_run")
45+
if got != "example.com" {
46+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "example.com")
47+
}
48+
}
49+
50+
func TestExtractScanTargetFromCommandDomainLong(t *testing.T) {
51+
got := extractScanTargetFromCommand([]string{"./autorecon", "--domain", "example.com"}, "domain_run")
52+
if got != "example.com" {
53+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "example.com")
54+
}
55+
}
56+
57+
func TestExtractScanTargetFromCommandSubdomainShort(t *testing.T) {
58+
got := extractScanTargetFromCommand([]string{"./autorecon", "-s", "sub.example.com"}, "subdomain_run")
59+
if got != "sub.example.com" {
60+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "sub.example.com")
61+
}
62+
}
63+
64+
func TestExtractScanTargetFromCommandSubdomainLong(t *testing.T) {
65+
got := extractScanTargetFromCommand([]string{"./autorecon", "--subdomain", "sub.example.com"}, "subdomain_run")
66+
if got != "sub.example.com" {
67+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "sub.example.com")
68+
}
69+
}
70+
71+
func TestExtractScanTargetFromCommandURL(t *testing.T) {
72+
got := extractScanTargetFromCommand([]string{"./autorecon", "-u", "https://example.com"}, "url_scan")
73+
if got != "https://example.com" {
74+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "https://example.com")
75+
}
76+
}
77+
78+
func TestExtractScanTargetFromCommandURNilValue(t *testing.T) {
79+
got := extractScanTargetFromCommand([]string{"./autorecon", "-u", ""}, "url_scan")
80+
if got != "" {
81+
t.Errorf("extractScanTargetFromCommand() = %q, want empty", got)
82+
}
83+
}
84+
85+
func TestExtractScanTargetFromCommandURLFirstArgOnly(t *testing.T) {
86+
got := extractScanTargetFromCommand([]string{"-u"}, "url_scan")
87+
if got != "" {
88+
t.Errorf("extractScanTargetFromCommand() = %q, want empty", got)
89+
}
90+
}
91+
92+
func TestExtractScanTargetFromCommandBucket(t *testing.T) {
93+
got := extractScanTargetFromCommand([]string{"./autorecon", "-b", "my-bucket"}, "s3")
94+
if got != "my-bucket" {
95+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "my-bucket")
96+
}
97+
}
98+
99+
func TestExtractScanTargetFromCommandRepo(t *testing.T) {
100+
got := extractScanTargetFromCommand([]string{"./autorecon", "-r", "owner/repo"}, "github")
101+
if got != "owner/repo" {
102+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "owner/repo")
103+
}
104+
}
105+
106+
func TestExtractScanTargetFromCommandOrg(t *testing.T) {
107+
got := extractScanTargetFromCommand([]string{"./autorecon", "-o", "my-org"}, "github_org")
108+
if got != "my-org" {
109+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, "my-org")
110+
}
111+
}
112+
113+
func TestExtractScanTargetFromCommandZerodays(t *testing.T) {
114+
got := extractScanTargetFromCommand([]string{"./autorecon", "-f", "/data/zerodays.txt"}, "zerodays")
115+
want := "file:zerodays.txt"
116+
if got != want {
117+
t.Errorf("extractScanTargetFromCommand() = %q, want %q", got, want)
118+
}
119+
}
120+
121+
func TestExtractScanTargetFromCommandNoMatch(t *testing.T) {
122+
got := extractScanTargetFromCommand([]string{"./autorecon", "--verbose"}, "domain_run")
123+
if got != "" {
124+
t.Errorf("extractScanTargetFromCommand() = %q, want empty", got)
125+
}
126+
}
127+
128+
func TestExtractScanTargetFromCommandDomainPriorityOverURL(t *testing.T) {
129+
got := extractScanTargetFromCommand([]string{"./autorecon", "-d", "example.com", "-u", "https://other.com"}, "domain_run")
130+
if got != "example.com" {
131+
t.Errorf("extractScanTargetFromCommand() = %q, want %q (domain before url)", got, "example.com")
132+
}
133+
}
134+
135+
func TestExtractScanTargetFromCommandBucketWrongScanType(t *testing.T) {
136+
got := extractScanTargetFromCommand([]string{"./autorecon", "-b", "my-bucket"}, "domain_run")
137+
if got != "" {
138+
t.Errorf("extractScanTargetFromCommand() = %q, want empty (bucket only valid for s3)", got)
139+
}
140+
}
141+
142+
func TestTargetHostForR2PrefixesSimple(t *testing.T) {
143+
got := targetHostForR2Prefixes("example.com")
144+
if got != "example.com" {
145+
t.Errorf("targetHostForR2Prefixes() = %q, want %q", got, "example.com")
146+
}
147+
}
148+
149+
func TestTargetHostForR2PrefixesWithHTTP(t *testing.T) {
150+
got := targetHostForR2Prefixes("http://example.com")
151+
if got != "example.com" {
152+
t.Errorf("targetHostForR2Prefixes() = %q, want %q", got, "example.com")
153+
}
154+
}
155+
156+
func TestTargetHostForR2PrefixesWithHTTPS(t *testing.T) {
157+
got := targetHostForR2Prefixes("https://example.com")
158+
if got != "example.com" {
159+
t.Errorf("targetHostForR2Prefixes() = %q, want %q", got, "example.com")
160+
}
161+
}
162+
163+
func TestTargetHostForR2PrefixesWithWWW(t *testing.T) {
164+
got := targetHostForR2Prefixes("www.example.com")
165+
if got != "example.com" {
166+
t.Errorf("targetHostForR2Prefixes() = %q, want %q", got, "example.com")
167+
}
168+
}
169+
170+
func TestTargetHostForR2PrefixesWithPath(t *testing.T) {
171+
got := targetHostForR2Prefixes("https://www.example.com/path/to/page")
172+
if got != "example.com" {
173+
t.Errorf("targetHostForR2Prefixes() = %q, want %q", got, "example.com")
174+
}
175+
}
176+
177+
func TestTargetHostForR2PrefixesCase(t *testing.T) {
178+
got := targetHostForR2Prefixes("WWW.EXAMPLE.COM")
179+
if got != "example.com" {
180+
t.Errorf("targetHostForR2Prefixes() = %q, want %q", got, "example.com")
181+
}
182+
}
183+
184+
func TestTargetHostForR2PrefixesEmpty(t *testing.T) {
185+
got := targetHostForR2Prefixes("")
186+
if got != "" {
187+
t.Errorf("targetHostForR2Prefixes() = %q, want empty", got)
188+
}
189+
}
190+
191+
func TestWorkflowScanR2Prefixes(t *testing.T) {
192+
got := workflowScanR2Prefixes("example.com")
193+
if len(got) != 4 {
194+
t.Errorf("workflowScanR2Prefixes() len = %d, want 4: %v", len(got), got)
195+
}
196+
expectedPrefixes := []string{
197+
"new-results/example.com/",
198+
"results/example.com/",
199+
"new-results/misconfig/example.com/",
200+
"misconfig/example.com/",
201+
}
202+
for _, want := range expectedPrefixes {
203+
found := false
204+
for _, g := range got {
205+
if g == want {
206+
found = true
207+
break
208+
}
209+
}
210+
if !found {
211+
t.Errorf("workflowScanR2Prefixes() missing %q in %v", want, got)
212+
}
213+
}
214+
}
215+
216+
func TestWorkflowScanR2PrefixesWithWWW(t *testing.T) {
217+
got := workflowScanR2Prefixes("www.example.com")
218+
// should normalize to example.com
219+
if len(got) < 1 || got[0] != "new-results/example.com/" {
220+
t.Errorf("workflowScanR2Prefixes() = %v, should use normalized host", got)
221+
}
222+
}
223+
224+
func TestWorkflowScanR2PrefixesEmpty(t *testing.T) {
225+
got := workflowScanR2Prefixes("")
226+
if got != nil {
227+
t.Errorf("workflowScanR2Prefixes() = %v, want nil", got)
228+
}
229+
}
230+
231+
func TestIsR2KeyIndexableArtifactSpecialFiles(t *testing.T) {
232+
if isR2KeyIndexableArtifact("scan-manifest.json") {
233+
t.Error("scan-manifest.json should not be indexable")
234+
}
235+
if isR2KeyIndexableArtifact("cache_info.json") {
236+
t.Error("cache_info.json should not be indexable")
237+
}
238+
if isR2KeyIndexableArtifact("report-table.json") {
239+
t.Error("report-table.json should not be indexable")
240+
}
241+
}
242+
243+
func TestIsR2KeyIndexableArtifactLiteUploadsPrefix(t *testing.T) {
244+
if isR2KeyIndexableArtifact(".lite-uploads-somehash") {
245+
t.Error(".lite-uploads-* should not be indexable")
246+
}
247+
}
248+
249+
func TestIsR2KeyIndexableArtifactTempPrefixes(t *testing.T) {
250+
if isR2KeyIndexableArtifact("temp-something.txt") {
251+
t.Error("temp-* should not be indexable")
252+
}
253+
if isR2KeyIndexableArtifact("dangling-ip-temp.json") {
254+
t.Error("dangling-ip-temp* should not be indexable")
255+
}
256+
}
257+
258+
func TestIsR2KeyIndexableArtifactTempURL(t *testing.T) {
259+
if isR2KeyIndexableArtifact("temp-url.txt") {
260+
t.Error("temp-url.txt should not be indexable")
261+
}
262+
if isR2KeyIndexableArtifact("Temp-URL.txt") {
263+
t.Error("Temp-URL.txt should not be indexable (case-insensitive)")
264+
}
265+
}
266+
267+
func TestIsR2KeyIndexableArtifactValidExtensions(t *testing.T) {
268+
validExts := []string{".txt", ".json", ".log", ".csv", ".html", ".md", ".bin", ".xml"}
269+
for _, ext := range validExts {
270+
t.Run(ext, func(t *testing.T) {
271+
if !isR2KeyIndexableArtifact("results/output" + ext) {
272+
t.Errorf("isR2KeyIndexableArtifact(output%s) should be true", ext)
273+
}
274+
})
275+
}
276+
}
277+
278+
func TestIsR2KeyIndexableArtifactInvalidExtension(t *testing.T) {
279+
if isR2KeyIndexableArtifact("results/image.png") {
280+
t.Error("image.png should not be indexable")
281+
}
282+
if isR2KeyIndexableArtifact("results/video.mp4") {
283+
t.Error("video.mp4 should not be indexable")
284+
}
285+
}
286+
287+
func TestIsR2KeyIndexableArtifactExtensionCaseInsensitive(t *testing.T) {
288+
if !isR2KeyIndexableArtifact("results/output.TXT") {
289+
t.Error("output.TXT should be indexable (case-insensitive extension)")
290+
}
291+
if !isR2KeyIndexableArtifact("results/output.JSON") {
292+
t.Error("output.JSON should be indexable (case-insensitive extension)")
293+
}
294+
}
295+
296+
func TestShouldSkipArtifactManifestFiles(t *testing.T) {
297+
if !shouldSkipArtifact("scan-manifest.json") {
298+
t.Error("scan-manifest.json should be skipped")
299+
}
300+
if !shouldSkipArtifact("cache_info.json") {
301+
t.Error("cache_info.json should be skipped")
302+
}
303+
if !shouldSkipArtifact("report-table.json") {
304+
t.Error("report-table.json should be skipped")
305+
}
306+
}
307+
308+
func TestShouldSkipArtifactByName(t *testing.T) {
309+
skipFiles := []string{
310+
"misconfig-scan-results.txt",
311+
"ffuf-results.txt",
312+
"kxss-results.txt",
313+
"exposure-findings.txt",
314+
"wp-confusion-results.txt",
315+
"js-secrets.txt",
316+
"nuclei-summary.txt",
317+
"all-subs.txt",
318+
"live-subs.txt",
319+
"live-hosts.txt",
320+
"all-urls.txt",
321+
"subdomains.txt",
322+
"enumerated-subs.txt",
323+
"urls.json",
324+
"js-urls.json",
325+
"subdomains.json",
326+
"ports.json",
327+
"livehosts.json",
328+
"cname-records.json",
329+
}
330+
for _, f := range skipFiles {
331+
t.Run(f, func(t *testing.T) {
332+
if !shouldSkipArtifact(f) {
333+
t.Errorf("shouldSkipArtifact(%q) should be true", f)
334+
}
335+
})
336+
}
337+
}
338+
339+
func TestShouldSkipArtifactByNameCaseInsensitive(t *testing.T) {
340+
if !shouldSkipArtifact("NUCLEI-SUMMARY.TXT") {
341+
t.Error("shouldSkipArtifact should be case-insensitive")
342+
}
343+
if !shouldSkipArtifact("Js-Urls.json") {
344+
t.Error("shouldSkipArtifact should be case-insensitive")
345+
}
346+
}
347+
348+
func TestShouldSkipArtifactAllowedFile(t *testing.T) {
349+
if shouldSkipArtifact("my-custom-results.txt") {
350+
t.Error("custom results file should not be skipped")
351+
}
352+
if shouldSkipArtifact("unique-output.json") {
353+
t.Error("unique output file should not be skipped")
354+
}
355+
}

0 commit comments

Comments
 (0)