mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-30 20:13:33 +01:00
368 lines
13 KiB
Go
368 lines
13 KiB
Go
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/netbirdio/netbird/management/server/http/api"
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
"github.com/netbirdio/netbird/route"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/netip"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/magiconair/properties/assert"
|
|
"github.com/netbirdio/netbird/management/server"
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
|
)
|
|
|
|
const (
|
|
existingRouteID = "existingRouteID"
|
|
notFoundRouteID = "notFoundRouteID"
|
|
existingPeerID = "100.64.0.100"
|
|
notFoundPeerID = "100.64.0.200"
|
|
existingPeerKey = "existingPeerKey"
|
|
testAccountID = "test_id"
|
|
)
|
|
|
|
var baseExistingRoute = &route.Route{
|
|
ID: existingRouteID,
|
|
Description: "base route",
|
|
NetID: "awesomeNet",
|
|
Network: netip.MustParsePrefix("192.168.0.0/24"),
|
|
NetworkType: route.IPv4Network,
|
|
Metric: 9999,
|
|
Masquerade: false,
|
|
Enabled: true,
|
|
}
|
|
|
|
var testingAccount = &server.Account{
|
|
Id: testAccountID,
|
|
Domain: "hotmail.com",
|
|
Peers: map[string]*server.Peer{
|
|
existingPeerKey: {
|
|
Key: existingPeerID,
|
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
|
},
|
|
},
|
|
Users: map[string]*server.User{
|
|
"test_user": server.NewAdminUser("test_user"),
|
|
},
|
|
}
|
|
|
|
func initRoutesTestData() *Routes {
|
|
return &Routes{
|
|
accountManager: &mock_server.MockAccountManager{
|
|
GetRouteFunc: func(_, routeID, _ string) (*route.Route, error) {
|
|
if routeID == existingRouteID {
|
|
return baseExistingRoute, nil
|
|
}
|
|
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
|
},
|
|
CreateRouteFunc: func(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
|
networkType, p, _ := route.ParseNetwork(network)
|
|
return &route.Route{
|
|
ID: existingRouteID,
|
|
NetID: netID,
|
|
Peer: peer,
|
|
Network: p,
|
|
NetworkType: networkType,
|
|
Description: description,
|
|
Masquerade: masquerade,
|
|
Enabled: enabled,
|
|
}, nil
|
|
},
|
|
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
|
return nil
|
|
},
|
|
DeleteRouteFunc: func(_ string, peerIP string) error {
|
|
if peerIP != existingRouteID {
|
|
return status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
|
}
|
|
return nil
|
|
},
|
|
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
|
if peerIP != existingPeerID {
|
|
return nil, status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
|
}
|
|
return &server.Peer{
|
|
Key: existingPeerKey,
|
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
|
}, nil
|
|
},
|
|
UpdateRouteFunc: func(_ string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
|
routeToUpdate := baseExistingRoute
|
|
if routeID != routeToUpdate.ID {
|
|
return nil, status.Errorf(status.NotFound, "route %s no longer exists", routeID)
|
|
}
|
|
for _, operation := range operations {
|
|
switch operation.Type {
|
|
case server.UpdateRouteNetwork:
|
|
routeToUpdate.NetworkType, routeToUpdate.Network, _ = route.ParseNetwork(operation.Values[0])
|
|
case server.UpdateRouteDescription:
|
|
routeToUpdate.Description = operation.Values[0]
|
|
case server.UpdateRouteNetworkIdentifier:
|
|
routeToUpdate.NetID = operation.Values[0]
|
|
case server.UpdateRoutePeer:
|
|
routeToUpdate.Peer = operation.Values[0]
|
|
case server.UpdateRouteMetric:
|
|
routeToUpdate.Metric, _ = strconv.Atoi(operation.Values[0])
|
|
case server.UpdateRouteMasquerade:
|
|
routeToUpdate.Masquerade, _ = strconv.ParseBool(operation.Values[0])
|
|
case server.UpdateRouteEnabled:
|
|
routeToUpdate.Enabled, _ = strconv.ParseBool(operation.Values[0])
|
|
default:
|
|
return nil, fmt.Errorf("no operation")
|
|
}
|
|
}
|
|
return routeToUpdate, nil
|
|
},
|
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
|
return testingAccount, testingAccount.Users["test_user"], nil
|
|
},
|
|
},
|
|
authAudience: "",
|
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
|
return jwtclaims.AuthorizationClaims{
|
|
UserId: "test_user",
|
|
Domain: "hotmail.com",
|
|
AccountId: testAccountID,
|
|
}
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestRoutesHandlers(t *testing.T) {
|
|
tt := []struct {
|
|
name string
|
|
expectedStatus int
|
|
expectedBody bool
|
|
expectedRoute *api.Route
|
|
requestType string
|
|
requestPath string
|
|
requestBody io.Reader
|
|
}{
|
|
{
|
|
name: "Get Existing Route",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
expectedStatus: http.StatusOK,
|
|
expectedBody: true,
|
|
expectedRoute: toRouteResponse(testingAccount, baseExistingRoute),
|
|
},
|
|
{
|
|
name: "Get Not Existing Route",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
|
expectedStatus: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "Delete Existing Route",
|
|
requestType: http.MethodDelete,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
expectedStatus: http.StatusOK,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "Delete Not Existing Route",
|
|
requestType: http.MethodDelete,
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
|
expectedStatus: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "POST OK",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/routes",
|
|
requestBody: bytes.NewBuffer(
|
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID))),
|
|
expectedStatus: http.StatusOK,
|
|
expectedBody: true,
|
|
expectedRoute: &api.Route{
|
|
Id: existingRouteID,
|
|
Description: "Post",
|
|
NetworkId: "awesomeNet",
|
|
Network: "192.168.0.0/16",
|
|
Peer: existingPeerID,
|
|
NetworkType: route.IPv4NetworkString,
|
|
Masquerade: false,
|
|
Enabled: false,
|
|
},
|
|
},
|
|
{
|
|
name: "POST Not Found Peer",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/routes",
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "POST Invalid Network Identifier",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/routes",
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "POST Invalid Network",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/routes",
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "PUT OK",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
|
expectedStatus: http.StatusOK,
|
|
expectedBody: true,
|
|
expectedRoute: &api.Route{
|
|
Id: existingRouteID,
|
|
Description: "Post",
|
|
NetworkId: "awesomeNet",
|
|
Network: "192.168.0.0/16",
|
|
Peer: existingPeerID,
|
|
NetworkType: route.IPv4NetworkString,
|
|
Masquerade: false,
|
|
Enabled: false,
|
|
},
|
|
},
|
|
{
|
|
name: "PUT Not Found Route",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "PUT Not Found Peer",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "PUT Invalid Network Identifier",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "PUT Invalid Network",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "PATCH Description OK",
|
|
requestType: http.MethodPatch,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
|
expectedStatus: http.StatusOK,
|
|
expectedBody: true,
|
|
expectedRoute: &api.Route{
|
|
Id: existingRouteID,
|
|
Description: "NewDesc",
|
|
NetworkId: "awesomeNet",
|
|
Network: baseExistingRoute.Network.String(),
|
|
NetworkType: route.IPv4NetworkString,
|
|
Masquerade: baseExistingRoute.Masquerade,
|
|
Enabled: baseExistingRoute.Enabled,
|
|
Metric: baseExistingRoute.Metric,
|
|
},
|
|
},
|
|
{
|
|
name: "PATCH Peer OK",
|
|
requestType: http.MethodPatch,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", existingPeerID)),
|
|
expectedStatus: http.StatusOK,
|
|
expectedBody: true,
|
|
expectedRoute: &api.Route{
|
|
Id: existingRouteID,
|
|
Description: "NewDesc",
|
|
NetworkId: "awesomeNet",
|
|
Network: baseExistingRoute.Network.String(),
|
|
NetworkType: route.IPv4NetworkString,
|
|
Peer: existingPeerID,
|
|
Masquerade: baseExistingRoute.Masquerade,
|
|
Enabled: baseExistingRoute.Enabled,
|
|
Metric: baseExistingRoute.Metric,
|
|
},
|
|
},
|
|
{
|
|
name: "PATCH Not Found Peer",
|
|
requestType: http.MethodPatch,
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", notFoundPeerID)),
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedBody: false,
|
|
},
|
|
{
|
|
name: "PATCH Not Found Route",
|
|
requestType: http.MethodPatch,
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"network\",\"value\":[\"192.168.0.0/34\"]}]"),
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedBody: false,
|
|
},
|
|
}
|
|
|
|
p := initRoutesTestData()
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
recorder := httptest.NewRecorder()
|
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
|
|
|
router := mux.NewRouter()
|
|
router.HandleFunc("/api/routes/{id}", p.GetRouteHandler).Methods("GET")
|
|
router.HandleFunc("/api/routes/{id}", p.DeleteRouteHandler).Methods("DELETE")
|
|
router.HandleFunc("/api/routes", p.CreateRouteHandler).Methods("POST")
|
|
router.HandleFunc("/api/routes/{id}", p.UpdateRouteHandler).Methods("PUT")
|
|
router.HandleFunc("/api/routes/{id}", p.PatchRouteHandler).Methods("PATCH")
|
|
router.ServeHTTP(recorder, req)
|
|
|
|
res := recorder.Result()
|
|
defer res.Body.Close()
|
|
|
|
content, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Fatalf("I don't know what I expected; %v", err)
|
|
}
|
|
|
|
if status := recorder.Code; status != tc.expectedStatus {
|
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
|
status, tc.expectedStatus, string(content))
|
|
return
|
|
}
|
|
|
|
if !tc.expectedBody {
|
|
return
|
|
}
|
|
|
|
got := &api.Route{}
|
|
if err = json.Unmarshal(content, &got); err != nil {
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
}
|
|
assert.Equal(t, got, tc.expectedRoute)
|
|
})
|
|
}
|
|
}
|