error handling, tests, fixes

pull/508/head
hunterlong 2020-04-16 20:21:17 -07:00
parent 03e490afb9
commit 25d6f3b66a
32 changed files with 447 additions and 277 deletions

View File

@ -72,6 +72,8 @@ jobs:
DB_CONN: sqlite3
STATPING_DIR: /home/runner/work/statping/statping
API_KEY: demopassword123
DISABLE_LOGS: true
ALLOW_REPORTS: true
DISCORD_URL: ${{ secrets.DISCORD_URL }}
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
EMAIL_USER: ${{ secrets.EMAIL_USER }}
@ -90,7 +92,7 @@ jobs:
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
TWILIO_TO: ${{ secrets.TWILIO_TO }}
run: SASS=`which sass` go test -v -covermode=count -coverprofile=coverage.out -p=1 ./...
run: go test -v -covermode=count -coverprofile=coverage.out -p=1 ./...
- name: Build Binaries
run: make build-bin build-win

View File

@ -4,6 +4,10 @@
- Added Viper and Cobra config/env parsing package
- Added more golang tests
- Modified handlers to use a more generic find method
- Added 'env' command to show variables used in config
- Added 'reset' command that will delete files and backup .db file for a fresh install
- Added error type that has common errors with http status code based on error
# 0.90.27 (04-15-2020)
- Fixed postgres database table creation process

View File

