package bridge import ( "bytes" "encoding/json" "fmt" "net/http" "testing" "time" ) // TestServer_Events_NoSecret tests the exact scenario from the browser extension: // server with empty secret, POST /api/events without X-Verstak-Secret header func TestServer_Events_NoSecret(t *testing.T) { received := make(chan []Event, 1) s := NewServer("", func(evts []Event) { received <- evts }) port, err := s.Start(Config{Secret: ""}) if err != nil { t.Fatal(err) } defer s.Stop() events := []Event{ {ID: "evt_1", Type: "page_visit", URL: "https://example.com", Title: "Example", Domain: "example.com", ActiveSeconds: 120}, } batch := EventBatch{Version: 1, DeviceID: "firefox-test", Events: events} b, _ := json.Marshal(batch) // Exactly what the browser extension does: POST without X-Verstak-Secret req, _ := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d/api/events", port), bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") // NOTE: No X-Verstak-Secret header — extension doesn't send it when secret is empty resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() t.Logf("Status: %d", resp.StatusCode) if resp.StatusCode != 200 { t.Errorf("expected 200, got %d", resp.StatusCode) } select { case evts := <-received: if len(evts) != 1 { t.Errorf("expected 1 event, got %d", len(evts)) } if evts[0].ID != "evt_1" { t.Errorf("expected event ID 'evt_1', got %s", evts[0].ID) } t.Logf("Handler received %d events", len(evts)) case <-time.After(2 * time.Second): t.Fatal("timeout waiting for event handler") } } // TestServer_Events_WithEmptySecretHeader tests what happens when extension // sends X-Verstak-Secret: "" (empty string) — some versions do this func TestServer_Events_WithEmptySecretHeader(t *testing.T) { received := make(chan []Event, 1) s := NewServer("", func(evts []Event) { received <- evts }) port, err := s.Start(Config{Secret: ""}) if err != nil { t.Fatal(err) } defer s.Stop() events := []Event{ {ID: "evt_2", Type: "page_visit", URL: "https://test.com", Domain: "test.com"}, } batch := EventBatch{Version: 1, DeviceID: "firefox-test", Events: events} b, _ := json.Marshal(batch) req, _ := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d/api/events", port), bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Verstak-Secret", "") // Extension sends empty secret resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() t.Logf("Status: %d", resp.StatusCode) if resp.StatusCode != 200 { t.Errorf("expected 200, got %d", resp.StatusCode) } select { case evts := <-received: t.Logf("Handler received %d events", len(evts)) case <-time.After(2 * time.Second): t.Fatal("timeout waiting for event handler") } } // TestServer_Ping_Then_Events simulates the exact flow: ping first, then send events func TestServer_Ping_Then_Events(t *testing.T) { received := make(chan []Event, 1) s := NewServer("", func(evts []Event) { received <- evts }) port, err := s.Start(Config{Secret: ""}) if err != nil { t.Fatal(err) } defer s.Stop() // Step 1: Ping (like extension does) pingResp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/api/ping", port)) if err != nil { t.Fatalf("ping failed: %v", err) } pingResp.Body.Close() if pingResp.StatusCode != 200 { t.Fatalf("ping returned %d", pingResp.StatusCode) } t.Log("Ping OK") // Step 2: Send events (like extension does after ping) events := []Event{ {ID: "evt_3", Type: "page_visit", URL: "https://flow-test.com", Domain: "flow-test.com", ActiveSeconds: 60}, } batch := EventBatch{Version: 1, DeviceID: "firefox-abc123", Events: events} b, _ := json.Marshal(batch) req, _ := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d/api/events", port), bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("events request failed: %v", err) } defer resp.Body.Close() t.Logf("Events status: %d", resp.StatusCode) if resp.StatusCode != 200 { t.Errorf("expected 200, got %d", resp.StatusCode) } select { case evts := <-received: t.Logf("Handler received %d events", len(evts)) case <-time.After(2 * time.Second): t.Fatal("timeout waiting for event handler") } }