mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
ARTEX — AI 自主渗透测试系统(Go 后端 + Next.js 前端,单二进制内嵌)。 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
143 lines
4.1 KiB
Go
143 lines
4.1 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// TestParseScopeLine covers classification + guardrails without a DB.
|
|
func TestParseScopeLine(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
kind string
|
|
wantErr bool
|
|
}{
|
|
{"example.com", "domain", false},
|
|
{"https://sub.example.com/path", "domain", false},
|
|
{"1.2.3.4", "ip", false},
|
|
{"10.0.0.0/8", "", true}, // over-broad IPv4 (< /16)
|
|
{"198.51.100.0/24", "cidr", false},
|
|
{"co.uk", "", true}, // bare public suffix
|
|
{"not a host", "", true},
|
|
{"1.2.3.1-1.2.3.9", "", true}, // ranges must be CIDR
|
|
}
|
|
for _, c := range cases {
|
|
r, err := ParseScopeLine(c.in)
|
|
if c.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ParseScopeLine(%q) want error, got %+v", c.in, r)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("ParseScopeLine(%q) unexpected error: %v", c.in, err)
|
|
continue
|
|
}
|
|
if r.Kind != c.kind {
|
|
t.Errorf("ParseScopeLine(%q) kind=%q want %q", c.in, r.Kind, c.kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCompanyScopeAttribution exercises the full loop against dev PG: create
|
|
// company (unique name), add scope, and verify auto-attribution at insert time,
|
|
// backfill of a pre-existing asset, CIDR + domain-suffix matching, and that
|
|
// out-of-scope assets stay unattributed.
|
|
func TestCompanyScopeAttribution(t *testing.T) {
|
|
d, err := Open(testDSN(t))
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v) — skipping", err)
|
|
}
|
|
as := d.Assets()
|
|
cs := d.Companies()
|
|
|
|
var startMax int64
|
|
if err := d.QueryRow(`SELECT COALESCE(MAX(id),0) FROM assets`).Scan(&startMax); err != nil {
|
|
d.Close()
|
|
t.Fatalf("startMax: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_, _ = d.Exec(`DELETE FROM assets WHERE id > $1`, startMax)
|
|
d.Close()
|
|
})
|
|
|
|
uniq := startMax + 1
|
|
root := fmt.Sprintf("scopetest%d.com", uniq)
|
|
sub := "api." + root
|
|
ipIn := "198.51.100.9"
|
|
ipOut := "203.0.113.9"
|
|
outDomain := fmt.Sprintf("other%d.net", uniq)
|
|
|
|
cid, _, err := cs.UpsertCompany(fmt.Sprintf("ScopeCo %d", uniq), "")
|
|
if err != nil {
|
|
t.Fatalf("UpsertCompany: %v", err)
|
|
}
|
|
|
|
// a pre-existing asset (inserted BEFORE any scope) — must be back-filled.
|
|
preID, err := as.UpsertSubdomain(UpsertSubdomainReq{Domain: sub})
|
|
if err != nil {
|
|
t.Fatalf("pre upsert: %v", err)
|
|
}
|
|
var preCompanyID *int64
|
|
d.QueryRow(`SELECT company_id FROM assets WHERE id = $1`, preID).Scan(&preCompanyID)
|
|
if preCompanyID != nil {
|
|
t.Fatalf("pre-scope asset should be unattributed, got %v", *preCompanyID)
|
|
}
|
|
|
|
rules, invalid := ParseScopeLines(root + "\n198.51.100.0/24\nco.uk")
|
|
if len(rules) != 2 {
|
|
t.Fatalf("want 2 valid rules, got %d (%+v)", len(rules), rules)
|
|
}
|
|
if len(invalid) != 1 {
|
|
t.Fatalf("want 1 invalid line, got %v", invalid)
|
|
}
|
|
cs.AddScope(cid, []string{root, "198.51.100.0/24"}, "unit test")
|
|
|
|
mustCid := func(id int64, want int64, label string) {
|
|
var cID *int64
|
|
d.QueryRow(`SELECT company_id FROM assets WHERE id = $1`, id).Scan(&cID)
|
|
if cID == nil {
|
|
t.Fatalf("%s company_id = nil, want %d", label, want)
|
|
}
|
|
if *cID != want {
|
|
t.Fatalf("%s company_id = %d, want %d", label, *cID, want)
|
|
}
|
|
}
|
|
mustNil := func(id int64, label string) {
|
|
var cID *int64
|
|
d.QueryRow(`SELECT company_id FROM assets WHERE id = $1`, id).Scan(&cID)
|
|
if cID != nil {
|
|
t.Fatalf("%s should be unattributed, got %d", label, *cID)
|
|
}
|
|
}
|
|
|
|
// backfill attributed the pre-existing subdomain (domain suffix match).
|
|
mustCid(preID, cid, "pre-existing subdomain (backfill)")
|
|
|
|
// insert-time attribution: ip in CIDR, another subdomain.
|
|
ipInID, err := as.UpsertIP(UpsertIPReq{IP: ipIn})
|
|
if err != nil {
|
|
t.Fatalf("UpsertIP in: %v", err)
|
|
}
|
|
mustCid(ipInID, cid, "in-CIDR ip (insert-time)")
|
|
|
|
sub2ID, err := as.UpsertSubdomain(UpsertSubdomainReq{Domain: "www." + root})
|
|
if err != nil {
|
|
t.Fatalf("UpsertSubdomain: %v", err)
|
|
}
|
|
mustCid(sub2ID, cid, "new subdomain (insert-time)")
|
|
|
|
// out of scope stays unattributed.
|
|
outID, err := as.UpsertRootDomain(UpsertRootDomainReq{Domain: outDomain})
|
|
if err != nil {
|
|
t.Fatalf("UpsertRootDomain out: %v", err)
|
|
}
|
|
mustNil(outID, "out-of-scope domain")
|
|
|
|
ipOutID, err := as.UpsertIP(UpsertIPReq{IP: ipOut})
|
|
if err != nil {
|
|
t.Fatalf("UpsertIP out: %v", err)
|
|
}
|
|
mustNil(ipOutID, "out-of-CIDR ip")
|
|
}
|