package imclient import ( "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "time" "git.118im.com/im_team/olons_server/internal/config" "github.com/gorilla/websocket" "golang.org/x/crypto/bcrypt" ) func TestCreateUserAcceptsNumericIDAndSignsRequest(t *testing.T) { const secret = "internal-secret" server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) if r.URL.Path != "/internal/admin-query/tenants/allrich/users" { t.Fatalf("unexpected path %s", r.URL.Path) } if r.Header.Get("X-Service-Name") != "olons-server" { t.Fatalf("missing service header") } canonical := strings.Join([]string{r.Method, r.URL.RequestURI(), hashHex(body), r.Header.Get("X-Timestamp"), r.Header.Get("X-Nonce")}, "\n") mac := hmac.New(sha256.New, []byte(secret)) _, _ = mac.Write([]byte(canonical)) if r.Header.Get("X-Signature") != hex.EncodeToString(mac.Sum(nil)) { t.Fatalf("invalid signature") } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"code":0,"data":{"user_id":42,"username":"allrich_m_7","nickname":"会员7"}}`)) })) defer server.Close() client := New(config.IMConfig{BaseURL: server.URL, HTTPAPIURL: server.URL, TenantCode: "allrich", APIKey: "public-key", InternalService: "olons-server", InternalSecret: secret, Timeout: time.Second}) created, err := client.CreateUser(context.Background(), "allrich_m_7", "会员7", "password") if err != nil { t.Fatal(err) } if got := string(created.UserID); got != "42" { t.Fatalf("user id = %q, want 42", got) } } func TestProductRequestHeaders(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "Bearer user-token" || r.Header.Get("X-MeetingIM-APIKey") != "public-key" { t.Fatalf("missing product API credentials") } if r.Header.Get("X-IM-Schema") != "1" { t.Fatalf("missing schema header") } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"code":0,"data":{"user_id":"usr_agent","relationship":"friend","topic":"p2p123","capabilities":{"can_send":true}}}`)) })) defer server.Close() client := New(config.IMConfig{BaseURL: server.URL, HTTPAPIURL: server.URL, TenantCode: "allrich", APIKey: "public-key", InternalService: "olons-server", InternalSecret: "secret", Timeout: time.Second}) profile, err := client.UserProfile(context.Background(), "user-token", "usr_agent") if err != nil { t.Fatal(err) } if profile.Relationship != "friend" || !profile.Capabilities.CanSend { t.Fatalf("unexpected profile: %+v", profile) } } func TestLoginBasicUsesExistingWebSocketProtocol(t *testing.T) { upgrader := websocket.Upgrader{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("apikey") != "public-key" { t.Fatalf("missing API key") } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { t.Fatal(err) } defer conn.Close() var packet map[string]map[string]any if err = conn.ReadJSON(&packet); err != nil { t.Fatal(err) } if packet["hi"]["tenant"] != "allrich" { t.Fatalf("unexpected hi: %#v", packet) } _ = conn.WriteJSON(map[string]any{"ctrl": map[string]any{"id": packet["hi"]["id"], "code": 201, "text": "created"}}) if err = conn.ReadJSON(&packet); err != nil { t.Fatal(err) } login := packet["login"] secret, _ := base64.StdEncoding.DecodeString(login["secret"].(string)) if string(secret) != "member01:password" || login["login_type"] != float64(0) || login["tenant_code"] != "allrich" { t.Fatalf("unexpected login: %#v", login) } _ = conn.WriteJSON(map[string]any{"ctrl": map[string]any{"id": login["id"], "code": 200, "text": "ok", "params": map[string]any{"user": "usr_member", "token": "token", "expires": "2026-10-27T10:00:00Z"}}}) })) defer server.Close() client := New(config.IMConfig{BaseURL: server.URL, HTTPAPIURL: server.URL, WebSocketURL: "ws" + strings.TrimPrefix(server.URL, "http"), TenantCode: "allrich", APIKey: "public-key", InternalService: "olons-server", InternalSecret: "secret", Timeout: time.Second}) session, err := client.LoginBasic(context.Background(), "member01", "password", "web-1", "web") if err != nil { t.Fatal(err) } if session.UserID != "usr_member" || session.Token != "token" { t.Fatalf("unexpected session: %+v", session) } } func TestLoginBasicUsesPersonalTypeForDefaultTenant(t *testing.T) { upgrader := websocket.Upgrader{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { t.Fatal(err) } defer conn.Close() var packet map[string]map[string]any if err = conn.ReadJSON(&packet); err != nil { t.Fatal(err) } _ = conn.WriteJSON(map[string]any{"ctrl": map[string]any{"id": packet["hi"]["id"], "code": 201, "text": "created"}}) if err = conn.ReadJSON(&packet); err != nil { t.Fatal(err) } login := packet["login"] if login["login_type"] != float64(1) || login["tenant_code"] != defaultIMTenantCode { t.Fatalf("unexpected login: %#v", login) } _ = conn.WriteJSON(map[string]any{"ctrl": map[string]any{"id": login["id"], "code": 200, "text": "ok", "params": map[string]any{"user": "usr_member", "token": "token", "expires": "2026-10-27T10:00:00Z"}}}) })) defer server.Close() client := New(config.IMConfig{BaseURL: server.URL, HTTPAPIURL: server.URL, WebSocketURL: "ws" + strings.TrimPrefix(server.URL, "http"), TenantCode: defaultIMTenantCode, APIKey: "public-key", InternalService: "olons-server", InternalSecret: "secret", Timeout: time.Second}) if _, err := client.LoginBasic(context.Background(), "member01", "password", "web-1", "web"); err != nil { t.Fatal(err) } } func TestResetPasswordSendsBase64BcryptHash(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body struct { EventType string `json:"event_type"` PasswordHash string `json:"password_hash"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatal(err) } hash, err := base64.StdEncoding.DecodeString(body.PasswordHash) if body.EventType != "user.password.reset" || err != nil || bcrypt.CompareHashAndPassword(hash, []byte("new-password")) != nil { t.Fatalf("unexpected reset body: %+v", body) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"code":0,"data":{"status":"succeeded"}}`)) })) defer server.Close() client := New(config.IMConfig{BaseURL: server.URL, HTTPAPIURL: server.URL, WebSocketURL: "ws://example.test", TenantCode: "allrich", APIKey: "public-key", InternalService: "olons-server", InternalSecret: "secret", Timeout: time.Second}) if err := client.ResetPassword(context.Background(), "usr_member", "new-password"); err != nil { t.Fatal(err) } } func hashHex(body []byte) string { digest := sha256.Sum256(body) return hex.EncodeToString(digest[:]) }