Files
rogee 4d684a71eb
Build and publish Docker images / Build and publish images (push) Successful in 2m10s
feat: geolocate anonymous widget visitors
2026-09-15 08:54:13 +08:00

102 lines
3.8 KiB
Go

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")
}