156 lines
5.9 KiB
Go
156 lines
5.9 KiB
Go
package externaladmin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
adminmiddleware "hyapp-admin-server/internal/middleware"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"google.golang.org/grpc"
|
|
userv1 "hyapp.local/api/proto/user/v1"
|
|
)
|
|
|
|
type managerTeamHostClient struct {
|
|
userv1.UserHostServiceClient
|
|
request *userv1.ListManagerTeamAgenciesRequest
|
|
calls int
|
|
}
|
|
|
|
func (client *managerTeamHostClient) ListManagerTeamAgencies(_ context.Context, request *userv1.ListManagerTeamAgenciesRequest, _ ...grpc.CallOption) (*userv1.ListManagerTeamAgenciesResponse, error) {
|
|
client.calls++
|
|
client.request = request
|
|
return &userv1.ListManagerTeamAgenciesResponse{
|
|
Agencies: []*userv1.ManagerTeamAgency{
|
|
{AgencyId: 101, OwnerUserId: 1001, ParentBdUserId: 2001},
|
|
{AgencyId: 102, OwnerUserId: 1002, ParentBdUserId: 2002},
|
|
{AgencyId: 103, OwnerUserId: 1003, ParentBdUserId: 2003},
|
|
},
|
|
TotalBdLeaders: 4,
|
|
TotalBds: 9,
|
|
Truncated: true,
|
|
}, nil
|
|
}
|
|
|
|
func TestMyTeamRouteUsesOnlySessionLinkedUserAndPaginates(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
client := &managerTeamHostClient{}
|
|
handler := New(nil, nil, Config{}, nil, WithUserHostClient(client))
|
|
engine := gin.New()
|
|
group := engine.Group("/api/v1/external")
|
|
group.Use(func(c *gin.Context) {
|
|
// This fixture models fields populated by AuthRequired. Query parameters below
|
|
// intentionally carry attacker-selected IDs and must never override the principal.
|
|
c.Set(contextPrincipal, SessionPrincipal{AppCode: "fami", LinkedAppUserID: 42})
|
|
c.Set(adminmiddleware.ContextPermissions, []string{"bd:view"})
|
|
c.Next()
|
|
})
|
|
registerBusinessWhitelist(group, handler, BusinessHandlers{})
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/v1/external/admin/my-team/agencies?page=2&page_size=2&manager_user_id=999&user_id=888", nil)
|
|
responseRecorder := httptest.NewRecorder()
|
|
engine.ServeHTTP(responseRecorder, request)
|
|
if responseRecorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", responseRecorder.Code, responseRecorder.Body.String())
|
|
}
|
|
if client.calls != 1 || client.request == nil {
|
|
t.Fatalf("owner service calls = %d request=%v", client.calls, client.request)
|
|
}
|
|
if client.request.GetManagerUserId() != 42 {
|
|
t.Fatalf("manager user id = %d, want session linked user 42", client.request.GetManagerUserId())
|
|
}
|
|
if client.request.GetMeta().GetAppCode() != "fami" || client.request.GetPageSize() != myTeamAgencyRPCPageSize {
|
|
t.Fatalf("owner request is not session scoped: %+v", client.request)
|
|
}
|
|
|
|
var body struct {
|
|
Data MyTeamAgencyPage `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(responseRecorder.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if body.Data.Page != 2 || body.Data.PageSize != 2 || body.Data.Total != 3 || len(body.Data.Items) != 1 {
|
|
t.Fatalf("unexpected page: %+v", body.Data)
|
|
}
|
|
item := body.Data.Items[0]
|
|
if item.AgencyID != 103 || item.OwnerUserID != 1003 || item.ParentBDUserID != 2003 || item.Status != "active" {
|
|
t.Fatalf("unexpected agency projection: %+v", item)
|
|
}
|
|
if body.Data.TotalBDLeaders != 4 || body.Data.TotalBDs != 9 || !body.Data.Truncated {
|
|
t.Fatalf("owner totals not preserved: %+v", body.Data)
|
|
}
|
|
if got := responseRecorder.Body.String(); !containsAll(got, `"agencyId":"103"`, `"ownerUserId":"1003"`, `"parentBdUserId":"2003"`) {
|
|
t.Fatalf("int64 ids must be JSON strings: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestMyTeamRouteRequiresBDViewPermissionBeforeOwnerCall(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
client := &managerTeamHostClient{}
|
|
handler := New(nil, nil, Config{}, nil, WithUserHostClient(client))
|
|
engine := gin.New()
|
|
group := engine.Group("/api/v1/external")
|
|
group.Use(func(c *gin.Context) {
|
|
c.Set(contextPrincipal, SessionPrincipal{AppCode: "fami", LinkedAppUserID: 42})
|
|
c.Set(adminmiddleware.ContextPermissions, []string{"agency:view"})
|
|
c.Next()
|
|
})
|
|
registerBusinessWhitelist(group, handler, BusinessHandlers{})
|
|
|
|
responseRecorder := httptest.NewRecorder()
|
|
engine.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodGet, "/api/v1/external/admin/my-team/agencies", nil))
|
|
if responseRecorder.Code != http.StatusForbidden {
|
|
t.Fatalf("status = %d body=%s", responseRecorder.Code, responseRecorder.Body.String())
|
|
}
|
|
if client.calls != 0 {
|
|
t.Fatalf("owner service called %d times without bd:view", client.calls)
|
|
}
|
|
}
|
|
|
|
func TestMyTeamRouteFiltersScopedIDProjectionBeforeTotalAndPagination(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
client := &managerTeamHostClient{}
|
|
handler := New(nil, nil, Config{}, nil, WithUserHostClient(client))
|
|
engine := gin.New()
|
|
group := engine.Group("/api/v1/external")
|
|
group.Use(func(c *gin.Context) {
|
|
c.Set(contextPrincipal, SessionPrincipal{AppCode: "fami", LinkedAppUserID: 42})
|
|
c.Set(adminmiddleware.ContextPermissions, []string{"bd:view"})
|
|
c.Next()
|
|
})
|
|
registerBusinessWhitelist(group, handler, BusinessHandlers{})
|
|
|
|
// "002" matches owner 1002 and parent BD 2002. Filtering happens only over
|
|
// the already session-scoped RPC projection and must precede total/pagination.
|
|
responseRecorder := httptest.NewRecorder()
|
|
engine.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodGet, "/api/v1/external/admin/my-team/agencies?keyword=002&page=1&page_size=1", nil))
|
|
if responseRecorder.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", responseRecorder.Code, responseRecorder.Body.String())
|
|
}
|
|
var body struct {
|
|
Data MyTeamAgencyPage `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(responseRecorder.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if body.Data.Total != 1 || len(body.Data.Items) != 1 || body.Data.Items[0].AgencyID != 102 {
|
|
t.Fatalf("keyword filter/page = %+v", body.Data)
|
|
}
|
|
if client.request.GetManagerUserId() != 42 {
|
|
t.Fatalf("keyword must not change team scope: manager=%d", client.request.GetManagerUserId())
|
|
}
|
|
}
|
|
|
|
func containsAll(value string, targets ...string) bool {
|
|
for _, target := range targets {
|
|
if !strings.Contains(value, target) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|