From fe2bcb4b05c4009d0bd2f813126c0a9bc145db9e Mon Sep 17 00:00:00 2001 From: boboliu-1010 Date: Tue, 8 Sep 2026 17:17:06 +0800 Subject: [PATCH] fix(chat): fetch public keys per user --- api/chat.go | 24 ++++++++---------------- api/chat_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/api/chat.go b/api/chat.go index 5f882e2..9df4fde 100644 --- a/api/chat.go +++ b/api/chat.go @@ -91,30 +91,22 @@ func GetChatPublicKeys(client Client, userID string, opts RequestOptions) ([]Cha } // GetChatUsersPublicKeys fetches registered public keys for the given users; -// each returned row carries its owner's user_id. The endpoint accepts at -// most 100 ids per request, so larger inputs are fetched in batches. +// each returned row carries its owner's user_id. Public keys are fetched via +// the per-user route because the batch route is not enabled for every X app. func GetChatUsersPublicKeys(client Client, userIDs []string, opts RequestOptions) ([]ChatPublicKey, error) { if len(userIDs) == 0 { return nil, nil } var keys []ChatPublicKey - for start := 0; start < len(userIDs); start += 100 { - batch := userIDs[start:min(start+100, len(userIDs))] - opts.Method = "GET" - opts.Endpoint = "/2/users/public_keys?ids=" + url.QueryEscape(strings.Join(batch, ",")) - opts.Data = "" - - resp, err := client.SendRequest(opts) + for _, userID := range userIDs { + userKeys, err := GetChatPublicKeys(client, userID, opts) if err != nil { - return nil, err - } - var out struct { - Data []ChatPublicKey `json:"data"` + return nil, fmt.Errorf("failed to fetch public keys for user %s: %w", userID, err) } - if err := json.Unmarshal(resp, &out); err != nil { - return nil, fmt.Errorf("failed to parse public keys response: %w", err) + for i := range userKeys { + userKeys[i].UserID = userID } - keys = append(keys, out.Data...) + keys = append(keys, userKeys...) } return keys, nil } diff --git a/api/chat_test.go b/api/chat_test.go index 82266df..013911b 100644 --- a/api/chat_test.go +++ b/api/chat_test.go @@ -113,6 +113,52 @@ func TestGetChatPublicKeys(t *testing.T) { assert.Empty(t, requests[0].URL.RawQuery) } +func TestGetChatUsersPublicKeysUsesPerUserRoutes(t *testing.T) { + var paths []string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + paths = append(paths, r.URL.Path) + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/2/users/7/public_keys": + _, _ = w.Write([]byte(`{"data":[{"public_key_version":"1700","public_key":"idpk7","signing_public_key":"sigpk7","identity_public_key_signature":"binding7"}]}`)) + case "/2/users/8/public_keys": + _, _ = w.Write([]byte(`{"data":[{"public_key_version":"1800","public_key":"idpk8","signing_public_key":"sigpk8","identity_public_key_signature":"binding8"}]}`)) + default: + http.Error(w, `{"error":"unexpected route"}`, http.StatusNotFound) + } + })) + defer server.Close() + client := chatTestClient(t, server) + + keys, err := GetChatUsersPublicKeys(client, []string{"7", "8"}, RequestOptions{}) + require.NoError(t, err) + require.Len(t, keys, 2) + assert.Equal(t, []string{"/2/users/7/public_keys", "/2/users/8/public_keys"}, paths) + assert.Equal(t, "7", keys[0].UserID) + assert.Equal(t, "1700", keys[0].Version) + assert.Equal(t, "8", keys[1].UserID) + assert.Equal(t, "1800", keys[1].Version) +} + +func TestGetChatUsersPublicKeysIdentifiesFailedUserAndReturnsNoPartialKeys(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if r.URL.Path == "/2/users/7/public_keys" { + _, _ = w.Write([]byte(`{"data":[{"public_key_version":"1700","public_key":"idpk7","signing_public_key":"sigpk7","identity_public_key_signature":"binding7"}]}`)) + return + } + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte(`{"error":"denied"}`)) + })) + defer server.Close() + client := chatTestClient(t, server) + + keys, err := GetChatUsersPublicKeys(client, []string{"7", "8"}, RequestOptions{}) + require.Error(t, err) + assert.Nil(t, keys) + assert.Contains(t, err.Error(), "user 8") +} + func TestGetChatEvents(t *testing.T) { var requests []*http.Request var bodies []string