From 5987f2d87a186cae9459fcf3767972d78118beab Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Fri, 28 Aug 2026 13:05:26 +0800 Subject: [PATCH] fix: return not found for missing internal user --- server/internal_api.go | 14 ++++++++++ server/internal_api_test.go | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 server/internal_api_test.go diff --git a/server/internal_api.go b/server/internal_api.go index 4a6bf04..e617d94 100644 --- a/server/internal_api.go +++ b/server/internal_api.go @@ -12,6 +12,7 @@ import ( "io" "net/http" "net/url" + "runtime/debug" "strconv" "strings" "sync" @@ -72,6 +73,12 @@ func initInternalAPI(raw json.RawMessage) error { } func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix string) { + defer func() { + if recovered := recover(); recovered != nil { + logs.Err.Printf("internal admin query panic path:%s err:%v stack:%s", req.URL.RequestURI(), recovered, debug.Stack()) + writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil) + } + }() if req.Method != http.MethodGet { writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil) return @@ -246,14 +253,21 @@ func serveInternalUserQuery(wrt http.ResponseWriter, req *http.Request, tenantID if row, err := store.InternalAdminQueries.QueryOne(tenantID, query, []any{userDBID}); err == nil { writeIMSuccess(wrt, req, normalizeInternalAdminRowForAPI(row)) return + } else { + logs.Warn.Printf("internal user query sql failed tenant:%d user:%d err:%v", tenantID, userDBID, err) } } uid := store.EncodeUid(userDBID) user, err := store.Users.Get(tenantID, uid) if err != nil { + logs.Warn.Printf("internal user query fallback failed tenant:%d user:%d err:%v", tenantID, userDBID, err) writeInternalQueryError(wrt, req, err) return } + if user == nil { + writeInternalQueryError(wrt, req, types.ErrUserNotFound) + return + } writeIMSuccess(wrt, req, map[string]any{ "user_id": userDBID, "state": user.State, diff --git a/server/internal_api_test.go b/server/internal_api_test.go new file mode 100644 index 0000000..ec24c22 --- /dev/null +++ b/server/internal_api_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/mock/gomock" + "github.com/tinode/chat/server/store" + "github.com/tinode/chat/server/store/mock_store" + "github.com/tinode/chat/server/store/types" +) + +type internalAdminQueryNotFound struct{} + +func (internalAdminQueryNotFound) Query(types.TenantID, string, string, []any, int, int) (*types.InternalAdminQueryResult, error) { + return nil, types.ErrNotFound +} + +func (internalAdminQueryNotFound) QueryOne(types.TenantID, string, []any) (map[string]any, error) { + return nil, types.ErrNotFound +} + +func TestServeInternalUserQueryMissingUserReturnsNotFound(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + oldQueries := store.InternalAdminQueries + oldUsers := store.Users + defer func() { + store.InternalAdminQueries = oldQueries + store.Users = oldUsers + }() + + store.InternalAdminQueries = internalAdminQueryNotFound{} + users := mock_store.NewMockUsersPersistenceInterface(ctrl) + users.EXPECT().Get(types.TenantID(1), gomock.Any()).Return(nil, nil) + store.Users = users + + req := httptest.NewRequest(http.MethodGet, "/internal/admin-query/tenants/default/users/1566402774239809500", nil) + wrt := httptest.NewRecorder() + serveInternalUserQuery(wrt, req, types.TenantID(1), "1566402774239809500") + + if wrt.Code != http.StatusNotFound { + t.Fatalf("unexpected status: got %d, want %d; body=%s", wrt.Code, http.StatusNotFound, wrt.Body.String()) + } + var resp imHTTPResponse + if err := json.Unmarshal(wrt.Body.Bytes(), &resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if resp.Code != 30004 { + t.Fatalf("unexpected response code: got %d, want 30004", resp.Code) + } +}