@ -79,6 +79,61 @@ func sassCli() error {
return nil
}
func resetCli() error {
d := utils.Directory
fmt.Println("Statping directory: ", d)
assets := d + "/assets"
if utils.FolderExists(assets) {
fmt.Printf("Deleting %s folder.\n", assets)
if err := utils.DeleteDirectory(assets); err != nil {
return err
}
} else {
fmt.Printf("Assets folder does not exist %s\n", assets)
}
logDir := d + "/logs"
if utils.FolderExists(logDir) {
fmt.Printf("Deleting %s directory.\n", logDir)
if err := utils.DeleteDirectory(logDir); err != nil {
return err
}
} else {
fmt.Printf("Logs folder does not exist %s\n", logDir)
}
c := d + "/config.yml"
if utils.FileExists(c) {
fmt.Printf("Deleting %s file.\n", c)
if err := utils.DeleteFile(c); err != nil {
return err
}
} else {
fmt.Printf("Config file does not exist %s\n", c)
}
dbFile := d + "/statping.db"
if utils.FileExists(dbFile) {
fmt.Printf("Backuping up %s file.\n", dbFile)
if err := utils.RenameDirectory(dbFile, d+"/statping.db.backup"); err != nil {
return err
}
} else {
fmt.Printf("Statping SQL Database file does not exist %s\n", dbFile)
}
fmt.Println("Statping has been reset")
return nil
}
func envCli() error {
fmt.Println("Statping Configuration")
for k, v := range utils.Params.AllSettings() {
fmt.Printf("%s=%v\n", strings.ToUpper(k), v)
}
return nil
}
func onceCli() error {
if err := utils.InitLogs(); err != nil {
return err
@ -182,21 +237,6 @@ func ask(format string) bool {
return strings.ToLower(text) == "y"
}
// ExportIndexHTML returns the HTML of the index page as a string
//func ExportIndexHTML() []byte {
// source.Assets()
// core.CoreApp.Connect(core.CoreApp., utils.Directory)
// core.SelectAllServices(false)
// core.CoreApp.UseCdn = types.NewNullBool(true)
// for _, srv := range core.Services() {
// core.CheckService(srv, true)
// }
// w := httptest.NewRecorder()
// r := httptest.NewRequest("GET", "/", nil)
// handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil)
// return w.Body.Bytes()
//}
func updateDisplay() error {
gitCurrent, err := checkGithubUpdates()
if err != nil {
@ -426,3 +466,18 @@ func ExportSettings() ([]byte, error) {
export, err := json.Marshal(data)
return export, err
}
// ExportIndexHTML returns the HTML of the index page as a string
//func ExportIndexHTML() []byte {
// source.Assets()
// core.CoreApp.Connect(core.CoreApp., utils.Directory)
// core.SelectAllServices(false)
// core.CoreApp.UseCdn = types.NewNullBool(true)
// for _, srv := range core.Services() {
// core.CheckService(srv, true)
// }
// w := httptest.NewRecorder()
// r := httptest.NewRequest("GET", "/", nil)
// handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil)
// return w.Body.Bytes()
//}

View File

@ -2,15 +2,11 @@ package main
import (
"bytes"
"github.com/rendon/testcli"
"github.com/statping/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io/ioutil"
"os"
"os/exec"
"testing"
"time"
)
var (
@ -18,102 +14,31 @@ var (
)
func init() {
dir = utils.Directory
//core.SampleHits = 480
utils.InitCLI()
}
func TestStartServerCommand(t *testing.T) {
t.SkipNow()
cmd := helperCommand(nil, "")
var got = make(chan string)
commandAndSleep(cmd, time.Duration(60*time.Second), got)
os.Unsetenv("DB_CONN")
gg, _ := <-got
assert.Contains(t, gg, "DB_CONN environment variable was found")
assert.Contains(t, gg, "Core database does not exist, creating now!")
assert.Contains(t, gg, "Starting monitoring process for 5 Services")
func TestStatpingDirectory(t *testing.T) {
dir := utils.Directory
require.NotContains(t, dir, "/cmd")
require.NotEmpty(t, dir)
dir = utils.Params.GetString("STATPING_DIR")
require.NotContains(t, dir, "/cmd")
require.NotEmpty(t, dir)
}
func TestVersionCommand(t *testing.T) {
c := testcli.Command("statping", "version")
c.Run()
assert.True(t, c.StdoutContains(VERSION))
}
func TestHelpCommand(t *testing.T) {
c := testcli.Command("statping", "help")
c.Run()
t.Log(c.Stdout())
assert.True(t, c.StdoutContains("statping help - Shows the user basic information about Statping"))
}
func TestStaticCommand(t *testing.T) {
t.SkipNow()
cmd := helperCommand(nil, "static")
var got = make(chan string)
commandAndSleep(cmd, time.Duration(10*time.Second), got)
gg, _ := <-got
t.Log(gg)
assert.Contains(t, gg, "Exporting Static 'index.html' page...")
assert.Contains(t, gg, "Exported Statping index page: 'index.html'")
assert.FileExists(t, dir+"/index.html")
}
func TestExportCommand(t *testing.T) {
t.SkipNow()
cmd := helperCommand(nil, "export")
var got = make(chan string)
commandAndSleep(cmd, time.Duration(10*time.Second), got)
gg, _ := <-got
t.Log(gg)
assert.FileExists(t, dir+"/statping-export.json")
}
func TestUpdateCommand(t *testing.T) {
t.SkipNow()
cmd := helperCommand(nil, "version")
var got = make(chan string)
commandAndSleep(cmd, time.Duration(15*time.Second), got)
gg, _ := <-got
t.Log(gg)
assert.Contains(t, gg, VERSION)
}
func TestAssetsCommand(t *testing.T) {
t.SkipNow()
c := testcli.Command("statping", "assets")
c.Run()
t.Log(c.Stdout())
t.Log("Directory for Assets: ", dir)
time.Sleep(1 * time.Second)
err := utils.DeleteDirectory(dir + "/assets")
func TestEnvCLI(t *testing.T) {
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"env"})
err := cmd.Execute()
require.Nil(t, err)
assert.FileExists(t, dir+"/assets/robots.txt")
assert.FileExists(t, dir+"/assets/scss/base.scss")
assert.FileExists(t, dir+"/assets/scss/main.scss")
assert.FileExists(t, dir+"/assets/scss/variables.scss")
assert.FileExists(t, dir+"/assets/css/main.css")
assert.FileExists(t, dir+"/assets/css/vendor.css")
assert.FileExists(t, dir+"/assets/css/style.css")
err = utils.DeleteDirectory(dir + "/assets")
out, err := ioutil.ReadAll(b)
require.Nil(t, err)
}
func TestRunCommand(t *testing.T) {
t.SkipNow()
cmd := helperCommand(nil, "run")
var got = make(chan string)
commandAndSleep(cmd, time.Duration(15*time.Second), got)
gg, _ := <-got
t.Log(gg)
assert.Contains(t, gg, "Running 1 time and saving to database...")
assert.Contains(t, gg, "Check is complete.")
}
func TestEnvironmentVarsCommand(t *testing.T) {
c := testcli.Command("statping", "env")
c.Run()
assert.True(t, c.StdoutContains("Statping Environment Variable"))
assert.Contains(t, string(out), VERSION)
assert.Contains(t, utils.Directory, string(out))
assert.Contains(t, "SAMPLE_DATA=true", string(out))
}
func TestVersionCLI(t *testing.T) {
@ -121,10 +46,11 @@ func TestVersionCLI(t *testing.T) {
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"version"})
cmd.Execute()
err := cmd.Execute()
require.Nil(t, err)
out, err := ioutil.ReadAll(b)
assert.Nil(t, err)
assert.Contains(t, string(out), VERSION)
require.Nil(t, err)
assert.Contains(t, VERSION, string(out))
}
func TestAssetsCLI(t *testing.T) {
@ -132,51 +58,51 @@ func TestAssetsCLI(t *testing.T) {
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"assets"})
cmd.Execute()
err := cmd.Execute()
require.Nil(t, err)
out, err := ioutil.ReadAll(b)
assert.Nil(t, err)
assert.Contains(t, string(out), VERSION)
assert.FileExists(t, dir+"/assets/css/main.css")
assert.FileExists(t, dir+"/assets/css/style.css")
assert.FileExists(t, dir+"/assets/css/vendor.css")
assert.FileExists(t, dir+"/assets/scss/base.scss")
assert.FileExists(t, dir+"/assets/scss/mobile.scss")
assert.FileExists(t, dir+"/assets/scss/variables.scss")
assert.FileExists(t, utils.Directory+"/assets/css/main.css")
assert.FileExists(t, utils.Directory+"/assets/css/style.css")
assert.FileExists(t, utils.Directory+"/assets/css/vendor.css")
assert.FileExists(t, utils.Directory+"/assets/scss/base.scss")
assert.FileExists(t, utils.Directory+"/assets/scss/mobile.scss")
assert.FileExists(t, utils.Directory+"/assets/scss/variables.scss")
}
func TestSassCLI(t *testing.T) {
c := testcli.Command("statping", "assets")
c.Run()
t.Log(c.Stdout())
assert.FileExists(t, dir+"/assets/css/main.css")
assert.FileExists(t, dir+"/assets/css/style.css")
assert.FileExists(t, dir+"/assets/css/vendor.css")
func TestHelpCLI(t *testing.T) {
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"help"})
err := cmd.Execute()
require.Nil(t, err)
out, err := ioutil.ReadAll(b)
require.Nil(t, err)
assert.Contains(t, string(out), VERSION)
}
func TestUpdateCLI(t *testing.T) {
t.SkipNow()
cmd := helperCommand(nil, "update")
var got = make(chan string)
commandAndSleep(cmd, time.Duration(15*time.Second), got)
gg, _ := <-got
t.Log(gg)
assert.Contains(t, gg, "version")
}
func TestResetCLI(t *testing.T) {
err := utils.SaveFile(utils.Directory+"/statping.db", []byte("test data"))
require.Nil(t, err)
func commandAndSleep(cmd *exec.Cmd, duration time.Duration, out chan<- string) {
go func(out chan<- string) {
runCommand(cmd, out)
}(out)
time.Sleep(duration)
cmd.Process.Kill()
}
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"reset"})
err = cmd.Execute()
require.Nil(t, err)
out, err := ioutil.ReadAll(b)
require.Nil(t, err)
assert.Contains(t, string(out), VERSION)
func helperCommand(envs []string, s ...string) *exec.Cmd {
cmd := exec.Command("statping", s...)
return cmd
}
assert.NoDirExists(t, utils.Directory+"/assets")
assert.NoDirExists(t, utils.Directory+"/logs")
assert.NoFileExists(t, utils.Directory+"/config.yml")
assert.NoFileExists(t, utils.Directory+"/statping.db")
assert.FileExists(t, utils.Directory+"/statping.db.backup")
func runCommand(c *exec.Cmd, out chan<- string) {
bout, _ := c.CombinedOutput()
out <- string(bout)
err = utils.DeleteFile(utils.Directory + "/statping.db.backup")
require.Nil(t, err)
}

View File

@ -50,6 +50,22 @@ var sassCmd = &cobra.Command{
},
}
var envCmd = &cobra.Command{
Use: "env",
Short: "Return the configs that will be ran",
RunE: func(cmd *cobra.Command, args []string) error {
return envCli()
},
}
var resetCmd = &cobra.Command{
Use: "reset",
Short: "Start a fresh copy of Statping",
RunE: func(cmd *cobra.Command, args []string) error {
return resetCli()
},
}
var onceCmd = &cobra.Command{
Use: "once",
Short: "Check all services 1 time and then quit",

View File

@ -32,14 +32,16 @@ var (
func init() {
core.New(VERSION)
utils.InitCLI()
parseFlags(rootCmd)
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(assetsCmd)
rootCmd.AddCommand(exportCmd)
rootCmd.AddCommand(importCmd)
rootCmd.AddCommand(sassCmd)
rootCmd.AddCommand(onceCmd)
rootCmd.AddCommand(envCmd)
rootCmd.AddCommand(resetCmd)
utils.InitCLI()
parseFlags(rootCmd)
}
// exit will return an error and return an exit code 1 due to this error

View File

@ -92,6 +92,7 @@ type Database interface {
// extra
Error() error
Status() int
RowsAffected() int64
Since(time.Time) Database
@ -504,6 +505,34 @@ func (it *Db) Error() error {
return it.Database.Error
}
func (it *Db) Status() int {
switch it.Database.Error {
case gorm.ErrRecordNotFound:
return 404
case gorm.ErrCantStartTransaction:
return 422
case gorm.ErrInvalidSQL:
return 500
case gorm.ErrUnaddressable:
return 500
default:
return 500
}
}
func (it *Db) Loggable() bool {
switch it.Database.Error {
case gorm.ErrCantStartTransaction:
return true
case gorm.ErrInvalidSQL:
return true
case gorm.ErrUnaddressable:
return true
default:
return false
}
}
func (it *Db) Since(ago time.Time) Database {
return it.Where("created_at > ?", it.FormatTime(ago))
}

View File

@ -1,11 +1,10 @@
package handlers
import (
"encoding/json"
"errors"
"fmt"
"github.com/statping/statping/types/checkins"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/groups"
"github.com/statping/statping/types/incidents"
"github.com/statping/statping/types/messages"
@ -22,7 +21,7 @@ type apiResponse struct {
Status string `json:"status"`
Object string `json:"type,omitempty"`
Method string `json:"method,omitempty"`
Error string `json:"error,omitempty"`
Error error `json:"error,omitempty"`
Id int64 `json:"id,omitempty"`
Output interface{} `json:"output,omitempty"`
}
@ -54,8 +53,7 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
var c *core.Core
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&c)
err := DecodeJSON(r, &c)
if err != nil {
sendErrorJson(err, w, r)
return
@ -111,17 +109,18 @@ func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
returnJson(output, w, r)
}
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request, statusCode ...int) {
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
errCode := 0
e, ok := err.(errors.Error)
if ok {
errCode = e.Status()
}
log.WithField("url", r.URL.String()).
WithField("method", r.Method).
WithField("code", statusCode).
WithField("code", errCode).
Errorln(fmt.Errorf("sending error response for %s: %s", r.URL.String(), err.Error()))
output := apiResponse{
Status: "error",
Error: err.Error(),
}
returnJson(output, w, r, statusCode...)
returnJson(err, w, r)
}
func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *http.Request) {
@ -173,11 +172,6 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
}
func sendUnauthorizedJson(w http.ResponseWriter, r *http.Request) {
output := apiResponse{
Status: "error",
Error: errors.New("not authorized").Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
returnJson(output, w, r)
returnJson(errors.NotAuthenticated, w, r)
}

View File

@ -106,7 +106,7 @@ func TestSetupRoutes(t *testing.T) {
}
func TestMainApiRoutes(t *testing.T) {
date := utils.Now().Format("2006-01-02")
date := utils.Now().Format("2006-01")
tests := []HTTPTest{
{
Name: "Statping Details",

View File

@ -1,10 +1,10 @@
package handlers
import (
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/statping/statping/types/checkins"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"net"
@ -13,14 +13,15 @@ import (
func findCheckin(r *http.Request) (*checkins.Checkin, string, error) {
vars := mux.Vars(r)
if vars["api"] == "" {
return nil, "", errors.New("missing checkin API in URL")
id := vars["api"]
if id == "" {
return nil, "", errors.IDMissing
}
checkin, err := checkins.FindByAPI(vars["api"])
checkin, err := checkins.FindByAPI(id)
if err != nil {
return nil, vars["api"], err
return nil, id, errors.Missing(checkins.Checkin{}, id)
}
return checkin, vars["api"], nil
return checkin, id, nil
}
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
@ -29,9 +30,9 @@ func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
}
func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
checkin, id, err := findCheckin(r)
checkin, _, err := findCheckin(r)
if err != nil {
sendErrorJson(fmt.Errorf("checkin %v was not found", id), w, r)
sendErrorJson(err, w, r)
return
}
returnJson(checkin, w, r)

View File

@ -73,6 +73,20 @@ func TestApiCheckinRoutes(t *testing.T) {
BeforeTest: SetTestENV,
Skip: true,
},
{
Name: "Statping Missing Trigger Checkin",
URL: "/checkin/" + apiToken,
Method: "GET",
BeforeTest: SetTestENV,
ExpectedStatus: 404,
},
{
Name: "Statping Missing Checkin",
URL: "/api/checkins/missing123",
Method: "GET",
BeforeTest: SetTestENV,
ExpectedStatus: 404,
},
}
for _, v := range tests {

View File

@ -2,7 +2,7 @@ package handlers
import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/groups"
"github.com/statping/statping/utils"
"net/http"
@ -10,14 +10,22 @@ import (
func findGroup(r *http.Request) (*groups.Group, error) {
vars := mux.Vars(r)
if utils.NotNumber(vars["id"]) {
return nil, errors.NotNumber
}
id := utils.ToInt(vars["id"])
if id == 0 {
return nil, errors.New("missing group id")
return nil, errors.IDMissing
}
g, err := groups.Find(id)
if err != nil {
return nil, err
}
if !g.Public.Bool {
if !IsReadAuthenticated(r) {
return nil, errors.NotAuthenticated
}
}
return g, nil
}
@ -31,7 +39,7 @@ func apiAllGroupHandler(r *http.Request) interface{} {
func apiGroupHandler(w http.ResponseWriter, r *http.Request) {
group, err := findGroup(r)
if err != nil {
sendErrorJson(errors.Wrap(err, "group not found"), w, r, http.StatusNotFound)
sendErrorJson(err, w, r)
return
}
returnJson(group, w, r)
@ -41,8 +49,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request) {
func apiGroupUpdateHandler(w http.ResponseWriter, r *http.Request) {
group, err := findGroup(r)
if err != nil {
w.WriteHeader(http.StatusNotFound)
sendErrorJson(errors.Wrap(err, "group not found"), w, r)
sendErrorJson(err, w, r)
return
}
@ -79,7 +86,7 @@ func apiCreateGroupHandler(w http.ResponseWriter, r *http.Request) {
func apiGroupDeleteHandler(w http.ResponseWriter, r *http.Request) {
group, err := findGroup(r)
if err != nil {
sendErrorJson(errors.Wrap(err, "group not found"), w, r)
sendErrorJson(err, w, r)
return
}

View File

@ -4,10 +4,10 @@ import (
"crypto/subtle"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/errors"
"html/template"
"net/http"
"os"
@ -248,12 +248,14 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
}
}
func returnJson(d interface{}, w http.ResponseWriter, r *http.Request, statusCode ...int) {
func returnJson(d interface{}, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if len(statusCode) != 0 {
code := statusCode[0]
w.WriteHeader(code)
if e, ok := d.(errors.Error); ok {
w.WriteHeader(e.Status())
json.NewEncoder(w).Encode(e)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(d)
}
@ -263,5 +265,5 @@ func error404Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
}
w.WriteHeader(http.StatusNotFound)
ExecuteResponse(w, r, "index.html", nil, nil)
ExecuteResponse(w, r, "base.gohtml", nil, nil)
}

View File

@ -2,7 +2,7 @@ package handlers
import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/incidents"
"github.com/statping/statping/utils"
"net/http"
@ -10,13 +10,16 @@ import (
func findIncident(r *http.Request) (*incidents.Incident, int64, error) {
vars := mux.Vars(r)
if utils.NotNumber(vars["id"]) {
return nil, 0, errors.NotNumber
}
id := utils.ToInt(vars["id"])
if id == 0 {
return nil, id, errors.New("missing checkin API in URL")
return nil, id, errors.IDMissing
}
checkin, err := incidents.Find(id)
if err != nil {
return nil, id, err
return nil, id, errors.Missing(&incidents.Incident{}, id)
}
return checkin, id, nil
}

View File

@ -1,8 +1,8 @@
package handlers
import (
"fmt"
"github.com/gorilla/mux"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/messages"
"github.com/statping/statping/utils"
"net/http"
@ -10,12 +10,15 @@ import (
func findMessage(r *http.Request) (*messages.Message, int64, error) {
vars := mux.Vars(r)
num := utils.ToInt(vars["id"])
message, err := messages.Find(num)
if err != nil {
return nil, num, err
if utils.NotNumber(vars["id"]) {
return nil, 0, errors.NotNumber
}
return message, num, nil
id := utils.ToInt(vars["id"])
message, err := messages.Find(id)
if err != nil {
return nil, id, err
}
return message, id, nil
}
func apiAllMessagesHandler(r *http.Request) interface{} {
@ -37,17 +40,17 @@ func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
}
func apiMessageGetHandler(r *http.Request) interface{} {
message, id, err := findMessage(r)
message, _, err := findMessage(r)
if err != nil {
return fmt.Errorf("message #%d was not found", id)
return err
}
return message
}
func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
message, id, err := findMessage(r)
message, _, err := findMessage(r)
if err != nil {
sendErrorJson(fmt.Errorf("message #%d was not found", id), w, r)
sendErrorJson(err, w, r)
return
}
err = message.Delete()
@ -59,9 +62,9 @@ func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
}
func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
message, id, err := findMessage(r)
message, _, err := findMessage(r)
if err != nil {
sendErrorJson(fmt.Errorf("message #%d was not found", id), w, r)
sendErrorJson(err, w, r)
return
}
if err := DecodeJSON(r, &message); err != nil {

View File

@ -29,7 +29,7 @@ func TestMessagesApiRoutes(t *testing.T) {
"notify_before_scale": "hour"
}`,
ExpectedStatus: 200,
ExpectedContains: []string{`"status":"success"`, `"type":"message"`, `"method":"create"`, `"title":"API Message"`},
ExpectedContains: []string{Success, `"type":"message"`, `"method":"create"`, `"title":"API Message"`},
BeforeTest: SetTestENV,
AfterTest: UnsetTestENV,
SecureRoute: true,
@ -40,7 +40,8 @@ func TestMessagesApiRoutes(t *testing.T) {
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`"title":"Routine Downtime"`},
}, {
},
{
Name: "Statping Update Message",
URL: "/api/messages/1",
Method: "POST",
@ -68,7 +69,14 @@ func TestMessagesApiRoutes(t *testing.T) {
ExpectedContains: []string{`"status":"success"`, `"method":"delete"`},
BeforeTest: SetTestENV,
SecureRoute: true,
}}
},
{
Name: "Statping Missing Message",
URL: "/api/messages/999999",
Method: "GET",
ExpectedStatus: 404,
},
}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {

View File

@ -4,9 +4,9 @@ import (
"compress/gzip"
"crypto/subtle"
"encoding/json"
"errors"
"fmt"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/utils"
"io"
"net/http"
@ -167,7 +167,7 @@ func DecodeJSON(r *http.Request, obj interface{}) error {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&obj)
if err != nil {
return err
return errors.DecodeJSON
}
defer r.Body.Close()
return nil

View File

@ -2,8 +2,8 @@ package handlers
import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/statping/statping/database"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/hits"
"github.com/statping/statping/types/services"
@ -21,7 +21,11 @@ func findService(r *http.Request) (*services.Service, error) {
id := utils.ToInt(vars["id"])
servicer, err := services.Find(id)
if err != nil {
return nil, errors.Errorf("service %d not found", id)
return nil, err
}
user := IsUser(r)
if !servicer.Public.Bool && !user {
return nil, errors.NotAuthenticated
}
return servicer, nil
}
@ -37,7 +41,7 @@ func reorderServiceHandler(w http.ResponseWriter, r *http.Request) {
for _, s := range newOrder {
service, err := services.Find(s.Id)
if err != nil {
sendErrorJson(errors.Errorf("service %d not found", s.Id), w, r)
sendErrorJson(err, w, r)
return
}
service.Order = s.Order
@ -51,10 +55,6 @@ func apiServiceHandler(r *http.Request) interface{} {
if err != nil {
return err
}
user := IsUser(r)
if !srv.Public.Bool && !user {
return errors.New("not authenticated")
}
srv = srv.UpdateStats()
return *srv
}
@ -78,11 +78,11 @@ func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
service, err := findService(r)
if err != nil {
sendErrorJson(err, w, r, http.StatusNotFound)
sendErrorJson(err, w, r)
return
}
if err := DecodeJSON(r, &service); err != nil {
sendErrorJson(err, w, r, http.StatusBadRequest)
sendErrorJson(err, w, r)
return
}
@ -110,10 +110,9 @@ func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) {
}
func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service, err := services.Find(utils.ToInt(vars["id"]))
service, err := findService(r)
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
sendErrorJson(err, w, r)
return
}
@ -132,10 +131,9 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
}
func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service, err := services.Find(utils.ToInt(vars["id"]))
service, err := findService(r)
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
sendErrorJson(err, w, r)
return
}
@ -157,7 +155,7 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
service, err := findService(r)
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
sendErrorJson(err, w, r)
return
}
@ -179,7 +177,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
func apiServiceTimeDataHandler(w http.ResponseWriter, r *http.Request) {
service, err := findService(r)
if err != nil {
sendErrorJson(errors.New("service data not found"), w, r)
sendErrorJson(err, w, r)
return
}
@ -258,12 +256,10 @@ func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
}
func apiServiceFailuresHandler(r *http.Request) interface{} {
vars := mux.Vars(r)
service, err := services.Find(utils.ToInt(vars["id"]))
service, err := findService(r)
if err != nil {
return errors.New("service not found")
return err
}
var fails []*failures.Failure
query, err := database.ParseQueries(r, service.AllFailures())
if err != nil {
@ -274,12 +270,10 @@ func apiServiceFailuresHandler(r *http.Request) interface{} {
}
func apiServiceHitsHandler(r *http.Request) interface{} {
vars := mux.Vars(r)
service, err := services.Find(utils.ToInt(vars["id"]))
service, err := findService(r)
if err != nil {
return errors.New("service not found")
return err
}
var hts []*hits.Hit
query, err := database.ParseQueries(r, service.AllHits())
if err != nil {

View File

@ -60,13 +60,20 @@ func TestApiServiceRoutes(t *testing.T) {
BeforeTest: UnsetTestENV,
},
{
Name: "Statping Private Service 1",
Name: "Statping Private Service 6",
URL: "/api/services/6",
Method: "GET",
ExpectedContains: []string{`"error":"not authenticated"`},
ExpectedStatus: 200,
ExpectedContains: []string{`"error":"user not authenticated"`},
ExpectedStatus: 401,
BeforeTest: UnsetTestENV,
},
{
Name: "Statping Authenticated Private Service 6",
URL: "/api/services/6",
Method: "GET",
ExpectedStatus: 200,
BeforeTest: SetTestENV,
},
{
Name: "Statping Service 1 with Private responses",
URL: "/api/services/1",
@ -122,14 +129,14 @@ func TestApiServiceRoutes(t *testing.T) {
URL: "/api/services/1/failure_data" + startEndQuery + "&group=24h",
Method: "GET",
ExpectedStatus: 200,
GreaterThan: 4,
GreaterThan: 3,
},
{
Name: "Statping Service 1 Failure Data - 12 Hour",
URL: "/api/services/1/failure_data" + startEndQuery + "&group=12h",
Method: "GET",
ExpectedStatus: 200,
GreaterThan: 7,
GreaterThan: 6,
},
{
Name: "Statping Service 1 Failure Data - 1 Hour",
@ -162,7 +169,7 @@ func TestApiServiceRoutes(t *testing.T) {
if err := json.Unmarshal(resp, &uptime); err != nil {
return err
}
assert.GreaterOrEqual(t, uptime.Uptime, int64(259100000))
assert.GreaterOrEqual(t, uptime.Uptime, int64(200000000))
return nil
},
},

View File

@ -1,9 +1,9 @@
package handlers
import (
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/users"
"github.com/statping/statping/utils"
"net/http"
@ -11,10 +11,13 @@ import (
func findUser(r *http.Request) (*users.User, int64, error) {
vars := mux.Vars(r)
if utils.NotNumber(vars["id"]) {
return nil, 0, errors.NotNumber
}
num := utils.ToInt(vars["id"])
user, err := users.Find(num)
if err != nil {
return nil, num, err
return nil, num, errors.Missing(&users.User{}, num)
}
return user, num, nil
}
@ -22,7 +25,7 @@ func findUser(r *http.Request) (*users.User, int64, error) {
func apiUserHandler(w http.ResponseWriter, r *http.Request) {
user, _, err := findUser(r)
if err != nil {
sendErrorJson(err, w, r, http.StatusNotFound)
sendErrorJson(err, w, r)
return
}
user.Password = ""
@ -30,15 +33,15 @@ func apiUserHandler(w http.ResponseWriter, r *http.Request) {
}
func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
user, id, err := findUser(r)
user, _, err := findUser(r)
if err != nil {
sendErrorJson(fmt.Errorf("user #%d was not found", id), w, r)
sendErrorJson(err, w, r)
return
}
err = DecodeJSON(r, &user)
if err != nil {
sendErrorJson(fmt.Errorf("user #%d was not found", id), w, r)
sendErrorJson(err, w, r)
return
}

View File

@ -42,7 +42,7 @@ func scssRendered(name string) string {
// CompileSASS will attempt to compile the SASS files into CSS
func CompileSASS(files ...string) error {
sassBin := utils.Getenv("SASS", "sass").(string)
sassBin := utils.Params.GetString("SASS")
for _, file := range files {
scssFile := fmt.Sprintf("%v/assets/%v", utils.Directory, file)

View File

@ -16,6 +16,9 @@ func (c *Checkin) Expected() time.Duration {
func (c *Checkin) Period() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%ds", c.Interval))
if duration.Seconds() <= 15 {
return 15 * time.Second
}
return duration
}

38
types/errors/common.go Normal file
View File

@ -0,0 +1,38 @@
package errors
import (
"fmt"
"strings"
)
var (
NotAuthenticated = &appError{
Err: "user not authenticated",
Code: 401,
}
DecodeJSON = &appError{
Err: "could not decode incoming JSON",
Code: 422,
}
IDMissing = &appError{
Err: "ID missing in URL",
Code: 422,
}
NotNumber = &appError{
Err: "ID needs to be an integer",
Code: 422,
}
)
func Missing(object interface{}, id interface{}) error {
outErr := fmt.Errorf("%s with id %v was not found", splitVar(object), id)
return &appError{
Err: outErr.Error(),
Code: 404,
}
}
func splitVar(val interface{}) string {
s := strings.Split(fmt.Sprintf("%T", val), ".")
return strings.ToLower(s[len(s)-1])
}

48
types/errors/struct.go Normal file
View File

@ -0,0 +1,48 @@
package errors
import (
"github.com/pkg/errors"
)
type appError struct {
Err string `json:"error"`
Code int `json:"-"`
DbCode int `json:"code,omitempty"`
Id int64 `json:"id,omitempty"`
loggable bool `json:"-"`
}
type Error interface {
Error() string
Status() int
}
func New(err string) Error {
return &appError{
Err: err,
}
}
func Err(err Error) Error {
return &appError{
Err: err.Error(),
Code: err.Status(),
}
}
func Wrap(err error, message string) Error {
return &appError{
Err: errors.Wrap(err, message).Error(),
}
}
func (e *appError) Error() string {
return e.Err
}
func (e appError) Status() int {
if e.Code == 0 {
return 200
}
return e.Code
}

View File

@ -2,6 +2,7 @@ package groups
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/errors"
"sort"
)
@ -14,6 +15,9 @@ func SetDB(database database.Database) {
func Find(id int64) (*Group, error) {
var group Group
q := db.Where("id = ?", id).Find(&group)
if q.Error() != nil {
return nil, errors.Missing(group, id)
}
return &group, q.Error()
}

View File

@ -1,6 +1,9 @@
package messages
import "github.com/statping/statping/database"
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/errors"
)
var db database.Database
@ -11,6 +14,9 @@ func SetDB(database database.Database) {
func Find(id int64) (*Message, error) {
var message Message
q := db.Where("id = ?", id).Find(&message)
if q.Error() != nil {
return nil, errors.Missing(message, id)
}
return &message, q.Error()
}

View File

@ -1,9 +1,9 @@
package services
import (
"errors"
"fmt"
"github.com/statping/statping/database"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/utils"
"sort"
)
@ -20,7 +20,7 @@ func SetDB(database database.Database) {
func Find(id int64) (*Service, error) {
srv := allServices[id]
if srv == nil {
return nil, errors.New("service not found")
return nil, errors.Missing(&Service{}, id)
}
return srv, nil
}

View File

@ -1,7 +1,6 @@
package utils
import (
"github.com/prometheus/common/log"
"github.com/spf13/viper"
"os"
"time"
@ -13,35 +12,34 @@ var (
func InitCLI() {
Params = viper.New()
Params.AutomaticEnv()
Directory = Params.GetString("STATPING_DIR")
//Params.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
setDefaults()
Params.SetConfigName("config")
Params.SetConfigType("yml")
Params.AddConfigPath(".")
err := Params.ReadInConfig()
if err != nil {
log.Debugf("config.yml Fatal error config file: %s", err)
}
Params.AddConfigPath(Directory)
Params.AddConfigPath(".")
Params.ReadInConfig()
Params.AddConfigPath(Directory)
Params.SetConfigFile(".env")
err = Params.ReadInConfig()
if err != nil {
log.Debugf(".env Fatal error config file: %s", err)
}
Params.ReadInConfig()
Params.AutomaticEnv()
if err != nil {
log.Debugf("No environment variables found: %s", err)
}
Params.Set("VERSION", version)
}
func setDefaults() {
defaultDir, err := os.Getwd()
if err != nil {
defaultDir = "."
if Directory == "" {
defaultDir, err := os.Getwd()
if err != nil {
defaultDir = "."
}
Params.SetDefault("STATPING_DIR", defaultDir)
Directory = defaultDir
}
Params.SetDefault("STATPING_DIR", defaultDir)
Directory = Params.GetString("STATPING_DIR")
Params.SetDefault("STATPING_DIR", Directory)
Params.SetDefault("GO_ENV", "")
Params.SetDefault("DISABLE_LOGS", false)
Params.SetDefault("BASE_PATH", "")
@ -51,9 +49,8 @@ func setDefaults() {
Params.SetDefault("SAMPLE_DATA", true)
Params.SetDefault("USE_CDN", false)
Params.SetDefault("ALLOW_REPORTS", false)
Params.SetDefault("AUTH_USERNAME", "")
Params.SetDefault("AUTH_PASSWORD", "")
Params.SetDefault("POSTGRES_SSLMODE", "disable")
Params.SetDefault("SASS", "sass")
dbConn := Params.GetString("DB_CONN")
dbInt := Params.GetInt("DB_PORT")

View File

@ -24,7 +24,7 @@ func CreateDirectory(directory string) error {
// FolderExists will return true if the folder exists
func FolderExists(folder string) bool {
if _, err := os.Stat(folder); os.IsExist(err) {
if stat, err := os.Stat(folder); err == nil && stat.IsDir() {
return true
}
return false
@ -45,13 +45,13 @@ func FileExists(name string) bool {
// DeleteFile will attempt to delete a file
// DeleteFile("newfile.json")
func DeleteFile(file string) error {
Log.Debugln("deleting file: " + file)
Log.Warn("deleting file: " + file)
return os.Remove(file)
}
// RenameDirectory will attempt rename a directory to a new name
func RenameDirectory(fromDir string, toDir string) error {
Log.Debugln("renaming directory: " + fromDir + "to: " + toDir)
Log.Warn("renaming directory: " + fromDir + "to: " + toDir)
return os.Rename(fromDir, toDir)
}

View File

@ -42,6 +42,11 @@ type env struct {
data interface{}
}
func NotNumber(val string) bool {
_, err := strconv.ParseInt(val, 10, 64)
return err != nil
}
func GetenvAs(key string, defaultValue interface{}) *env {
return &env{
data: Getenv(key, defaultValue),

View File

@ -201,6 +201,5 @@ func TestConfigLoad(t *testing.T) {
assert.Equal(t, "sqlite", s("DB_CONN"))
assert.Equal(t, Directory, s("STATPING_DIR"))
assert.True(t, b("SAMPLE_DATA"))
assert.False(t, b("DISABLE_LOGS"))
assert.False(t, b("ALLOW_REPORTS"))
}