mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
241 lines
7.8 KiB
Go
241 lines
7.8 KiB
Go
package api_test
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// The /api/v1/blocks routes are mounted by MountDMRoutes but were never
|
||
|
|
// exercised: handleListBlocks, handleBlockUser and handleUnblockUser had no
|
||
|
|
// test hitting them, so the whole blocking feature was untested from the REST
|
||
|
|
// edge down to the database. These tests reuse the DM harness
|
||
|
|
// (newDMTestDB / buildDMRouter / dmCreateToken) since the routes share a mount.
|
||
|
|
|
||
|
|
// dmPut issues a PUT against the DM/blocks router. The existing helpers cover
|
||
|
|
// POST, GET and DELETE only.
|
||
|
|
func dmPut(t *testing.T, router http.Handler, path, token string) *httptest.ResponseRecorder {
|
||
|
|
t.Helper()
|
||
|
|
req := httptest.NewRequest(http.MethodPut, path, nil)
|
||
|
|
if token != "" {
|
||
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
||
|
|
}
|
||
|
|
req.RemoteAddr = "127.0.0.1:9999"
|
||
|
|
rr := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(rr, req)
|
||
|
|
return rr
|
||
|
|
}
|
||
|
|
|
||
|
|
// decodeBlockedIDs pulls blocked_user_ids out of a GET /api/v1/blocks response.
|
||
|
|
func decodeBlockedIDs(t *testing.T, rr *httptest.ResponseRecorder) []int64 {
|
||
|
|
t.Helper()
|
||
|
|
var body struct {
|
||
|
|
BlockedUserIDs []int64 `json:"blocked_user_ids"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil {
|
||
|
|
t.Fatalf("decode blocked_user_ids from %q: %v", rr.Body.String(), err)
|
||
|
|
}
|
||
|
|
return body.BlockedUserIDs
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── PUT /api/v1/blocks/{userId} (handleBlockUser) ──────────────────────────
|
||
|
|
|
||
|
|
func TestBlockUser_Success(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
dmCreateToken(t, database, "bob", 4) // user id 2
|
||
|
|
|
||
|
|
rr := dmPut(t, router, "/api/v1/blocks/2", alice)
|
||
|
|
if rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("status = %d, want 200 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
blocked, err := database.IsBlocked(t.Context(), 1, 2)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("IsBlocked: %v", err)
|
||
|
|
}
|
||
|
|
if !blocked {
|
||
|
|
t.Error("block was not persisted")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBlockUser_SelfBlockRejected(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
|
||
|
|
rr := dmPut(t, router, "/api/v1/blocks/1", alice)
|
||
|
|
if rr.Code != http.StatusBadRequest {
|
||
|
|
t.Errorf("status = %d for a self-block, want 400 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBlockUser_UnknownTarget(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
|
||
|
|
rr := dmPut(t, router, "/api/v1/blocks/9999", alice)
|
||
|
|
if rr.Code != http.StatusNotFound {
|
||
|
|
t.Errorf("status = %d for an unknown target, want 404 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBlockUser_InvalidUserID(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
|
||
|
|
rr := dmPut(t, router, "/api/v1/blocks/not-a-number", alice)
|
||
|
|
if rr.Code != http.StatusBadRequest {
|
||
|
|
t.Errorf("status = %d for a non-numeric userId, want 400 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBlockUser_Unauthorized(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
dmCreateToken(t, database, "bob", 4)
|
||
|
|
|
||
|
|
rr := dmPut(t, router, "/api/v1/blocks/1", "")
|
||
|
|
if rr.Code != http.StatusUnauthorized {
|
||
|
|
t.Errorf("status = %d without a token, want 401 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── DELETE /api/v1/blocks/{userId} (handleUnblockUser) ─────────────────────
|
||
|
|
|
||
|
|
func TestUnblockUser_Success(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
dmCreateToken(t, database, "bob", 4)
|
||
|
|
|
||
|
|
if rr := dmPut(t, router, "/api/v1/blocks/2", alice); rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("setup block failed: status %d", rr.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
rr := dmDelete(t, router, "/api/v1/blocks/2", alice)
|
||
|
|
if rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("status = %d, want 200 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
blocked, err := database.IsBlocked(t.Context(), 1, 2)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("IsBlocked: %v", err)
|
||
|
|
}
|
||
|
|
if blocked {
|
||
|
|
t.Error("block survived the unblock request")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUnblockUser_NotBlockedStillSucceeds(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
dmCreateToken(t, database, "bob", 4)
|
||
|
|
|
||
|
|
// Unblocking someone who was never blocked is a no-op, not a 404.
|
||
|
|
rr := dmDelete(t, router, "/api/v1/blocks/2", alice)
|
||
|
|
if rr.Code != http.StatusOK {
|
||
|
|
t.Errorf("status = %d, want 200 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUnblockUser_InvalidUserID(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
|
||
|
|
rr := dmDelete(t, router, "/api/v1/blocks/abc", alice)
|
||
|
|
if rr.Code != http.StatusBadRequest {
|
||
|
|
t.Errorf("status = %d for a non-numeric userId, want 400 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUnblockUser_Unauthorized(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
|
||
|
|
rr := dmDelete(t, router, "/api/v1/blocks/2", "")
|
||
|
|
if rr.Code != http.StatusUnauthorized {
|
||
|
|
t.Errorf("status = %d without a token, want 401 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── GET /api/v1/blocks (handleListBlocks) ──────────────────────────────────
|
||
|
|
|
||
|
|
func TestListBlocks_EmptyArray(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
|
||
|
|
rr := dmGet(t, router, "/api/v1/blocks", alice)
|
||
|
|
if rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("status = %d, want 200 (body %q)", rr.Code, rr.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
// The service normalizes nil to an empty slice specifically so this
|
||
|
|
// serializes as [] rather than null.
|
||
|
|
if got := rr.Body.String(); !jsonContainsEmptyBlockList(got) {
|
||
|
|
t.Errorf("body = %q, want blocked_user_ids to be []", got)
|
||
|
|
}
|
||
|
|
if ids := decodeBlockedIDs(t, rr); len(ids) != 0 {
|
||
|
|
t.Errorf("blocked_user_ids = %v, want empty", ids)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestListBlocks_ReturnsBlockedIDs(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
alice := dmCreateToken(t, database, "alice", 4)
|
||
|
|
bob := dmCreateToken(t, database, "bob", 4)
|
||
|
|
dmCreateToken(t, database, "carol", 4) // user id 3
|
||
|
|
|
||
|
|
for _, target := range []string{"2", "3"} {
|
||
|
|
if rr := dmPut(t, router, "/api/v1/blocks/"+target, alice); rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("setup block of %s failed: status %d", target, rr.Code)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
rr := dmGet(t, router, "/api/v1/blocks", alice)
|
||
|
|
if rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("status = %d, want 200", rr.Code)
|
||
|
|
}
|
||
|
|
ids := decodeBlockedIDs(t, rr)
|
||
|
|
if len(ids) != 2 {
|
||
|
|
t.Fatalf("blocked_user_ids = %v, want 2 entries", ids)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Blocks are per-user: bob sees none of alice's.
|
||
|
|
rr = dmGet(t, router, "/api/v1/blocks", bob)
|
||
|
|
if rr.Code != http.StatusOK {
|
||
|
|
t.Fatalf("status = %d for bob, want 200", rr.Code)
|
||
|
|
}
|
||
|
|
if ids := decodeBlockedIDs(t, rr); len(ids) != 0 {
|
||
|
|
t.Errorf("bob's blocked_user_ids = %v, want empty", ids)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestListBlocks_Unauthorized(t *testing.T) {
|
||
|
|
database := newDMTestDB(t)
|
||
|
|
router := buildDMRouter(database, &mockBroadcaster{})
|
||
|
|
|
||
|
|
rr := dmGet(t, router, "/api/v1/blocks", "")
|
||
|
|
if rr.Code != http.StatusUnauthorized {
|
||
|
|
t.Errorf("status = %d without a token, want 401", rr.Code)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// jsonContainsEmptyBlockList reports whether the payload encodes an empty JSON
|
||
|
|
// array (not null) for blocked_user_ids.
|
||
|
|
func jsonContainsEmptyBlockList(body string) bool {
|
||
|
|
var raw map[string]json.RawMessage
|
||
|
|
if err := json.Unmarshal([]byte(body), &raw); err != nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
return string(raw["blocked_user_ids"]) == "[]"
|
||
|
|
}
|