- 单用户资料接口补上距离:此前只有推荐/附近列表会算距离,资料页和 聊天头部因此无内容可显示。沿用同一套 haversine 与隐私开关。 - 语音/图片消息可限定会员发送,两个开关在管理端「运营配置」中修改 (迁移 032)。校验放在 persistMessageContext,HTTP 与 WebSocket 两条发送路径都覆盖;文本消息永不受限。 - 短信服务关闭时注册不再要求验证码:关掉之后没人能拿到验证码,继续 要求就等于关闭注册通道。重置密码不做同样放宽,那里缺验证码等于 凭手机号夺号。app/config 增加 smsVerification 供客户端决定表单形态。 - 修复 AI 托管账号之间不回复:原规则按「发送方是否托管账号」拦截, 把真人操作测试号的正常对话也挡了。改为标记 worker 自己写入的回复, 只对 AI 生成的消息跳过入队。 - ai.default_model_id 同时接受模型 ID 与名称,填名称时不再被 MySQL 静默转成 0 而使配置失效。 - 聊天媒体留存管理与清理任务(迁移 033,两台线上均已应用)。 新增集成测试均针对真实 MySQL:会员限制、免短信注册、AI 入队规则、 默认模型解析、资料距离。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.2 KiB
Go
65 lines
2.2 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// The profile and chat headers label distance, so the single-profile endpoint
|
|
// has to answer with the same reading the nearby list gives.
|
|
func TestProfileDistanceMySQL(t *testing.T) {
|
|
db := isolatedIMDatabase(t)
|
|
a := &App{db: db, hub: NewHub(), config: Config{Environment: "development", JWTSecret: "isolated-im-regression-secret-only"}}
|
|
// Shenzhen city centre and a point a few kilometres north of it.
|
|
for _, seed := range []struct {
|
|
user int64
|
|
lat, lng float64
|
|
}{{1, 22.5431, 114.0579}, {2, 22.5731, 114.0579}} {
|
|
if _, err := db.Exec(`INSERT INTO user_location_states(user_id,city_code,latitude,longitude) VALUES(?,'440300',?,?)`, seed.user, seed.lat, seed.lng); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
profile := func(viewer, target int64) profileView {
|
|
t.Helper()
|
|
var item profileView
|
|
path := fmt.Sprintf("/api/v1/users/%d/profile", target)
|
|
if err := json.Unmarshal(imTestCall(t, a.userProfile, viewer, "GET", path, "", 200), &item); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return item
|
|
}
|
|
|
|
t.Run("another profile carries the distance between the two locations", func(t *testing.T) {
|
|
item := profile(1, 2)
|
|
if math.Abs(item.Distance-3.34) > 0.1 {
|
|
t.Fatalf("distance = %v km, want about 3.34", item.Distance)
|
|
}
|
|
if item.DistanceText != distanceText(item.Distance) {
|
|
t.Fatalf("distanceText = %q, want %q", item.DistanceText, distanceText(item.Distance))
|
|
}
|
|
})
|
|
|
|
t.Run("my own profile has no distance to label", func(t *testing.T) {
|
|
if item := profile(1, 1); item.DistanceText != "" {
|
|
t.Fatalf("distanceText = %q, want empty", item.DistanceText)
|
|
}
|
|
})
|
|
|
|
t.Run("a viewer without a location sees no distance", func(t *testing.T) {
|
|
if item := profile(3, 2); item.DistanceText != "" {
|
|
t.Fatalf("distanceText = %q, want empty", item.DistanceText)
|
|
}
|
|
})
|
|
|
|
t.Run("hiding distance hides it here too", func(t *testing.T) {
|
|
if _, err := db.Exec(`UPDATE user_privacy_settings SET distance_visible=0 WHERE user_id=2`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if item := profile(1, 2); item.DistanceText != "" || item.Distance != 0 {
|
|
t.Fatalf("hidden distance leaked: %v %q", item.Distance, item.DistanceText)
|
|
}
|
|
})
|
|
}
|