github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiserver/alerts_test.go (about)

     1  package apiserver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  
    12  	"github.com/gin-gonic/gin"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    17  	"github.com/crowdsecurity/crowdsec/pkg/csplugin"
    18  	"github.com/crowdsecurity/crowdsec/pkg/models"
    19  )
    20  
    21  type LAPI struct {
    22  	router     *gin.Engine
    23  	loginResp  models.WatcherAuthResponse
    24  	bouncerKey string
    25  	DBConfig   *csconfig.DatabaseCfg
    26  }
    27  
    28  func SetupLAPITest(t *testing.T) LAPI {
    29  	t.Helper()
    30  	router, loginResp, config := InitMachineTest(t)
    31  
    32  	APIKey := CreateTestBouncer(t, config.API.Server.DbConfig)
    33  
    34  	return LAPI{
    35  		router:     router,
    36  		loginResp:  loginResp,
    37  		bouncerKey: APIKey,
    38  		DBConfig:   config.API.Server.DbConfig,
    39  	}
    40  }
    41  
    42  func (l *LAPI) InsertAlertFromFile(t *testing.T, path string) *httptest.ResponseRecorder {
    43  	alertReader := GetAlertReaderFromFile(t, path)
    44  	return l.RecordResponse(t, http.MethodPost, "/v1/alerts", alertReader, "password")
    45  }
    46  
    47  func (l *LAPI) RecordResponse(t *testing.T, verb string, url string, body *strings.Reader, authType string) *httptest.ResponseRecorder {
    48  	w := httptest.NewRecorder()
    49  	req, err := http.NewRequest(verb, url, body)
    50  	require.NoError(t, err)
    51  
    52  	switch authType {
    53  	case "apikey":
    54  		req.Header.Add("X-Api-Key", l.bouncerKey)
    55  	case "password":
    56  		AddAuthHeaders(req, l.loginResp)
    57  	default:
    58  		t.Fatal("auth type not supported")
    59  	}
    60  
    61  	l.router.ServeHTTP(w, req)
    62  
    63  	return w
    64  }
    65  
    66  func InitMachineTest(t *testing.T) (*gin.Engine, models.WatcherAuthResponse, csconfig.Config) {
    67  	router, config := NewAPITest(t)
    68  	loginResp := LoginToTestAPI(t, router, config)
    69  
    70  	return router, loginResp, config
    71  }
    72  
    73  func LoginToTestAPI(t *testing.T, router *gin.Engine, config csconfig.Config) models.WatcherAuthResponse {
    74  	body := CreateTestMachine(t, router)
    75  	ValidateMachine(t, "test", config.API.Server.DbConfig)
    76  
    77  	w := httptest.NewRecorder()
    78  	req, _ := http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(body))
    79  	req.Header.Add("User-Agent", UserAgent)
    80  	router.ServeHTTP(w, req)
    81  
    82  	loginResp := models.WatcherAuthResponse{}
    83  	err := json.NewDecoder(w.Body).Decode(&loginResp)
    84  	require.NoError(t, err)
    85  
    86  	return loginResp
    87  }
    88  
    89  func AddAuthHeaders(request *http.Request, authResponse models.WatcherAuthResponse) {
    90  	request.Header.Add("User-Agent", UserAgent)
    91  	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authResponse.Token))
    92  }
    93  
    94  func TestSimulatedAlert(t *testing.T) {
    95  	lapi := SetupLAPITest(t)
    96  	lapi.InsertAlertFromFile(t, "./tests/alert_minibulk+simul.json")
    97  	alertContent := GetAlertReaderFromFile(t, "./tests/alert_minibulk+simul.json")
    98  	//exclude decision in simulation mode
    99  
   100  	w := lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=false", alertContent, "password")
   101  	assert.Equal(t, 200, w.Code)
   102  	assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
   103  	assert.NotContains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
   104  	//include decision in simulation mode
   105  
   106  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=true", alertContent, "password")
   107  	assert.Equal(t, 200, w.Code)
   108  	assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
   109  	assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
   110  }
   111  
   112  func TestCreateAlert(t *testing.T) {
   113  	lapi := SetupLAPITest(t)
   114  	// Create Alert with invalid format
   115  
   116  	w := lapi.RecordResponse(t, http.MethodPost, "/v1/alerts", strings.NewReader("test"), "password")
   117  	assert.Equal(t, 400, w.Code)
   118  	assert.Equal(t, `{"message":"invalid character 'e' in literal true (expecting 'r')"}`, w.Body.String())
   119  
   120  	// Create Alert with invalid input
   121  	alertContent := GetAlertReaderFromFile(t, "./tests/invalidAlert_sample.json")
   122  
   123  	w = lapi.RecordResponse(t, http.MethodPost, "/v1/alerts", alertContent, "password")
   124  	assert.Equal(t, 500, w.Code)
   125  	assert.Equal(t, `{"message":"validation failure list:\n0.scenario in body is required\n0.scenario_hash in body is required\n0.scenario_version in body is required\n0.simulated in body is required\n0.source in body is required"}`, w.Body.String())
   126  
   127  	// Create Valid Alert
   128  	w = lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   129  	assert.Equal(t, 201, w.Code)
   130  	assert.Equal(t, `["1"]`, w.Body.String())
   131  }
   132  
   133  func TestCreateAlertChannels(t *testing.T) {
   134  	apiServer, config := NewAPIServer(t)
   135  	apiServer.controller.PluginChannel = make(chan csplugin.ProfileAlert)
   136  	apiServer.InitController()
   137  
   138  	loginResp := LoginToTestAPI(t, apiServer.router, config)
   139  	lapi := LAPI{router: apiServer.router, loginResp: loginResp}
   140  
   141  	var (
   142  		pd csplugin.ProfileAlert
   143  		wg sync.WaitGroup
   144  	)
   145  
   146  	wg.Add(1)
   147  
   148  	go func() {
   149  		pd = <-apiServer.controller.PluginChannel
   150  
   151  		wg.Done()
   152  	}()
   153  
   154  	lapi.InsertAlertFromFile(t, "./tests/alert_ssh-bf.json")
   155  	wg.Wait()
   156  	assert.Len(t, pd.Alert.Decisions, 1)
   157  	apiServer.Close()
   158  }
   159  
   160  func TestAlertListFilters(t *testing.T) {
   161  	lapi := SetupLAPITest(t)
   162  	lapi.InsertAlertFromFile(t, "./tests/alert_ssh-bf.json")
   163  	alertContent := GetAlertReaderFromFile(t, "./tests/alert_ssh-bf.json")
   164  
   165  	//bad filter
   166  
   167  	w := lapi.RecordResponse(t, "GET", "/v1/alerts?test=test", alertContent, "password")
   168  	assert.Equal(t, 500, w.Code)
   169  	assert.Equal(t, `{"message":"Filter parameter 'test' is unknown (=test): invalid filter"}`, w.Body.String())
   170  
   171  	//get without filters
   172  
   173  	w = lapi.RecordResponse(t, "GET", "/v1/alerts", emptyBody, "password")
   174  	assert.Equal(t, 200, w.Code)
   175  	//check alert and decision
   176  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   177  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   178  
   179  	//test decision_type filter (ok)
   180  
   181  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?decision_type=ban", emptyBody, "password")
   182  	assert.Equal(t, 200, w.Code)
   183  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   184  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   185  
   186  	//test decision_type filter (bad value)
   187  
   188  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?decision_type=ratata", emptyBody, "password")
   189  	assert.Equal(t, 200, w.Code)
   190  	assert.Equal(t, "null", w.Body.String())
   191  
   192  	//test scope (ok)
   193  
   194  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?scope=Ip", emptyBody, "password")
   195  	assert.Equal(t, 200, w.Code)
   196  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   197  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   198  
   199  	//test scope (bad value)
   200  
   201  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?scope=rarara", emptyBody, "password")
   202  	assert.Equal(t, 200, w.Code)
   203  	assert.Equal(t, "null", w.Body.String())
   204  
   205  	//test scenario (ok)
   206  
   207  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?scenario=crowdsecurity/ssh-bf", emptyBody, "password")
   208  	assert.Equal(t, 200, w.Code)
   209  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   210  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   211  
   212  	//test scenario (bad value)
   213  
   214  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?scenario=crowdsecurity/nope", emptyBody, "password")
   215  	assert.Equal(t, 200, w.Code)
   216  	assert.Equal(t, "null", w.Body.String())
   217  
   218  	//test ip (ok)
   219  
   220  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?ip=91.121.79.195", emptyBody, "password")
   221  	assert.Equal(t, 200, w.Code)
   222  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   223  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   224  
   225  	//test ip (bad value)
   226  
   227  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?ip=99.122.77.195", emptyBody, "password")
   228  	assert.Equal(t, 200, w.Code)
   229  	assert.Equal(t, "null", w.Body.String())
   230  
   231  	//test ip (invalid value)
   232  
   233  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?ip=gruueq", emptyBody, "password")
   234  	assert.Equal(t, 500, w.Code)
   235  	assert.Equal(t, `{"message":"unable to convert 'gruueq' to int: invalid address: invalid ip address / range"}`, w.Body.String())
   236  
   237  	//test range (ok)
   238  
   239  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?range=91.121.79.0/24&contains=false", emptyBody, "password")
   240  	assert.Equal(t, 200, w.Code)
   241  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   242  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   243  
   244  	//test range
   245  
   246  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?range=99.122.77.0/24&contains=false", emptyBody, "password")
   247  	assert.Equal(t, 200, w.Code)
   248  	assert.Equal(t, "null", w.Body.String())
   249  
   250  	//test range (invalid value)
   251  
   252  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?range=ratata", emptyBody, "password")
   253  	assert.Equal(t, 500, w.Code)
   254  	assert.Equal(t, `{"message":"unable to convert 'ratata' to int: invalid address: invalid ip address / range"}`, w.Body.String())
   255  
   256  	//test since (ok)
   257  
   258  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?since=1h", emptyBody, "password")
   259  	assert.Equal(t, 200, w.Code)
   260  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   261  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   262  
   263  	//test since (ok but yields no results)
   264  
   265  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?since=1ns", emptyBody, "password")
   266  	assert.Equal(t, 200, w.Code)
   267  	assert.Equal(t, "null", w.Body.String())
   268  
   269  	//test since (invalid value)
   270  
   271  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?since=1zuzu", emptyBody, "password")
   272  	assert.Equal(t, 500, w.Code)
   273  	assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
   274  
   275  	//test until (ok)
   276  
   277  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?until=1ns", emptyBody, "password")
   278  	assert.Equal(t, 200, w.Code)
   279  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   280  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   281  
   282  	//test until (ok but no return)
   283  
   284  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?until=1m", emptyBody, "password")
   285  	assert.Equal(t, 200, w.Code)
   286  	assert.Equal(t, "null", w.Body.String())
   287  
   288  	//test until (invalid value)
   289  
   290  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?until=1zuzu", emptyBody, "password")
   291  	assert.Equal(t, 500, w.Code)
   292  	assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
   293  
   294  	//test simulated (ok)
   295  
   296  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=true", emptyBody, "password")
   297  	assert.Equal(t, 200, w.Code)
   298  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   299  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   300  
   301  	//test simulated (ok)
   302  
   303  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=false", emptyBody, "password")
   304  	assert.Equal(t, 200, w.Code)
   305  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   306  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   307  
   308  	//test has active decision
   309  
   310  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?has_active_decision=true", emptyBody, "password")
   311  	assert.Equal(t, 200, w.Code)
   312  	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
   313  	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
   314  
   315  	//test has active decision
   316  
   317  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?has_active_decision=false", emptyBody, "password")
   318  	assert.Equal(t, 200, w.Code)
   319  	assert.Equal(t, "null", w.Body.String())
   320  
   321  	//test has active decision (invalid value)
   322  
   323  	w = lapi.RecordResponse(t, "GET", "/v1/alerts?has_active_decision=ratatqata", emptyBody, "password")
   324  	assert.Equal(t, 500, w.Code)
   325  	assert.Equal(t, `{"message":"'ratatqata' is not a boolean: strconv.ParseBool: parsing \"ratatqata\": invalid syntax: unable to parse type"}`, w.Body.String())
   326  }
   327  
   328  func TestAlertBulkInsert(t *testing.T) {
   329  	lapi := SetupLAPITest(t)
   330  	//insert a bulk of 20 alerts to trigger bulk insert
   331  	lapi.InsertAlertFromFile(t, "./tests/alert_bulk.json")
   332  	alertContent := GetAlertReaderFromFile(t, "./tests/alert_bulk.json")
   333  
   334  	w := lapi.RecordResponse(t, "GET", "/v1/alerts", alertContent, "password")
   335  	assert.Equal(t, 200, w.Code)
   336  }
   337  
   338  func TestListAlert(t *testing.T) {
   339  	lapi := SetupLAPITest(t)
   340  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   341  	// List Alert with invalid filter
   342  
   343  	w := lapi.RecordResponse(t, "GET", "/v1/alerts?test=test", emptyBody, "password")
   344  	assert.Equal(t, 500, w.Code)
   345  	assert.Equal(t, `{"message":"Filter parameter 'test' is unknown (=test): invalid filter"}`, w.Body.String())
   346  
   347  	// List Alert
   348  
   349  	w = lapi.RecordResponse(t, "GET", "/v1/alerts", emptyBody, "password")
   350  	assert.Equal(t, 200, w.Code)
   351  	assert.Contains(t, w.Body.String(), "crowdsecurity/test")
   352  }
   353  
   354  func TestCreateAlertErrors(t *testing.T) {
   355  	lapi := SetupLAPITest(t)
   356  	alertContent := GetAlertReaderFromFile(t, "./tests/alert_sample.json")
   357  
   358  	//test invalid bearer
   359  	w := httptest.NewRecorder()
   360  	req, _ := http.NewRequest(http.MethodPost, "/v1/alerts", alertContent)
   361  	req.Header.Add("User-Agent", UserAgent)
   362  	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "ratata"))
   363  	lapi.router.ServeHTTP(w, req)
   364  	assert.Equal(t, 401, w.Code)
   365  
   366  	//test invalid bearer
   367  	w = httptest.NewRecorder()
   368  	req, _ = http.NewRequest(http.MethodPost, "/v1/alerts", alertContent)
   369  	req.Header.Add("User-Agent", UserAgent)
   370  	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", lapi.loginResp.Token+"s"))
   371  	lapi.router.ServeHTTP(w, req)
   372  	assert.Equal(t, 401, w.Code)
   373  }
   374  
   375  func TestDeleteAlert(t *testing.T) {
   376  	lapi := SetupLAPITest(t)
   377  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   378  
   379  	// Fail Delete Alert
   380  	w := httptest.NewRecorder()
   381  	req, _ := http.NewRequest(http.MethodDelete, "/v1/alerts", strings.NewReader(""))
   382  	AddAuthHeaders(req, lapi.loginResp)
   383  	req.RemoteAddr = "127.0.0.2:4242"
   384  	lapi.router.ServeHTTP(w, req)
   385  	assert.Equal(t, 403, w.Code)
   386  	assert.Equal(t, `{"message":"access forbidden from this IP (127.0.0.2)"}`, w.Body.String())
   387  
   388  	// Delete Alert
   389  	w = httptest.NewRecorder()
   390  	req, _ = http.NewRequest(http.MethodDelete, "/v1/alerts", strings.NewReader(""))
   391  	AddAuthHeaders(req, lapi.loginResp)
   392  	req.RemoteAddr = "127.0.0.1:4242"
   393  	lapi.router.ServeHTTP(w, req)
   394  	assert.Equal(t, 200, w.Code)
   395  	assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
   396  }
   397  
   398  func TestDeleteAlertByID(t *testing.T) {
   399  	lapi := SetupLAPITest(t)
   400  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   401  
   402  	// Fail Delete Alert
   403  	w := httptest.NewRecorder()
   404  	req, _ := http.NewRequest(http.MethodDelete, "/v1/alerts/1", strings.NewReader(""))
   405  	AddAuthHeaders(req, lapi.loginResp)
   406  	req.RemoteAddr = "127.0.0.2:4242"
   407  	lapi.router.ServeHTTP(w, req)
   408  	assert.Equal(t, 403, w.Code)
   409  	assert.Equal(t, `{"message":"access forbidden from this IP (127.0.0.2)"}`, w.Body.String())
   410  
   411  	// Delete Alert
   412  	w = httptest.NewRecorder()
   413  	req, _ = http.NewRequest(http.MethodDelete, "/v1/alerts/1", strings.NewReader(""))
   414  	AddAuthHeaders(req, lapi.loginResp)
   415  	req.RemoteAddr = "127.0.0.1:4242"
   416  	lapi.router.ServeHTTP(w, req)
   417  	assert.Equal(t, 200, w.Code)
   418  	assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
   419  }
   420  
   421  func TestDeleteAlertTrustedIPS(t *testing.T) {
   422  	cfg := LoadTestConfig(t)
   423  	// IPv6 mocking doesn't seem to work.
   424  	// cfg.API.Server.TrustedIPs = []string{"1.2.3.4", "1.2.4.0/24", "::"}
   425  	cfg.API.Server.TrustedIPs = []string{"1.2.3.4", "1.2.4.0/24"}
   426  	cfg.API.Server.ListenURI = "::8080"
   427  	server, err := NewServer(cfg.API.Server)
   428  	require.NoError(t, err)
   429  
   430  	err = server.InitController()
   431  	require.NoError(t, err)
   432  
   433  	router, err := server.Router()
   434  	require.NoError(t, err)
   435  
   436  	loginResp := LoginToTestAPI(t, router, cfg)
   437  	lapi := LAPI{
   438  		router:    router,
   439  		loginResp: loginResp,
   440  	}
   441  
   442  	assertAlertDeleteFailedFromIP := func(ip string) {
   443  		w := httptest.NewRecorder()
   444  		req, _ := http.NewRequest(http.MethodDelete, "/v1/alerts", strings.NewReader(""))
   445  
   446  		AddAuthHeaders(req, loginResp)
   447  		req.RemoteAddr = ip + ":1234"
   448  
   449  		router.ServeHTTP(w, req)
   450  		assert.Equal(t, 403, w.Code)
   451  		assert.Contains(t, w.Body.String(), fmt.Sprintf(`{"message":"access forbidden from this IP (%s)"}`, ip))
   452  	}
   453  
   454  	assertAlertDeletedFromIP := func(ip string) {
   455  		w := httptest.NewRecorder()
   456  		req, _ := http.NewRequest(http.MethodDelete, "/v1/alerts", strings.NewReader(""))
   457  		AddAuthHeaders(req, loginResp)
   458  		req.RemoteAddr = ip + ":1234"
   459  
   460  		router.ServeHTTP(w, req)
   461  		assert.Equal(t, 200, w.Code)
   462  		assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
   463  	}
   464  
   465  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   466  	assertAlertDeleteFailedFromIP("4.3.2.1")
   467  	assertAlertDeletedFromIP("1.2.3.4")
   468  
   469  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   470  	assertAlertDeletedFromIP("1.2.4.0")
   471  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   472  	assertAlertDeletedFromIP("1.2.4.1")
   473  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   474  	assertAlertDeletedFromIP("1.2.4.255")
   475  
   476  	lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
   477  	assertAlertDeletedFromIP("127.0.0.1")
   478  }