package user import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nixcn-cms/data" "nixcn-cms/internal/authtoken" "nixcn-cms/testutil" ) func init() { gin.SetMode(gin.TestMode) } func issueToken(t *testing.T, userId uuid.UUID) string { t.Helper() tok := &authtoken.Token{Application: viper.GetString("server.application")} access, _, err := tok.IssueTokens(context.Background(), testutil.TestClientID, userId) require.NoError(t, err) return access } func newUserRouter(t *testing.T) *gin.Engine { t.Helper() r := gin.New() ApiHandler(r.Group("/user")) return r } func getWithBearer(t *testing.T, r *gin.Engine, path, token string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodGet, path, nil) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() r.ServeHTTP(w, req) return w } func patchWithBearer(t *testing.T, r *gin.Engine, path, token string, body any) *httptest.ResponseRecorder { t.Helper() b, _ := json.Marshal(body) req := httptest.NewRequest(http.MethodPatch, path, bytes.NewBuffer(b)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() r.ServeHTTP(w, req) return w } func patchJSON(t *testing.T, r *gin.Engine, path string, body any) *httptest.ResponseRecorder { t.Helper() b, _ := json.Marshal(body) req := httptest.NewRequest(http.MethodPatch, path, bytes.NewBuffer(b)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) return w } func getRequest(t *testing.T, r *gin.Engine, path string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodGet, path, nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) return w } // ---- Info ---- func TestUserInfoHandlerSelf(t *testing.T) { testutil.SetupWithAuth(t) user := testutil.SeedUser(t, "selfinfo@example.com", 10) token := issueToken(t, user.UserId) r := newUserRouter(t) w := getWithBearer(t, r, "/user/info", token) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]any require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) data, ok := resp["data"].(map[string]any) require.True(t, ok) assert.Equal(t, "selfinfo@example.com", data["email"]) } func TestUserInfoHandlerOtherNotFound(t *testing.T) { testutil.SetupWithAuth(t) user := testutil.SeedUser(t, "caller@example.com", 10) token := issueToken(t, user.UserId) r := newUserRouter(t) w := getWithBearer(t, r, "/user/info/00000000-0000-0000-0000-000000000099", token) assert.Equal(t, http.StatusNotFound, w.Code) } // ---- Update ---- func TestUserUpdateHandlerEmptyNickname(t *testing.T) { testutil.SetupWithAuth(t) user := testutil.SeedUser(t, "update@example.com", 10) token := issueToken(t, user.UserId) r := newUserRouter(t) w := patchWithBearer(t, r, "/user/update", token, map[string]any{ "nickname": "", }) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestUserUpdateHandlerSuccess(t *testing.T) { testutil.SetupWithAuth(t) user := testutil.SeedUser(t, "update2@example.com", 10) token := issueToken(t, user.UserId) r := newUserRouter(t) w := patchWithBearer(t, r, "/user/update", token, map[string]any{ "nickname": "New Nickname", }) assert.Equal(t, http.StatusOK, w.Code) } // ---- List (admin) ---- func TestUserListHandlerRequiresOffset(t *testing.T) { testutil.SetupWithAuth(t) admin := testutil.SeedUser(t, "admin@example.com", 40) token := issueToken(t, admin.UserId) r := newUserRouter(t) w := getWithBearer(t, r, "/user/list", token) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestUserListHandlerSuccess(t *testing.T) { testutil.SetupWithAuth(t) admin := testutil.SeedUser(t, testutil.RandomEmail(), 40) for i := 0; i < 3; i++ { testutil.SeedUser(t, testutil.RandomEmail(), 10) } token := issueToken(t, admin.UserId) r := newUserRouter(t) w := getWithBearer(t, r, "/user/list?offset=0&limit=10", token) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]any require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["data"]) } func TestUserListHandlerLowPermission(t *testing.T) { testutil.SetupWithAuth(t) user := testutil.SeedUser(t, testutil.RandomEmail(), 10) token := issueToken(t, user.UserId) r := newUserRouter(t) w := getWithBearer(t, r, "/user/list?offset=0", token) assert.Equal(t, http.StatusForbidden, w.Code) } // ---- Other (info by user_id) ---- func TestUserInfoHandlerOtherSuccess(t *testing.T) { testutil.SetupWithAuth(t) caller := testutil.SeedUser(t, testutil.RandomEmail(), 10) target := testutil.SeedUser(t, testutil.RandomEmail(), 10) require.NoError(t, new(data.User).PatchByUserId(context.Background(), target.UserId, data.WithAllowPublic(true))) token := issueToken(t, caller.UserId) r := newUserRouter(t) w := getWithBearer(t, r, "/user/info/"+target.UserId.String(), token) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]any require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) d, ok := resp["data"].(map[string]any) require.True(t, ok) assert.Equal(t, target.Email, d["email"]) } // ---- AdminUpdate ---- func TestUserAdminUpdateHandlerSuccess(t *testing.T) { testutil.SetupWithAuth(t) admin := testutil.SeedUser(t, testutil.RandomEmail(), 40) target := testutil.SeedUser(t, testutil.RandomEmail(), 10) token := issueToken(t, admin.UserId) r := newUserRouter(t) w := patchWithBearer(t, r, "/user/update/"+target.UserId.String(), token, map[string]any{ "nickname": "Admin Set Nickname", }) assert.Equal(t, http.StatusOK, w.Code) } func TestUserAdminUpdateHandlerInvalidUserId(t *testing.T) { testutil.SetupWithAuth(t) admin := testutil.SeedUser(t, testutil.RandomEmail(), 40) token := issueToken(t, admin.UserId) r := newUserRouter(t) w := patchWithBearer(t, r, "/user/update/not-a-uuid", token, map[string]any{ "nickname": "Test", }) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestUserAdminUpdateHandlerLowPermission(t *testing.T) { testutil.SetupWithAuth(t) user := testutil.SeedUser(t, testutil.RandomEmail(), 10) target := testutil.SeedUser(t, testutil.RandomEmail(), 10) token := issueToken(t, user.UserId) r := newUserRouter(t) w := patchWithBearer(t, r, "/user/update/"+target.UserId.String(), token, map[string]any{ "nickname": "Hacked", }) assert.Equal(t, http.StatusForbidden, w.Code) }