package httpx import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "go.uber.org/zap" "spark-server/internal/infrastructure/id" authentity "spark-server/internal/module/auth/entity" authlogic "spark-server/internal/module/auth/logic" authlogictest "spark-server/internal/module/auth/logictest" convlogic "spark-server/internal/module/conversation/logic" convmodel "spark-server/internal/module/conversation/model" delivermodel "spark-server/internal/module/deliver/model" friendentity "spark-server/internal/module/friend/entity" friendlogic "spark-server/internal/module/friend/logic" friendmodel "spark-server/internal/module/friend/model" msgmodel "spark-server/internal/module/message/model" "spark-server/internal/module/user/logictest" "spark-server/internal/pkg/reply" ) func testFriendEngine(t *testing.T) *ginEngineBundle { t.Helper() j, err := authlogic.NewJWT(&authlogic.Options{Secret: "test-secret-test-secret", Issuer: "spark-im"}) if err != nil { t.Fatal(err) } ids, err := id.New(1) if err != nil { t.Fatal(err) } users, _ := logictest.UsersAndSMS(ids, nil) chats := convlogic.NewP2PService(convmodel.NewMemoryP2P(), ids) friends := friendlogic.NewService( friendmodel.NewMemoryRepo(), users, ids, friendlogic.NewGreeter(chats, msgmodel.NewMemoryRepo(), ids, delivermodel.NewMemoryQueue()), ) r := NewEngine(zap.NewNop(), &EngineDeps{ Auth: j, Login: authlogictest.LoginService(j, users), Users: users, Friends: friends, }) return &ginEngineBundle{engine: r} } type ginEngineBundle struct { engine http.Handler } func loginUser(t *testing.T, h http.Handler, email string, _ uint64) authentity.IssuedToken { t.Helper() body := authlogictest.LoginBodyJSONNamed(email, "any", "phone") req := httptest.NewRequest(http.MethodPost, "/v1/auth/login", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() h.ServeHTTP(w, req) var env reply.Envelope if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil { t.Fatal(err) } if env.Code != "0" { t.Fatalf("login %s %+v %s", email, env, w.Body.String()) } var tok authentity.IssuedToken if err := json.Unmarshal(env.Data, &tok); err != nil { t.Fatal(err) } return tok } func authJSON(t *testing.T, h http.Handler, tok *authentity.IssuedToken, method, path, body string) reply.Envelope { t.Helper() var rdr *strings.Reader if body != "" { rdr = strings.NewReader(body) } else { rdr = strings.NewReader("") } req := httptest.NewRequest(method, path, rdr) req.Header.Set("Authorization", "Bearer "+tok.AccessToken) if body != "" { req.Header.Set("Content-Type", "application/json") } w := httptest.NewRecorder() h.ServeHTTP(w, req) var env reply.Envelope if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil { t.Fatal(err) } return env } func TestFriendApplyAcceptHTTP(t *testing.T) { bundle := testFriendEngine(t) h := bundle.engine a := loginUser(t, h, "test01@spark.com", 1) b := loginUser(t, h, "test02@spark.com", 2) apply := authJSON(t, h, &a, http.MethodPost, "/v1/friends/requests", `{"email":"test02@spark.com","reason":"hi","remark":"work"}`) if apply.Code != "0" { t.Fatalf("apply %+v", apply) } var applied friendlogic.ApplyResult if err := json.Unmarshal(apply.Data, &applied); err != nil || applied.Status != friendentity.RequestPending || applied.RequestID == 0 { t.Fatalf("apply data %+v %v", applied, err) } again := authJSON(t, h, &a, http.MethodPost, "/v1/friends/requests", `{"email":"test02@spark.com","reason":"again"}`) var applied2 friendlogic.ApplyResult _ = json.Unmarshal(again.Data, &applied2) if applied2.RequestID != applied.RequestID { t.Fatalf("dedupe %d %d", applied.RequestID, applied2.RequestID) } inbox := authJSON(t, h, &b, http.MethodGet, "/v1/friends/requests", "") if inbox.Code != "0" || !strings.Contains(string(inbox.Data), `"reason":"again"`) { t.Fatalf("inbox %+v", inbox) } acc := authJSON(t, h, &b, http.MethodPost, "/v1/friends", fmt.Sprintf(`{"request_id":%d,"remark":"ok"}`, applied.RequestID)) if acc.Code != "0" { t.Fatalf("accept %+v", acc) } got := authJSON(t, h, &a, http.MethodGet, fmt.Sprintf("/v1/friends/%d", b.UserID), "") if got.Code != "0" || !strings.Contains(string(got.Data), `"remark":"work"`) { t.Fatalf("get %+v", got) } look := authJSON(t, h, &a, http.MethodGet, "/v1/friends/lookup?q=test02@spark.com", "") if look.Code != "0" || !strings.Contains(string(look.Data), fmt.Sprintf(`"uid":%d`, b.UserID)) { t.Fatalf("lookup %+v", look) } list := authJSON(t, h, &b, http.MethodGet, "/v1/friends", "") if list.Code != "0" || !strings.Contains(string(list.Data), fmt.Sprintf(`"uid":%d`, a.UserID)) { t.Fatalf("list %+v", list) } pin := authJSON(t, h, &a, http.MethodPut, fmt.Sprintf("/v1/friends/%d/pin", b.UserID), "") if pin.Code != "0" { t.Fatalf("pin %+v", pin) } got = authJSON(t, h, &a, http.MethodGet, fmt.Sprintf("/v1/friends/%d", b.UserID), "") if got.Code != "0" || !strings.Contains(string(got.Data), `"pinned":true`) { t.Fatalf("pinned get %+v", got) } block := authJSON(t, h, &a, http.MethodPut, fmt.Sprintf("/v1/friends/%d/block", b.UserID), "") if block.Code != "0" { t.Fatalf("block %+v", block) } got = authJSON(t, h, &a, http.MethodGet, fmt.Sprintf("/v1/friends/%d", b.UserID), "") if got.Code != "0" || !strings.Contains(string(got.Data), `"blocked":true`) { t.Fatalf("blocked get %+v", got) } }