package widget import ( "encoding/json" "net" "net/http" "net/http/httptest" "strings" "testing" "github.com/gochat/gochat/internal/geoip" "github.com/gochat/gochat/internal/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/datatypes" ) type handlerGeoResolver struct { location geoip.Location seenIP net.IP } func (r *handlerGeoResolver) Lookup(ip net.IP) (geoip.Location, bool) { r.seenIP = append(net.IP(nil), ip...) return r.location, true } func TestWidgetHandler_ConfigUsesServerClientIPForAnonymousName(t *testing.T) { db, router, handler := setupWidgetHandlerTest(t) _, _ = seedWidgetHandlerData(t, db) resolver := &handlerGeoResolver{location: geoip.Location{Province: "河北省", City: "保定市"}} handler.widgetService.SetVisitorGeoResolver(resolver) recorder := httptest.NewRecorder() req, err := http.NewRequest(http.MethodPost, "/api/v1/widget/config?website_token=handler_ws_token_123", nil) require.NoError(t, err) req.RemoteAddr = "203.0.113.20:4567" router.ServeHTTP(recorder, req) require.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, "203.0.113.20", resolver.seenIP.String()) var payload map[string]any require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &payload)) contact, ok := payload["contact"].(map[string]any) require.True(t, ok) assert.Equal(t, "河北保定客户", contact["name"]) } func TestWidgetHandler_InitIgnoresClientSuppliedIP(t *testing.T) { db, router, handler := setupWidgetHandlerTest(t) _, _ = seedWidgetHandlerData(t, db) require.NoError(t, router.SetTrustedProxies(nil)) resolver := &handlerGeoResolver{location: geoip.Location{Province: "北京市", City: "北京市"}} handler.widgetService.SetVisitorGeoResolver(resolver) recorder := httptest.NewRecorder() req, err := http.NewRequest(http.MethodPost, "/widget/init", strings.NewReader(`{"website_token":"handler_ws_token_123","client_ip":"198.51.100.99"}`)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Forwarded-For", "198.51.100.99") req.RemoteAddr = "203.0.113.21:4567" router.ServeHTTP(recorder, req) require.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, "203.0.113.21", resolver.seenIP.String()) var contact model.Contact require.NoError(t, db.First(&contact).Error) assert.Equal(t, "北京客户", contact.Name) } func TestWidgetHandler_ConfigHonorsConfiguredTrustedProxyIP(t *testing.T) { db, router, handler := setupWidgetHandlerTest(t) _, _ = seedWidgetHandlerData(t, db) require.NoError(t, router.SetTrustedProxies([]string{"203.0.113.0/24"})) resolver := &handlerGeoResolver{location: geoip.Location{Province: "北京市", City: "北京市"}} handler.widgetService.SetVisitorGeoResolver(resolver) recorder := httptest.NewRecorder() req, err := http.NewRequest(http.MethodPost, "/api/v1/widget/config?website_token=handler_ws_token_123", nil) require.NoError(t, err) req.Header.Set("X-Forwarded-For", "198.51.100.44") req.RemoteAddr = "203.0.113.21:4567" router.ServeHTTP(recorder, req) require.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, "198.51.100.44", resolver.seenIP.String()) } func TestWidgetContactFullPayloadDoesNotExposeVisitorMetadata(t *testing.T) { contact := &model.Contact{ Name: "河北保定客户", AdditionalAttributes: datatypes.JSON(`{"visitor_name_source":"ip_geolocation","visitor_province":"河北","visitor_city":"保定","customer_note":"keep"}`), } payload := widgetContactFullPayload(contact) attributes, ok := payload["additional_attributes"].(map[string]any) require.True(t, ok) assert.Equal(t, "keep", attributes["customer_note"]) assert.NotContains(t, attributes, "visitor_name_source") assert.NotContains(t, attributes, "visitor_province") assert.NotContains(t, attributes, "visitor_city") }