From ce4313b18e7e08fd3ff1db6b96c4939c389bebd9 Mon Sep 17 00:00:00 2001 From: nicksherron Date: Fri, 14 Feb 2020 04:21:34 -0500 Subject: [PATCH] test/server_test: first tests --- Makefile | 2 +- cmd/root.go | 2 +- cmd/transfer.go | 1 + go.mod | 3 + go.sum | 2 + internal/db.go | 2 +- internal/server.go | 15 ++- test/server_test.go | 318 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 338 insertions(+), 7 deletions(-) create mode 100644 test/server_test.go diff --git a/Makefile b/Makefile index a7467f9..ee895c6 100644 --- a/Makefile +++ b/Makefile @@ -61,5 +61,5 @@ clean: @test ! -e bin/${BIN_NAME} || rm bin/${BIN_NAME} test: - go test ./... + go test -v ./... diff --git a/cmd/root.go b/cmd/root.go index 1673e0b..f45ebef 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -119,6 +119,6 @@ func checkBhEnv() { WARNING: BH_URL is set to https://bashhub.com on this machine If you will be running bashhub-client locally be sure to add export BH_URL=%v to your .bashrc or .zshrc`, internal.Addr) - fmt.Println(msg, "\n") + fmt.Println(msg) } } diff --git a/cmd/transfer.go b/cmd/transfer.go index f38cf74..13a9dcb 100644 --- a/cmd/transfer.go +++ b/cmd/transfer.go @@ -186,6 +186,7 @@ func sysRegister(mac string, site string, user string, pass string) string { "hostname": host, "mac": mac, } + payloadBytes, err := json.Marshal(sys) if err != nil { log.Fatal(err) diff --git a/go.mod b/go.mod index d71fb1d..1300447 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,18 @@ module github.com/nicksherron/bashhub-server require ( github.com/appleboy/gin-jwt/v2 v2.6.3 + github.com/appleboy/gofight/v2 v2.1.2 github.com/cheggaaa/pb/v3 v3.0.4 github.com/fatih/color v1.9.0 github.com/gin-gonic/gin v1.5.0 + github.com/google/uuid v1.1.1 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jinzhu/gorm v1.9.12 github.com/lib/pq v1.3.0 github.com/mattn/go-sqlite3 v2.0.1+incompatible github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.4.0 golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd ) diff --git a/go.sum b/go.sum index eaeec50..a11fee5 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= diff --git a/internal/db.go b/internal/db.go index 1a3be94..4833081 100644 --- a/internal/db.go +++ b/internal/db.go @@ -222,7 +222,7 @@ func (cmd Command) commandInsert() int64 { res, err := db.Exec(` INSERT INTO commands("process_id","process_start_time","exit_status","uuid","command", "created", "path", "user_id", "system_name") - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) ON CONFLICT do nothing`, cmd.ProcessId, cmd.ProcessStartTime, cmd.ExitStatus, cmd.Uuid, cmd.Command, cmd.Created, cmd.Path, cmd.User.ID, cmd.SystemName) if err != nil { log.Fatal(err) diff --git a/internal/server.go b/internal/server.go index e74fbd7..2854558 100644 --- a/internal/server.go +++ b/internal/server.go @@ -129,8 +129,8 @@ func loggerWithFormatterWriter(f gin.LogFormatter) gin.HandlerFunc { }) } -// Run starts server -func Run() { +// configure routes and middleware +func SetupRouter() *gin.Engine { // Initialize backend dbInit() @@ -197,7 +197,7 @@ func Run() { return &User{ Username: user.Username, SystemName: user.userGetSystemName(), - ID: user.userGetID(), + ID: user.userGetID(), }, nil } fmt.Println("failed") @@ -457,8 +457,15 @@ func Run() { c.AbortWithStatus(http.StatusOK) }) + return r +} + +// Run starts server +func Run() { + r := SetupRouter() + Addr = strings.ReplaceAll(Addr, "http://", "") - err = r.Run(Addr) + err := r.Run(Addr) if err != nil { fmt.Println("Error: \t", err) diff --git a/test/server_test.go b/test/server_test.go new file mode 100644 index 0000000..f400b65 --- /dev/null +++ b/test/server_test.go @@ -0,0 +1,318 @@ +/* + * + * Copyright © 2020 nicksherron + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/nicksherron/bashhub-server/internal" + "github.com/stretchr/testify/assert" +) + +var ( + dir string + router *gin.Engine + sysRegistered bool + jwtToken string + user = "tester" + pass = "tester" + mac = "888888888888888" +) + +func createUser(t *testing.T) { + auth := map[string]interface{}{ + "Username": user, + "password": pass, + "email": "test@email.com", + } + + payloadBytes, err := json.Marshal(auth) + if err != nil { + log.Fatal(err) + } + body := bytes.NewReader(payloadBytes) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/user", body) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) +} + +func getToken(t *testing.T) string { + + auth := map[string]interface{}{ + "username": user, + "password": pass, + "mac": mac, + } + + payloadBytes, err := json.Marshal(auth) + if err != nil { + log.Fatal(err) + } + + body := bytes.NewReader(payloadBytes) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/login", body) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + + buf, err := ioutil.ReadAll(w.Body) + + if err != nil { + t.Fatal(err) + } + j := make(map[string]interface{}) + + json.Unmarshal(buf, &j) + + if len(j) == 0 { + t.Fatal("login failed for getToken") + + } + token := fmt.Sprintf("Bearer %v", j["accessToken"]) + + if !sysRegistered { + // register system + return sysRegister(t, token) + } + return token +} + +func sysRegister(t *testing.T, token string) string { + + host, err := os.Hostname() + if err != nil { + log.Fatal(err) + } + sys := map[string]interface{}{ + "clientVersion": "1.2.0", + "name": "test-system", + "hostname": host, + "mac": mac, + } + payloadBytes, err := json.Marshal(sys) + if err != nil { + log.Fatal(err) + } + + body := bytes.NewReader(payloadBytes) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/system", body) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", token) + + router.ServeHTTP(w, req) + + assert.Equal(t, 201, w.Code) + + sysRegistered = true + + return getToken(t) + +} + +func TestMain(m *testing.M) { + err := os.RemoveAll("testdata") + if err != nil { + log.Fatal(err) + } + dir, err = os.Getwd() + if err != nil { + log.Fatal(err) + } + err = os.Mkdir("testdata", 0700) + if err != nil { + log.Fatal(err) + } + + internal.DbPath = filepath.Join(dir, "test.db") + + router = internal.SetupRouter() + + m.Run() +} + +func TestToken(t *testing.T) { + + w := httptest.NewRecorder() + + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "{\"message\":\"pong\"}\n", w.Body.String()) + createUser(t) + sysRegistered = false + jwtToken = getToken(t) + +} + +var commandTests = []internal.Command{ + {ProcessId: 90226, ExitStatus: 0, Command: "cat foo.txt"}, + {ProcessId: 90226, ExitStatus: 0, Command: "ls"}, + {ProcessId: 90226, ExitStatus: 0, Command: "pwd"}, + {ProcessId: 90226, ExitStatus: 0, Command: "whoami"}, + {ProcessId: 90226, ExitStatus: 0, Command: "which cat"}, + {ProcessId: 90226, ExitStatus: 0, Command: "head foo.txt"}, + {ProcessId: 90226, ExitStatus: 0, Command: "sed 's/fooobaar/foobar/g' somefile.txt"}, + {ProcessId: 90226, ExitStatus: 0, Command: "curl google.com"}, + {ProcessId: 90226, ExitStatus: 0, Command: "file /dev/null"}, + {ProcessId: 90226, ExitStatus: 0, Command: "df -h"}, + {ProcessId: 90226, ExitStatus: 127, Command: "catt"}, + {ProcessId: 90226, ExitStatus: 127, Command: "cay"}, +} + +func TestCommand(t *testing.T) { + hourAgo := time.Now().UnixNano() - (1 * time.Hour).Nanoseconds() + + for i := 0; i < 5; i++ { + for _, tc := range commandTests { + uid, err := uuid.NewRandom() + if err != nil { + t.Fatal(err) + } + tc.Path = dir + tc.Created = time.Now().Unix() + tc.ProcessStartTime = hourAgo + tc.Uuid = uid.String() + payloadBytes, err := json.Marshal(&tc) + if err != nil { + t.Fatal(err) + } + body := bytes.NewReader(payloadBytes) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/v1/command", body) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", jwtToken) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + } + } + func() { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/v1/command/search?unique=true", nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", jwtToken) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + var data []internal.Command + err = json.Unmarshal(b, &data) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 10, len(data)) + }() + func() { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/v1/command/search?", nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", jwtToken) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + var data []internal.Command + err = json.Unmarshal(b, &data) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 50, len(data)) + }() + func() { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/v1/command/search?query=%5Ecurl&unique=true", nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", jwtToken) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + var data []internal.Command + err = json.Unmarshal(b, &data) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(data)) + }() + func() { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/v1/command/search?unique=true&systemName=test-system", nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", jwtToken) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + var data []internal.Command + err = json.Unmarshal(b, &data) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 10, len(data)) + }() + func() { + w := httptest.NewRecorder() + v := url.Values{} + v.Add("unique", "true") + v.Add("path", dir) + u := fmt.Sprintf("/api/v1/command/search?%v", v.Encode()) + req, _ := http.NewRequest("GET", u, nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", jwtToken) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + var data []internal.Command + err = json.Unmarshal(b, &data) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 10, len(data)) + }() +}