소스 검색

update tests

AlteredCoder 5 년 전
부모
커밋
60a5aa3800

+ 8 - 28
pkg/cwapi/auth.go

@@ -1,7 +1,6 @@
 package cwapi
 package cwapi
 
 
 import (
 import (
-	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
@@ -169,20 +168,13 @@ func (ctx *ApiCtx) Signin() error {
 func (ctx *ApiCtx) RegisterMachine(machineID string, password string) error {
 func (ctx *ApiCtx) RegisterMachine(machineID string, password string) error {
 	ctx.Creds.User = machineID
 	ctx.Creds.User = machineID
 	ctx.Creds.Password = password
 	ctx.Creds.Password = password
+	jsonResp := &ApiResp{}
 
 
-	req, err := ctx.Http.New().Post(ctx.RegisterPath).BodyJSON(ctx.Creds).Request()
+	resp, err := ctx.Http.Post(ctx.RegisterPath).BodyJSON(ctx.Creds).ReceiveSuccess(jsonResp)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("api register machine: HTTP request creation failed: %s", err)
 		return fmt.Errorf("api register machine: HTTP request creation failed: %s", err)
 	}
 	}
-	log.Debugf("api register: URL: '%s'", req.URL)
-
-	httpClient := http.Client{Timeout: 20 * time.Second}
-	resp, err := httpClient.Do(req)
-	if err != nil {
-		return fmt.Errorf("api register machine: API call failed : %s", err)
-	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
-
 	body, err := ioutil.ReadAll(resp.Body)
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("api register machine: unable to read API response body: %s", err.Error())
 		return fmt.Errorf("api register machine: unable to read API response body: %s", err.Error())
@@ -192,10 +184,8 @@ func (ctx *ApiCtx) RegisterMachine(machineID string, password string) error {
 		return fmt.Errorf("api register machine: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
 		return fmt.Errorf("api register machine: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
 	}
 	}
 
 
-	jsonResp := ApiResp{}
-	err = json.Unmarshal(body, &jsonResp)
-	if err != nil {
-		return fmt.Errorf("api register machine: unable to unmarshall api response '%s': %s", string(body), err.Error())
+	if jsonResp.Message == "" || jsonResp.Message != "OK" || jsonResp.StatusCode != 200 {
+		return fmt.Errorf("api signin failed. http response: %s", body)
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -203,19 +193,14 @@ func (ctx *ApiCtx) RegisterMachine(machineID string, password string) error {
 func (ctx *ApiCtx) ResetPassword(machineID string, password string) error {
 func (ctx *ApiCtx) ResetPassword(machineID string, password string) error {
 	ctx.Creds.User = machineID
 	ctx.Creds.User = machineID
 	ctx.Creds.Password = password
 	ctx.Creds.Password = password
+	jsonResp := &ApiResp{}
 
 
 	data := map[string]string{"machine_id": ctx.Creds.User, "password": ctx.Creds.Password}
 	data := map[string]string{"machine_id": ctx.Creds.User, "password": ctx.Creds.Password}
-	req, err := ctx.Http.New().Post(ctx.ResetPwdPath).BodyJSON(data).Request()
+	resp, err := ctx.Http.Post(ctx.ResetPwdPath).BodyJSON(data).ReceiveSuccess(jsonResp)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("api reset password: HTTP request creation failed: %s", err)
 		return fmt.Errorf("api reset password: HTTP request creation failed: %s", err)
 	}
 	}
-	log.Debugf("api reset: URL: '%s'", req.URL)
 
 
-	httpClient := http.Client{Timeout: 20 * time.Second}
-	resp, err := httpClient.Do(req)
-	if err != nil {
-		return fmt.Errorf("api reset password: API call failed : %s", err)
-	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 
 
 	body, err := ioutil.ReadAll(resp.Body)
 	body, err := ioutil.ReadAll(resp.Body)
@@ -227,13 +212,8 @@ func (ctx *ApiCtx) ResetPassword(machineID string, password string) error {
 		return fmt.Errorf("api reset password: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
 		return fmt.Errorf("api reset password: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
 	}
 	}
 
 
-	jsonResp := ApiResp{}
-	err = json.Unmarshal(body, &jsonResp)
-	if err != nil {
-		return fmt.Errorf("api reset password: unable to unmarshall api response '%s': %s", string(body), err.Error())
-	}
-	if jsonResp.StatusCode != 200 {
-		return fmt.Errorf("api reset password: return bad HTTP code (%d): %s", jsonResp.StatusCode, string(body))
+	if jsonResp.Message == "" || jsonResp.Message != "password updated successfully" || jsonResp.StatusCode != 200 {
+		return fmt.Errorf("api signin failed. http response: %s", body)
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 400 - 44
pkg/cwapi/auth_test.go

@@ -1,59 +1,101 @@
 package cwapi
 package cwapi
 
 
 import (
 import (
-	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
-	"strings"
 	"testing"
 	"testing"
 
 
 	"github.com/dghubble/sling"
 	"github.com/dghubble/sling"
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
 	"gopkg.in/tomb.v2"
 	"gopkg.in/tomb.v2"
+	"gopkg.in/yaml.v2"
 )
 )
 
 
-const configFile = "./tests/api_config.yaml"
-const apiVersion = "v1"
-const apiURL = "https://my_test_endpoint"
-
-var apiBaseURL = fmt.Sprintf("%s/%s/", apiURL, apiVersion)
+func assertConfigFileEqual(t *testing.T, filepath1 string, filepath2 string) {
+	file1, err := ioutil.ReadFile(filepath1)
+	if err != nil {
+		t.Fatalf("unable to read file '%s': %s", filepath1, err)
+	}
+	apiCtx1 := &ApiCtx{}
+	if err := yaml.UnmarshalStrict(file1, &apiCtx1); err != nil {
+		t.Fatalf("unable to unmarshall configuration file '%s' : %s", filepath1, err)
+	}
 
 
-var httpClientMock = &http.Client{
-	Transport: newMockTransport(),
+	file2, err := ioutil.ReadFile(filepath2)
+	if err != nil {
+		t.Fatalf("unable to read file '%s': %s", filepath2, err)
+	}
+	apiCtx2 := &ApiCtx{}
+	if err := yaml.UnmarshalStrict(file2, &apiCtx2); err != nil {
+		t.Fatalf("unable to unmarshall configuration file '%s' : %s", filepath2, err)
+	}
+	assert.Equal(t, apiCtx1, apiCtx2)
 }
 }
 
 
-type mockTransport struct{}
+func TestWriteConfig(t *testing.T) {
+	tests := []struct {
+		name          string
+		configPath    string
+		compareToFile string
+		expectedErr   bool
+		givenAPICtx   *ApiCtx
+	}{
+		{
+			name:          "basic write config",
+			configPath:    "./tests/tmp_api_config.yaml",
+			compareToFile: "./tests/api_config.yaml",
+			expectedErr:   false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				PullPath:     "pull",
+				PushPath:     "signals",
+				SigninPath:   "signin",
+				RegisterPath: "register",
+				ResetPwdPath: "resetpassword",
+				EnrollPath:   "enroll",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "test",
+				CfgPassword:  "test",
+				Creds: ApiCreds{
+					User:     "test",
+					Password: "test",
+				},
+				Muted:     false,
+				DebugDump: false,
+				Http:      sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+	}
 
 
-func newMockTransport() http.RoundTripper {
-	return &mockTransport{}
-}
+	for _, test := range tests {
+		err := test.givenAPICtx.WriteConfig(test.configPath)
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an error", test.name)
+		}
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' returned an error", test.name)
+		}
+		if test.expectedErr {
+			continue
+		}
 
 
-// Implement http.RoundTripper
-func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	var responseBody string
-	var statusCode int
-	// Create mocked http.Response
-	response := &http.Response{
-		Header:  make(http.Header),
-		Request: req,
-	}
-	response.Header.Set("Content-Type", "application/json")
-	if req.URL.Path == "/v1/signin" {
-		responseBody = `{"statusCode": 200, "message": "crowdsec_api_token"}`
-		statusCode = 200
+		assertConfigFileEqual(t, test.configPath, test.compareToFile)
 	}
 	}
-	response.StatusCode = statusCode
-	response.Body = ioutil.NopCloser(strings.NewReader(responseBody))
-	return response, nil
-}
 
 
-func TestSignin(t *testing.T) {
+}
 
 
+func TestLoadConfig(t *testing.T) {
 	tests := []struct {
 	tests := []struct {
-		apiCtx *ApiCtx
-		err    error
+		name           string
+		configPath     string
+		expectedErr    bool
+		expectedAPICtx *ApiCtx
 	}{
 	}{
 		{
 		{
-			apiCtx: &ApiCtx{
+			name:        "basic load config",
+			configPath:  "./tests/api_config.yaml",
+			expectedErr: false,
+			expectedAPICtx: &ApiCtx{
 				ApiVersion:   "v1",
 				ApiVersion:   "v1",
 				PullPath:     "pull",
 				PullPath:     "pull",
 				PushPath:     "signals",
 				PushPath:     "signals",
@@ -62,6 +104,187 @@ func TestSignin(t *testing.T) {
 				ResetPwdPath: "resetpassword",
 				ResetPwdPath: "resetpassword",
 				EnrollPath:   "enroll",
 				EnrollPath:   "enroll",
 				BaseURL:      "https://my_testendpoint.com",
 				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "test",
+				CfgPassword:  "test",
+				Creds: ApiCreds{
+					User:     "test",
+					Password: "test",
+				},
+				Muted:     false,
+				DebugDump: false,
+				Http:      sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "load config with bad api version",
+			configPath:  "./tests/api_config_bad_api_version.yaml",
+			expectedErr: true,
+		},
+		{
+			name:        "load config with bad format file",
+			configPath:  "./tests/api_config_bad_format.yaml",
+			expectedErr: true,
+		},
+	}
+
+	for _, test := range tests {
+		apiCtx := &ApiCtx{}
+		err := apiCtx.LoadConfig(test.configPath)
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an error", test.name)
+		}
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' return an error : %s", test.name, err)
+		}
+		if test.expectedErr {
+			continue
+		}
+		apiCtx.Http = test.expectedAPICtx.Http // if we don't do that, assert will fail
+		assert.Equal(t, test.expectedAPICtx, apiCtx)
+	}
+}
+
+func TestSignin(t *testing.T) {
+
+	tests := []struct {
+		name        string
+		givenAPICtx *ApiCtx
+		expectedErr bool
+	}{
+		{
+			name:        "basic api signin",
+			expectedErr: false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				SigninPath:  "signin",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin missing credentials",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				SigninPath:  "signin",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin unknown api PATH",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				SigninPath:  "unknown_path",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin malformed response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				SigninPath:  "malformed_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin bad response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				SigninPath:  "bad_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		err := test.givenAPICtx.Signin()
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' failed : %s", test.name, err)
+		}
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an err", test.name)
+		}
+		log.Printf("test '%s' : OK", test.name)
+	}
+
+}
+
+func TestRegisterMachine(t *testing.T) {
+
+	tests := []struct {
+		name             string
+		givenAPICtx      *ApiCtx
+		expectedErr      bool
+		expectedAPICtx   *ApiCtx
+		expectedAPICreds *ApiCreds
+	}{
+		{
+			name:        "basic api register machine",
+			expectedErr: false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				RegisterPath: "register",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "machine_id",
+				CfgPassword:  "machine_password",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+			expectedAPICreds: &ApiCreds{
+				User:     "machine_id",
+				Password: "machine_password",
+				Profile:  "crowdsec/test1,crowdsec/test2",
+			},
+		},
+		{
+			name:        "api register unknown api PATH",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				RegisterPath: "unknown_path",
+				BaseURL:      "https://my_testendpoint.com",
 				CfgUser:      "machine_id",
 				CfgUser:      "machine_id",
 				CfgPassword:  "machine_password",
 				CfgPassword:  "machine_password",
 				Creds: ApiCreds{
 				Creds: ApiCreds{
@@ -69,27 +292,160 @@ func TestSignin(t *testing.T) {
 					Password: "machine_password",
 					Password: "machine_password",
 					Profile:  "crowdsec/test1,crowdsec/test2",
 					Profile:  "crowdsec/test1,crowdsec/test2",
 				},
 				},
-				Muted:      false,
-				DebugDump:  false,
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api register malformed response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				RegisterPath: "malformed_response",
+				BaseURL:      "https://my_testendpoint.com",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
 				Http:       sling.New().Client(httpClientMock).Base(apiBaseURL),
 				Http:       sling.New().Client(httpClientMock).Base(apiBaseURL),
 				PusherTomb: tomb.Tomb{},
 				PusherTomb: tomb.Tomb{},
 			},
 			},
 		},
 		},
+		{
+			name:        "api register bad response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				RegisterPath: "bad_response",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "machine_id",
+				CfgPassword:  "machine_password",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
 	}
 	}
 
 
 	for _, test := range tests {
 	for _, test := range tests {
-		if err := test.apiCtx.Signin(); err != nil {
-			t.Fatalf(err.Error())
+		err := test.givenAPICtx.RegisterMachine(test.givenAPICtx.CfgUser, test.givenAPICtx.CfgPassword)
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' failed : %s", test.name, err)
+		}
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an err", test.name)
 		}
 		}
+		if test.expectedAPICreds != nil {
+			assert.Equal(t, *test.expectedAPICreds, test.givenAPICtx.Creds)
+		}
+		log.Printf("test '%s' : OK", test.name)
 	}
 	}
 
 
 }
 }
 
 
-/*func TestRegister(t *testing.T) {
-	prepTest()
+func TestResetPassword(t *testing.T) {
 
 
-	if err := apiCtx.RegisterMachine(); err != nil {
-		t.Fatalf(err.Error())
+	tests := []struct {
+		name             string
+		givenAPICtx      *ApiCtx
+		expectedErr      bool
+		expectedAPICtx   *ApiCtx
+		expectedAPICreds *ApiCreds
+	}{
+		{
+			name:        "basic api machine reset password",
+			expectedErr: false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				ResetPwdPath: "resetpassword",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "machine_id",
+				CfgPassword:  "new_machine_password",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+			expectedAPICreds: &ApiCreds{
+				User:     "machine_id",
+				Password: "new_machine_password",
+				Profile:  "crowdsec/test1,crowdsec/test2",
+			},
+		},
+		{
+			name:        "api reset password unknown api PATH",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				ResetPwdPath: "unknown_path",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "machine_id",
+				CfgPassword:  "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api reset password malformed response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				ResetPwdPath: "malformed_response",
+				BaseURL:      "https://my_testendpoint.com",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http:       sling.New().Client(httpClientMock).Base(apiBaseURL),
+				PusherTomb: tomb.Tomb{},
+			},
+		},
+		{
+			name:        "api reset password bad response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				ResetPwdPath: "bad_response",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "machine_id",
+				CfgPassword:  "machine_password",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api reset password unknown user",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:   "v1",
+				ResetPwdPath: "resestpassword_unknown_user",
+				BaseURL:      "https://my_testendpoint.com",
+				CfgUser:      "machine_id",
+				CfgPassword:  "machine_password",
+				Creds: ApiCreds{
+					Profile: "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
 	}
 	}
+
+	for _, test := range tests {
+		err := test.givenAPICtx.ResetPassword(test.givenAPICtx.CfgUser, test.givenAPICtx.CfgPassword)
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' failed : %s", test.name, err)
+		}
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an err", test.name)
+		}
+		if test.expectedAPICreds != nil {
+			assert.Equal(t, *test.expectedAPICreds, test.givenAPICtx.Creds)
+		}
+		log.Printf("test '%s' : OK", test.name)
+	}
+
 }
 }
-*/

+ 5 - 9
pkg/cwapi/enroll.go

@@ -3,25 +3,18 @@ package cwapi
 import (
 import (
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
-	"time"
 
 
 	log "github.com/sirupsen/logrus"
 	log "github.com/sirupsen/logrus"
 )
 )
 
 
 func (ctx *ApiCtx) Enroll(userID string) error {
 func (ctx *ApiCtx) Enroll(userID string) error {
 	toPush := map[string]string{"user_id": userID}
 	toPush := map[string]string{"user_id": userID}
+	jsonResp := &ApiResp{}
 
 
-	req, err := ctx.Http.New().Post(ctx.EnrollPath).BodyJSON(&toPush).Request()
+	resp, err := ctx.Http.Post(ctx.EnrollPath).BodyJSON(&toPush).ReceiveSuccess(jsonResp)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("api enroll: HTTP request creation failed: %s", err)
 		return fmt.Errorf("api enroll: HTTP request creation failed: %s", err)
 	}
 	}
-	log.Debugf("api enroll: URL: '%s'", req.URL)
-	httpClient := http.Client{Timeout: 20 * time.Second}
-	resp, err := httpClient.Do(req)
-	if err != nil {
-		return fmt.Errorf("api enroll: API call failed : %s", err)
-	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	body, err := ioutil.ReadAll(resp.Body)
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 	if err != nil {
@@ -31,6 +24,9 @@ func (ctx *ApiCtx) Enroll(userID string) error {
 	if resp.StatusCode != 200 {
 	if resp.StatusCode != 200 {
 		return fmt.Errorf("api enroll: user '%s' return bad HTTP code (%d): %s", userID, resp.StatusCode, string(body))
 		return fmt.Errorf("api enroll: user '%s' return bad HTTP code (%d): %s", userID, resp.StatusCode, string(body))
 	}
 	}
+	if jsonResp.Message == "" || jsonResp.Message != "OK" || jsonResp.StatusCode != 200 {
+		return fmt.Errorf("api user enroll failed. http response: %s", body)
+	}
 	log.Printf("user '%s' is enrolled successfully", string(userID))
 	log.Printf("user '%s' is enrolled successfully", string(userID))
 	return nil
 	return nil
 }
 }

+ 103 - 0
pkg/cwapi/enroll_test.go

@@ -0,0 +1,103 @@
+package cwapi
+
+import (
+	"testing"
+
+	"github.com/dghubble/sling"
+	log "github.com/sirupsen/logrus"
+)
+
+func TestEnroll(t *testing.T) {
+
+	tests := []struct {
+		name        string
+		givenAPICtx *ApiCtx
+		expectedErr bool
+		userID      string
+	}{
+		{
+			name:        "basic api user enroll",
+			expectedErr: false,
+			userID:      "1234",
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				EnrollPath:  "enroll",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin unknown api PATH",
+			expectedErr: true,
+			userID:      "1234",
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				EnrollPath:  "unknown_path",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin malformed response",
+			expectedErr: true,
+			userID:      "1234",
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				EnrollPath:  "malformed_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api signin bad response",
+			expectedErr: true,
+			userID:      "1234",
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				EnrollPath:  "bad_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				Http: sling.New().Client(httpClientMock).Base(apiBaseURL),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		err := test.givenAPICtx.Enroll(test.userID)
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' failed : %s", test.name, err)
+		}
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an err", test.name)
+		}
+		log.Printf("test '%s' : OK", test.name)
+	}
+
+}

+ 7 - 10
pkg/cwapi/signals.go

@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 	"time"
 	"time"
 
 
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
@@ -22,18 +21,11 @@ func (ctx *ApiCtx) pushSignals() error {
 	if len(ctx.toPush) == 0 {
 	if len(ctx.toPush) == 0 {
 		return nil
 		return nil
 	}
 	}
-
-	req, err := ctx.Http.New().Put(ctx.PushPath).BodyJSON(&ctx.toPush).Request()
+	jsonResp := &ApiResp{}
+	resp, err := ctx.Http.New().Put(ctx.PushPath).BodyJSON(&ctx.toPush).ReceiveSuccess(jsonResp)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("api push signal: HTTP request creation failed: %s", err)
 		return fmt.Errorf("api push signal: HTTP request creation failed: %s", err)
 	}
 	}
-	log.Debugf("api push: URL: '%s'", req.URL)
-
-	httpClient := http.Client{Timeout: 20 * time.Second}
-	resp, err := httpClient.Do(req)
-	if err != nil {
-		return fmt.Errorf("api push signal: API call failed : %s", err)
-	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	body, err := ioutil.ReadAll(resp.Body)
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 	if err != nil {
@@ -57,6 +49,11 @@ func (ctx *ApiCtx) pushSignals() error {
 			return fmt.Errorf("api push signal: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
 			return fmt.Errorf("api push signal: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
 		}
 		}
 	}
 	}
+
+	if resp.StatusCode != 401 && (jsonResp.Message == "" || jsonResp.Message != "OK" || jsonResp.StatusCode != 200) {
+		return fmt.Errorf("api push failed. http response: %s", body)
+	}
+
 	if len(ctx.toPush) > 0 {
 	if len(ctx.toPush) > 0 {
 		log.Infof("api push signal: pushed %d signals successfully", len(ctx.toPush))
 		log.Infof("api push signal: pushed %d signals successfully", len(ctx.toPush))
 	}
 	}

+ 248 - 0
pkg/cwapi/signals_test.go

@@ -0,0 +1,248 @@
+package cwapi
+
+import (
+	"testing"
+	"time"
+
+	"github.com/crowdsecurity/crowdsec/pkg/types"
+	"github.com/dghubble/sling"
+	log "github.com/sirupsen/logrus"
+)
+
+var signalList = []types.Event{
+	{
+		Overflow: types.SignalOccurence{
+			Scenario:                            "crowdsec/test",
+			Bucket_id:                           "1234",
+			Events_count:                        1,
+			Events_sequence:                     []types.EventSequence{},
+			Start_at:                            time.Now(),
+			BanApplications:                     []types.BanApplication{},
+			Stop_at:                             time.Now(),
+			Source_ip:                           "1.2.3.4",
+			Source_range:                        "1.2.3.0/24",
+			Source_AutonomousSystemNumber:       "1234",
+			Source_AutonomousSystemOrganization: "TestAS",
+			Source_Country:                      "FR",
+			Dest_ip:                             "1.2.3.5",
+			Capacity:                            1,
+			Whitelisted:                         false,
+			Simulation:                          false,
+		},
+	},
+	{
+		Overflow: types.SignalOccurence{
+			Scenario:                            "crowdsec/test",
+			Bucket_id:                           "1235",
+			Events_count:                        1,
+			Events_sequence:                     []types.EventSequence{},
+			Start_at:                            time.Now(),
+			BanApplications:                     []types.BanApplication{},
+			Stop_at:                             time.Now(),
+			Source_ip:                           "1.2.3.5",
+			Source_range:                        "1.2.3.0/24",
+			Source_AutonomousSystemNumber:       "1234",
+			Source_AutonomousSystemOrganization: "TestAS",
+			Source_Country:                      "FR",
+			Dest_ip:                             "1.2.3.6",
+			Capacity:                            1,
+			Whitelisted:                         false,
+			Simulation:                          false,
+		},
+	},
+}
+
+func TestPushSignal(t *testing.T) {
+
+	tests := []struct {
+		name        string
+		givenAPICtx *ApiCtx
+		expectedErr bool
+	}{
+		{
+			name:        "basic api push signal",
+			expectedErr: false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "signals",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush: signalList,
+				Http:   sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal unknown api PATH",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "unknown_path",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush: signalList,
+				Http:   sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal malformed response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "malformed_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush: signalList,
+				Http:   sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal bad response",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "bad_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush: signalList,
+				Http:   sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal empty signal list",
+			expectedErr: false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "signals",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush: []types.Event{},
+				Http:   sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal expired token",
+			expectedErr: false,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "signals_token_expired",
+				SigninPath:  "signin",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush:       signalList,
+				tokenExpired: false,
+				Http:         sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal unable to renew expired token",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "signals_token_renew_fail",
+				SigninPath:  "signin",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush:       signalList,
+				tokenExpired: false,
+				Http:         sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal bad response code",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "signals_bad_response_code",
+				SigninPath:  "signin",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush:       signalList,
+				tokenExpired: false,
+				Http:         sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+		{
+			name:        "api push signal signin while token expired failed",
+			expectedErr: true,
+			givenAPICtx: &ApiCtx{
+				ApiVersion:  "v1",
+				PushPath:    "signals_token_expired",
+				SigninPath:  "bad_response",
+				BaseURL:     "https://my_testendpoint.com",
+				CfgUser:     "machine_id",
+				CfgPassword: "machine_password",
+				Creds: ApiCreds{
+					User:     "machine_id",
+					Password: "machine_password",
+					Profile:  "crowdsec/test1,crowdsec/test2",
+				},
+				toPush:       signalList,
+				tokenExpired: false,
+				Http:         sling.New().Client(newMockClient()).Base(apiBaseURL),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		err := test.givenAPICtx.pushSignals()
+		if !test.expectedErr && err != nil {
+			t.Fatalf("test '%s' failed : %s", test.name, err)
+		}
+		if test.expectedErr && err == nil {
+			t.Fatalf("test '%s' should return an err", test.name)
+		}
+		if test.expectedErr {
+			continue
+		}
+		log.Printf("test '%s' : OK", test.name)
+	}
+
+}

+ 1 - 1
pkg/cwapi/tests/api_config.yaml

@@ -1,5 +1,5 @@
 version: v1
 version: v1
-url: https://test_endpoint
+url: https://my_testendpoint.com
 signin_path: signin
 signin_path: signin
 push_path: signals
 push_path: signals
 pull_path: pull
 pull_path: pull

+ 10 - 0
pkg/cwapi/tests/api_config_bad_api_version.yaml

@@ -0,0 +1,10 @@
+version: v0
+url: https://test_endpoint
+signin_path: signin
+push_path: signals
+pull_path: pull
+enroll_path: enroll
+reset_pwd_path: resetpassword
+register_path: register
+machine_id: test
+password: test

+ 11 - 0
pkg/cwapi/tests/api_config_bad_format.yaml

@@ -0,0 +1,11 @@
+bad_key: test
+version: v1
+url: https://test_endpoint
+signin_path: signin
+push_path: signals
+pull_path: pull
+enroll_path: enroll
+reset_pwd_path: resetpassword
+register_path: register
+machine_id: test
+password: test

+ 101 - 0
pkg/cwapi/utils_test.go

@@ -0,0 +1,101 @@
+package cwapi
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"time"
+)
+
+const configFile = "./tests/api_config.yaml"
+const apiVersion = "v1"
+const apiURL = "https://my_test_endpoint"
+
+var apiBaseURL = fmt.Sprintf("%s/%s/", apiURL, apiVersion)
+
+var httpClientMock = &http.Client{
+	Transport: newMockTransport(),
+	Timeout:   time.Second * 20,
+}
+
+type mockTransport struct {
+	nbTryTokenOK  int // to test token expiration
+	nbTryTokenNOK int
+}
+
+func newMockTransport() http.RoundTripper {
+	return &mockTransport{}
+}
+
+func newMockClient() *http.Client {
+	return &http.Client{
+		Transport: newMockTransport(),
+		Timeout:   time.Second * 20,
+	}
+}
+
+// Implement http.RoundTripper
+func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	var responseBody string
+	var statusCode int
+	// Create mocked http.Response
+	response := &http.Response{
+		Header:  make(http.Header),
+		Request: req,
+	}
+	response.Header.Set("Content-Type", "application/json")
+	switch req.URL.Path {
+	case "/v1/signin":
+		responseBody = `{"statusCode": 200, "message": "crowdsec_api_token"}`
+		statusCode = 200
+	case "/v1/register":
+		responseBody = `{"statusCode": 200, "message": "OK"}`
+		statusCode = 200
+	case "/v1/signals":
+		responseBody = `{"statusCode": 200, "message": "OK"}`
+		statusCode = 200
+	case "/v1/signals_token_expired":
+		if t.nbTryTokenOK == 0 {
+			responseBody = `{"statusCode": 200, "message": "crowdsec_api_token"}`
+			statusCode = 401
+			t.nbTryTokenOK++
+		} else {
+			responseBody = `{"statusCode": 200, "message": "OK"}`
+			statusCode = 200
+		}
+	case "/v1/signals_token_renew_fail":
+		if t.nbTryTokenNOK == 0 {
+			responseBody = `{"statusCode": 200, "message": "crowdsec_api_token"}`
+			statusCode = 401
+			t.nbTryTokenNOK++
+		} else {
+			responseBody = `{"statusCode": 500, "message": "token expired"}`
+			statusCode = 500
+		}
+	case "/v1/signals_bad_response_code":
+		responseBody = `{"statusCode": 200, "message": "OK"}`
+		statusCode = 500
+	case "/v1/enroll":
+		responseBody = `{"statusCode": 200, "message": "OK"}`
+		statusCode = 200
+	case "/v1/resetpassword":
+		responseBody = `{"statusCode": 200, "message": "password updated successfully"}`
+		statusCode = 200
+	case "/v1/resetpassword_unknown_user":
+		responseBody = `{"statusCode": 500, "message": "User not found"}`
+		statusCode = 200
+	case "/v1/unknown_path":
+		statusCode = 404
+		responseBody = `{"error": "unknown URI"}`
+	case "/v1/malformed_response":
+		statusCode = 200
+		responseBody = `{"statusCode" : 200, "msg" : "api_token"`
+	case "/v1/bad_response":
+		statusCode = 200
+		responseBody = `{"statusCode" : 200, "msg" : "api_token"}`
+	}
+	response.StatusCode = statusCode
+	response.Body = ioutil.NopCloser(strings.NewReader(responseBody))
+	return response, nil
+}