package http import ( "net/http/httptest" "strings" "testing" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) // 设备资料头是 App 全局注入的,两个 meta builder(httpkit 通用版与 auth 专用版)都必须透传, // 否则登录链路与业务链路的设备档案会出现来源分叉。 func TestRequestMetaBuildersCarryDeviceProfileHeaders(t *testing.T) { request := httptest.NewRequest("POST", "/api/v1/auth/login", nil) request.Header.Set("X-Device-Model", "Redmi Note 8 Pro") request.Header.Set("X-Device-Manufacturer", "Xiaomi") request.Header.Set("X-OS-Version", "13") request.Header.Set("X-Device-IMEI", "123456789012345") authMeta := authRequestMetaWithLoginContext(request, "dev-1", "android", "en", "Asia/Riyadh", "", "") if authMeta.GetDeviceModel() != "Redmi Note 8 Pro" || authMeta.GetDeviceManufacturer() != "Xiaomi" || authMeta.GetOsVersion() != "13" || authMeta.GetImei() != "123456789012345" { t.Fatalf("auth meta must carry device profile headers: %+v", authMeta) } userMeta := httpkit.UserMeta(request, "dev-1") if userMeta.GetDeviceModel() != "Redmi Note 8 Pro" || userMeta.GetDeviceManufacturer() != "Xiaomi" || userMeta.GetOsVersion() != "13" || userMeta.GetImei() != "123456789012345" { t.Fatalf("httpkit user meta must carry device profile headers: %+v", userMeta) } // 头缺失时保持空串:user-service 端“非空覆盖”依赖空值不参与合并。 bare := httptest.NewRequest("POST", "/api/v1/auth/refresh", nil) bareMeta := authRequestMetaWithLoginContext(bare, "dev-1", "", "", "", "", "") if bareMeta.GetDeviceModel() != "" || bareMeta.GetImei() != "" { t.Fatalf("missing headers must map to empty meta fields: %+v", bareMeta) } } // body device/os_version 属于旧协议;标准客户端只发设备头,回退逻辑保证 register 快照不再为空, // 同时 body 显式值仍优先,旧客户端行为不变。 func TestDeviceFieldsWithHeaderFallback(t *testing.T) { request := httptest.NewRequest("POST", "/api/v1/auth/register", nil) request.Header.Set("X-Device-Model", "iPhone17,2") request.Header.Set("X-OS-Version", "18.1") meta := authRequestMetaWithLoginContext(request, "dev-2", "ios", "", "", "", "") device, osVersion := deviceFieldsWithHeaderFallback("", " ", meta) if device != "iPhone17,2" || osVersion != "18.1" { t.Fatalf("empty body fields must fall back to headers: device=%q os=%q", device, osVersion) } device, osVersion = deviceFieldsWithHeaderFallback("Pixel 8", "14", meta) if device != "Pixel 8" || osVersion != "14" { t.Fatalf("body fields must win over headers: device=%q os=%q", device, osVersion) } } // H5 与原生共用设备上报协议,新设备头必须落进默认 CORS 允许集,否则浏览器 preflight 直接拦掉。 func TestCORSDefaultHeadersAllowDeviceProfile(t *testing.T) { config := normalizeCORSConfig(CORSConfig{}) allowedHeaders := "," + strings.ToLower(strings.Join(config.AllowedHeaders, ",")) + "," for _, header := range []string{"X-Device-Model", "X-Device-Manufacturer", "X-OS-Version", "X-Device-IMEI"} { if !strings.Contains(allowedHeaders, ","+strings.ToLower(header)+",") { t.Fatalf("default CORS headers must allow device profile header %q: %+v", header, config.AllowedHeaders) } } }