Files
ARTEX/traffic/external_test.go
T
BaaRai d8f47d4359 Traffic ingest: capture operator browsing via a browser extension
Closes #33. The MITM proxy only sees the agent's own actions, so
anything behind a human gate (signup captcha, email OTP, 2FA, SSO)
left the authenticated surface dark. The operator's browser becomes a
capture source instead:

- traffic: exported RecordExternal storing an externally captured
  exchange through the same spill/blob/FTS path as proxy traffic
  (insert logic shared with record()); rejects unparsable URLs and
  empty methods
- server: POST /api/traffic/ingest inside the authenticated API group,
  batch capped at 50 entries, bodies truncated at 256 KiB, per-entry
  ingested/failed counts
- extension/: Manifest V3 Chrome/Edge extension driving the
  chrome.debugger Network domain on the chosen tab, batching text
  exchanges to the ingest endpoint with the operator's URL + JWT in
  chrome.storage.local; binary bodies skipped; flush on batch/stop
- tests for both layers, README section
2026-10-11 14:53:08 +02:00

78 lines
2.3 KiB
Go

package traffic
import (
"strings"
"testing"
)
func TestRecordExternalStoresLikeProxyTraffic(t *testing.T) {
tr, _ := openTraffic(t)
big := strings.Repeat("A", 2<<20) // spills to the blob store
err := tr.RecordExternal(ExternalExchange{
Method: "POST",
URL: "https://api.example.com/v1/login",
Status: 200,
RequestHeaders: map[string]string{"content-type": "application/json", "authorization": "Bearer xyz"},
RequestBody: `{"user":"u1","password":"p1"}`,
ResponseHeaders: map[string]string{"content-type": "application/json"},
ResponseBody: `{"token":"tok"}`,
})
if err != nil {
t.Fatalf("RecordExternal small: %v", err)
}
if err := tr.RecordExternal(ExternalExchange{
Method: "GET",
URL: "https://api.example.com/v1/items?page=1",
Status: 404,
ResponseHeaders: map[string]string{"content-type": "text/html"},
ResponseBody: big,
}); err != nil {
t.Fatalf("RecordExternal spilled: %v", err)
}
if n, _ := tr.Count(); n != 2 {
t.Fatalf("count = %d, want 2", n)
}
rows, total, err := tr.Page(PageQuery{Query: "login", RespMin: -1, RespMax: -1}, 0, 10)
if err != nil {
t.Fatal(err)
}
if total != 1 || len(rows) != 1 {
t.Fatalf("filtered page = %d rows (total %d), want 1", len(rows), total)
}
req, resp, err := tr.Get(rows[0].ID)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(req, "POST /v1/login") || !strings.Contains(req, "Bearer xyz") || !strings.Contains(req, `"user":"u1"`) {
t.Fatalf("request side incomplete: %q", req)
}
if !strings.Contains(resp, `{"token":"tok"}`) {
t.Fatalf("response side incomplete: %q", resp)
}
// The spilled entry must be FTS-searchable by its body content too.
rows, total, err = tr.Page(PageQuery{Body: strings.Repeat("A", 64), RespMin: -1, RespMax: -1}, 0, 10)
if err != nil {
t.Fatal(err)
}
if total != 1 || len(rows) != 1 {
t.Fatalf("spilled-body FTS search = %d rows (total %d), want 1", len(rows), total)
}
}
func TestRecordExternalRejectsBadInput(t *testing.T) {
tr, _ := openTraffic(t)
cases := []ExternalExchange{
{Method: "GET", URL: "not a url"},
{Method: "GET", URL: ""},
{Method: "", URL: "https://api.example.com/"},
}
for i, c := range cases {
if err := tr.RecordExternal(c); err == nil {
t.Fatalf("case %d: expected an error for %+v", i, c)
}
}
}