...

Source file src/edge-infra.dev/pkg/sds/emergencyaccess/eagateway/server/send_test.go

Documentation: edge-infra.dev/pkg/sds/emergencyaccess/eagateway/server

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"strings"
    11  	"testing"
    12  
    13  	"edge-infra.dev/pkg/lib/fog"
    14  	"edge-infra.dev/pkg/sds/emergencyaccess/apierror"
    15  	errorhandler "edge-infra.dev/pkg/sds/emergencyaccess/apierror/handler"
    16  	"edge-infra.dev/pkg/sds/emergencyaccess/eagateway"
    17  	"edge-infra.dev/pkg/sds/emergencyaccess/middleware"
    18  	"edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
    19  	"edge-infra.dev/pkg/sds/emergencyaccess/remotecli"
    20  	"edge-infra.dev/pkg/sds/emergencyaccess/requestservice"
    21  	"edge-infra.dev/pkg/sds/emergencyaccess/types"
    22  
    23  	"github.com/DATA-DOG/go-sqlmock"
    24  	"github.com/gin-gonic/gin"
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  var (
    29  	defaultCommand = "echo hello"
    30  )
    31  
    32  type sendTestRCLI struct {
    33  	eagateway.RemoteCLI
    34  
    35  	commandID string
    36  	userID    string
    37  	sessionID string
    38  	request   msgdata.Request
    39  }
    40  
    41  func (rcli *sendTestRCLI) Send(_ context.Context, userID, sessionID, commandID string, request msgdata.Request, _ ...remotecli.RCLIOption) error {
    42  	rcli.userID = userID
    43  	rcli.sessionID = sessionID
    44  	rcli.commandID = commandID
    45  	rcli.request = request
    46  	return nil
    47  }
    48  
    49  func createSendRequest(c *gin.Context, target types.Target, sessionID, command string, darkmode bool) (*http.Request, error) {
    50  	payload := types.SendPayload{
    51  		Command:   command,
    52  		SessionID: sessionID,
    53  		Target:    target,
    54  		AuthDetails: types.AuthDetails{
    55  			DarkMode: darkmode,
    56  		},
    57  	}
    58  	message, err := json.Marshal(payload)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	req, err := http.NewRequestWithContext(c, http.MethodPost, "/ea/sendCommand", bytes.NewBuffer(message))
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	setAuthHeaders(req)
    68  
    69  	return req, nil
    70  }
    71  
    72  func newRequestService(t *testing.T) *requestservice.RequestService {
    73  	t.Helper()
    74  
    75  	db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
    76  	assert.NoError(t, err)
    77  	mock.ExpectQuery(`SELECT value FROM watched_field_objects
    78  		INNER JOIN watched_field_values
    79  			ON watched_field_values.object_id = watched_field_objects.object_id
    80  		INNER JOIN terminals
    81  			ON watched_field_objects.cluster_edge_id = terminals.cluster_edge_id
    82  		WHERE watched_field_objects.cluster_edge_id = $1
    83  			AND kind = 'Node'
    84  			AND terminals.terminal_id = $2
    85  			AND jsonpath = '$.metadata.labels["feature.node.kubernetes.io/ien-version"]'
    86  			AND deleted IS NOT true
    87  		ORDER BY watched_at DESC
    88  		LIMIT 1
    89  		;`).
    90  		WithArgs(`storeID`, `terminalID`).
    91  		WillReturnRows(sqlmock.NewRows([]string{"value"}).
    92  			AddRow("v1.14.0"),
    93  		)
    94  	requestService, err := requestservice.New(db)
    95  	assert.NoError(t, err)
    96  
    97  	t.Cleanup(func() {
    98  		assert.NoError(t, mock.ExpectationsWereMet())
    99  	})
   100  
   101  	t.Cleanup(func() {
   102  		db.Close()
   103  	})
   104  
   105  	return requestService
   106  }
   107  
   108  func TestSendSuccess(t *testing.T) {
   109  	// Setup
   110  	r := httptest.NewRecorder()
   111  	gin.SetMode(gin.TestMode)
   112  	c, ginEngine := gin.CreateTestContext(r)
   113  
   114  	authServer, url := authserviceServer(http.StatusOK, WithMiddleware(verifyUserAuthHeaders(t)))
   115  	defer authServer.Close()
   116  
   117  	requestService := newRequestService(t)
   118  
   119  	rcli := &sendTestRCLI{}
   120  	_, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, requestService)
   121  	assert.NoError(t, err)
   122  
   123  	// Test
   124  	sessionID := "TestSendSuccess"
   125  	target := defaultTarget
   126  	command := defaultCommand
   127  	userID := "user"
   128  	req, err := createSendRequest(c, target, sessionID, command, false)
   129  	assert.NoError(t, err)
   130  	ginEngine.ServeHTTP(r, req)
   131  
   132  	// Confirm correlation ID is used as commandID
   133  	correlationID := r.Result().Header.Get(middleware.CorrelationIDKey)
   134  
   135  	expReq, err := msgdata.NewV1_0Request(defaultCommand)
   136  	assert.NoError(t, err)
   137  
   138  	expected := &sendTestRCLI{
   139  		request:   expReq,
   140  		sessionID: sessionID,
   141  		commandID: correlationID,
   142  		userID:    userID,
   143  	}
   144  	assert.Equal(t, http.StatusOK, r.Result().StatusCode)
   145  	assert.Equal(t, expected, rcli)
   146  }
   147  
   148  func TestSendBadPayload(t *testing.T) {
   149  	// Setup
   150  	r := httptest.NewRecorder()
   151  	gin.SetMode(gin.TestMode)
   152  	c, ginEngine := gin.CreateTestContext(r)
   153  
   154  	authServer, url := authserviceServer(http.StatusForbidden)
   155  	defer authServer.Close()
   156  
   157  	rcli := &sendTestRCLI{}
   158  	_, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, nil)
   159  	assert.NoError(t, err)
   160  
   161  	// Test
   162  	req, err := http.NewRequestWithContext(c, http.MethodPost, "/ea/sendCommand", nil)
   163  	assert.NoError(t, err)
   164  	ginEngine.ServeHTTP(r, req)
   165  
   166  	assert.Equal(t, http.StatusBadRequest, r.Result().StatusCode)
   167  	assert.Empty(t, rcli) // Assert rcli.Send isn't called
   168  }
   169  
   170  func TestSendUnauthorizedCommand(t *testing.T) {
   171  	// Setup
   172  	r := httptest.NewRecorder()
   173  	gin.SetMode(gin.TestMode)
   174  	c, ginEngine := gin.CreateTestContext(r)
   175  
   176  	authServer, url := authserviceServer(http.StatusOK)
   177  	defer authServer.Close()
   178  
   179  	requestService := newRequestService(t)
   180  
   181  	rcli := &sendTestRCLI{}
   182  	_, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, requestService)
   183  	assert.NoError(t, err)
   184  
   185  	// Test
   186  	sessionID := "TestSendUnauthorizedCommand"
   187  	target := defaultTarget
   188  	command := badCommand
   189  	req, err := createSendRequest(c, target, sessionID, command, false)
   190  	assert.NoError(t, err)
   191  	ginEngine.ServeHTTP(r, req)
   192  
   193  	assert.Equal(t, http.StatusForbidden, r.Result().StatusCode)
   194  	assert.Empty(t, rcli) // Assert rcli.Send isn't called
   195  
   196  	// Assert eagateway Response includes appropriate APIError
   197  	body, err := io.ReadAll(r.Result().Body)
   198  	assert.NoError(t, err)
   199  	var resp errorhandler.ErrorResponse
   200  	err = json.Unmarshal(body, &resp)
   201  	assert.NoError(t, err)
   202  	assert.Equal(t, apierror.ErrUnauthorizedCommand, resp.ErrorCode)
   203  }
   204  
   205  // checks sendcommand updates user error IF darkmode is enabled AND authcommand fails
   206  func TestDarkmodeUserErrorUnauthorizedCommand(t *testing.T) {
   207  	// Setup
   208  	r := httptest.NewRecorder()
   209  	gin.SetMode(gin.TestMode)
   210  	c, ginEngine := gin.CreateTestContext(r)
   211  
   212  	authServer, url := authserviceServer(http.StatusOK)
   213  	defer authServer.Close()
   214  
   215  	requestService := newRequestService(t)
   216  
   217  	rcli := &sendTestRCLI{}
   218  	_, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, requestService)
   219  	assert.NoError(t, err)
   220  
   221  	// create a bad request
   222  	sessionID := "TestDarkmodeUserErrorUnauthorizedCommand"
   223  	target := defaultTarget
   224  	command := badCommand
   225  	req, err := createSendRequest(c, target, sessionID, command, true)
   226  	assert.NoError(t, err)
   227  
   228  	ginEngine.ServeHTTP(r, req)
   229  	assert.Equal(t, http.StatusForbidden, r.Result().StatusCode)
   230  
   231  	// TEST
   232  	// parse the result
   233  	outErr := errorhandler.ParseJSONAPIError(r.Result().Body)
   234  	// cast it to apiErr
   235  	apiErr, ok := outErr.(apierror.APIError)
   236  	assert.True(t, ok)
   237  	// DarkMode no longer provides a nice user error
   238  	assert.Equal(t, "", strings.Join(apiErr.UserError(), ""))
   239  }
   240  
   241  func TestExtractPayloadSuccess(t *testing.T) {
   242  	r := httptest.NewRecorder()
   243  	gin.SetMode(gin.TestMode)
   244  	c, _ := gin.CreateTestContext(r)
   245  	sessionID := "TestSendSuccess"
   246  	command := defaultCommand
   247  	target := defaultTarget
   248  	req, err := createSendRequest(c, target, sessionID, command, false)
   249  	assert.NoError(t, err)
   250  	c.Request = req
   251  
   252  	payload, err := extractSendPayload(c)
   253  	expected := types.SendPayload{
   254  		Target:    target,
   255  		SessionID: sessionID,
   256  		Command:   command,
   257  	}
   258  	assert.Equal(t, expected, payload)
   259  	assert.NoError(t, err)
   260  }
   261  
   262  func TestExtractPayloadFail(t *testing.T) {
   263  	r := httptest.NewRecorder()
   264  	gin.SetMode(gin.TestMode)
   265  	c, _ := gin.CreateTestContext(r)
   266  	req, err := createSendRequest(c, types.Target{}, "", "", false)
   267  	assert.NoError(t, err)
   268  	c.Request = req
   269  
   270  	payload, err := extractSendPayload(c)
   271  	assert.Equal(t, types.SendPayload{}, payload)
   272  	assert.Error(t, err)
   273  }
   274  
   275  func TestSendAuditLog(t *testing.T) {
   276  	// Setup
   277  	r := httptest.NewRecorder()
   278  	gin.SetMode(gin.TestMode)
   279  	c, ginEngine := gin.CreateTestContext(r)
   280  
   281  	authServer, url := authserviceServer(http.StatusOK)
   282  	defer authServer.Close()
   283  
   284  	rcli := &sendTestRCLI{}
   285  	// test logger writes to a byte buffer so it can be read from test
   286  	b := bytes.Buffer{}
   287  	log := fog.New(fog.To(&b))
   288  	_, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, log, rcli, nil)
   289  	assert.NoError(t, err)
   290  
   291  	// Execute send api call
   292  	sessionID := "AuditLogCheck"
   293  	target := defaultTarget
   294  	command := defaultCommand
   295  	req, err := createSendRequest(c, target, sessionID, command, false)
   296  	assert.NoError(t, err)
   297  	ginEngine.ServeHTTP(r, req)
   298  	//test
   299  	assert.True(t, validateAuditLog(&b, "Send API Called"))
   300  }
   301  

View as plain text