Merge pull request #12 from cloudflare/tests

Collection of a small fixes to improve testing
This commit is contained in:
Łukasz Mierzwa
2017-03-28 12:38:59 -07:00
committed by GitHub
8 changed files with 210 additions and 14 deletions

View File

@@ -35,9 +35,19 @@ To build and start `unsee` from local branch see `Running` section of the
[README](README.md) file.
When working with assets (templates, stylesheets and javascript files) `DEBUG`
flag for make can be set, which will recompile binary assets in debug mode,
make variable can be set, which will recompile binary assets in debug mode,
meaning that files from disk will be read instead of compiled in assets.
See [go-bindata docs](https://github.com/jteeuwen/go-bindata#debug-vs-release-builds)
for details. Example:
make DEBUG=true run
make DEBUG=true run-docker
Note that this is not the same as enabling [debug mode](/README.md#debug) for
the [gin web framework](https://github.com/gin-gonic/gin) which is used
internally, but enabling `DEBUG` via this make variable will also enable gin
debug mode.
When running docker image via `make run-docker` with `DEBUG` make variable set
to `true` volume mapping will be added (in read-only mode), so that unsee
instance running inside the docker can read asset files from the sources
directory.

View File

@@ -10,9 +10,12 @@ SOURCES := $(wildcard *.go) $(wildcard */*.go)
ASSET_SOURCES := $(wildcard assets/*/* assets/*/*/*)
GO_BINDATA_MODE := prod
GIN_DEBUG := false
ifdef DEBUG
GO_BINDATA_FLAGS = -debug
GO_BINDATA_MODE = debug
GIN_DEBUG = true
DOCKER_ARGS = -v $(CURDIR)/assets:$(CURDIR)/assets:ro
endif
.DEFAULT_GOAL := $(NAME)
@@ -42,10 +45,10 @@ clean:
.PHONY: run
run: $(NAME)
DEBUG=true \
ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
COLOR_LABELS_UNIQUE="instance cluster" \
COLOR_LABELS_STATIC="job" \
DEBUG="$(GIN_DEBUG)" \
PORT=$(PORT) \
./$(NAME)
@@ -58,9 +61,11 @@ run-docker: docker-image
@docker rm -f $(NAME) || true
docker run \
--name $(NAME) \
$(DOCKER_ARGS) \
-e ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
-e COLOR_LABELS_UNIQUE="instance cluster" \
-e COLOR_LABELS_STATIC="job" \
-e DEBUG="$(GIN_DEBUG)" \
-e PORT=$(PORT) \
-p $(PORT):$(PORT) \
$(NAME):$(VERSION)

View File

@@ -117,7 +117,9 @@ This variable is required and there is no default value.
#### DEBUG
Will enable [gin](https://github.com/gin-gonic/gin) debug mode. Examples:
Will enable [gin](https://github.com/gin-gonic/gin) debug mode. This will
configure to print out more debugging information on startup.
Examples:
DEBUG=true
DEBUG=false

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"flag"
"fmt"
"net/url"
"os"
"reflect"
"strings"
@@ -11,6 +12,7 @@ import (
"unicode"
log "github.com/Sirupsen/logrus"
"github.com/asaskevich/govalidator"
"github.com/kelseyhightower/envconfig"
)
@@ -119,12 +121,35 @@ func (config *configEnvs) Read() {
if err != nil {
log.Fatal(err)
}
}
func hideURLPassword(s string) (string, error) {
u, err := url.Parse(s)
if err != nil {
return "", err
}
if u.User != nil {
if _, pwdSet := u.User.Password(); pwdSet {
u.User = url.UserPassword(u.User.Username(), "xxx")
}
}
return u.String(), nil
}
func (config *configEnvs) LogValues() {
s := reflect.ValueOf(config).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
log.Infof("%20s => %v", typeOfT.Field(i).Tag.Get("envconfig"), f.Interface())
env := typeOfT.Field(i).Tag.Get("envconfig")
val := fmt.Sprintf("%v", s.Field(i).Interface())
if govalidator.IsURL(val) {
var err error
val, err = hideURLPassword(val)
if err != nil {
log.Errorf("Failed to parse url value for %s: %s", env, err.Error())
}
}
log.Infof("%20s => %v", env, val)
}
}

99
config/config_test.go Normal file
View File

@@ -0,0 +1,99 @@
package config
import (
"os"
"testing"
"time"
)
type flagNameTest struct {
env string
flag string
}
var flagNameTests = []flagNameTest{
flagNameTest{env: "MyEnv", flag: "my.env"},
flagNameTest{env: "MyENV", flag: "my.env"},
flagNameTest{env: "MYEnv", flag: "myenv"},
}
func TestMakeFlagName(t *testing.T) {
for _, testCase := range flagNameTests {
generatedFlag := makeFlagName(testCase.env)
if generatedFlag != testCase.flag {
t.Errorf("Invalid flag name generated from env '%s', expected '%s', got '%s'", testCase.env, testCase.flag, generatedFlag)
}
}
}
func stringInSlice(stringArray []string, value string) bool {
for _, s := range stringArray {
if s == value {
return true
}
}
return false
}
func TestReadConfig(t *testing.T) {
os.Setenv("ALERTMANAGER_TTL", "1s")
os.Setenv("ALERTMANAGER_URI", "http://localhost")
os.Setenv("DEBUG", "true")
os.Setenv("COLOR_LABELS_STATIC", "a bb ccc")
Config.Read()
if Config.AlertmanagerTTL != time.Second {
t.Errorf("Config.AlertmanagerTTL is invalid, expected 1s, got %v", Config.AlertmanagerTTL)
}
if Config.Debug != true {
t.Errorf("Config.Debug is %v with env DEBUG=true set", Config.Debug)
}
if !stringInSlice(Config.ColorLabelsStatic, "a") {
t.Errorf("Config.ColorLabelsStatic is missing value 'a': %v", Config.ColorLabelsStatic)
}
if !stringInSlice(Config.ColorLabelsStatic, "bb") {
t.Errorf("Config.ColorLabelsStatic is missing value 'bb': %v", Config.ColorLabelsStatic)
}
if !stringInSlice(Config.ColorLabelsStatic, "ccc") {
t.Errorf("Config.ColorLabelsStatic is missing value 'ccc': %v", Config.ColorLabelsStatic)
}
if Config.Port != 8080 {
t.Errorf("Config.Port is invalid, expected 8080, got %v", Config.Port)
}
}
type urlSecretTest struct {
raw string
sanitized string
}
var urlSecretTests = []urlSecretTest{
urlSecretTest{
raw: "http://localhost",
sanitized: "http://localhost",
},
urlSecretTest{
raw: "http://alertmanager.example.com/path",
sanitized: "http://alertmanager.example.com/path",
},
urlSecretTest{
raw: "http://user@alertmanager.example.com/path",
sanitized: "http://user@alertmanager.example.com/path",
},
urlSecretTest{
raw: "https://user:password@alertmanager.example.com/path",
sanitized: "https://user:xxx@alertmanager.example.com/path",
},
}
func TestUrlSecretTest(t *testing.T) {
for _, testCase := range urlSecretTests {
sanitized, err := hideURLPassword(testCase.raw)
if err != nil {
t.Errorf("Unexpected error when parsing '%s': %s", testCase.raw, err.Error())
}
if sanitized != testCase.sanitized {
t.Errorf("Invalid sanitized url, expected '%s', got '%s'", testCase.sanitized, sanitized)
}
}
}

View File

@@ -70,8 +70,6 @@ func init() {
}
func setupRouter(router *gin.Engine) {
router.SetHTMLTemplate(loadTemplates("templates"))
router.Use(static.Serve("/static", newBinaryFileSystem("static")))
router.GET("/favicon.ico", favicon)
@@ -85,6 +83,7 @@ func main() {
log.Infof("Version: %s", version)
config.Config.Read()
config.Config.LogValues()
transform.ParseRules(config.Config.JiraRegexp)
apiCache = cache.New(cache.NoExpiration, 10*time.Second)
@@ -106,7 +105,7 @@ func main() {
}
router := gin.New()
setupRouter(router)
router.SetHTMLTemplate(loadTemplates("templates"))
prom := ginprometheus.NewPrometheus("gin")
prom.Use(router)
@@ -120,5 +119,6 @@ func main() {
router.Use(sentry.Recovery(raven.DefaultClient, false))
}
setupRouter(router)
router.Run()
}

View File

@@ -4,9 +4,6 @@ import (
"crypto/sha1"
"encoding/json"
"fmt"
"github.com/cloudflare/unsee/config"
"github.com/cloudflare/unsee/models"
"github.com/cloudflare/unsee/store"
"io"
"net/http"
"sort"
@@ -14,10 +11,19 @@ import (
"strings"
"time"
"github.com/cloudflare/unsee/config"
"github.com/cloudflare/unsee/models"
"github.com/cloudflare/unsee/store"
log "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)
var (
// needed for serving favicon from binary assets
faviconFileServer = http.FileServer(newBinaryFileSystem("static"))
)
func boolInSlice(boolArray []bool, value bool) bool {
for _, s := range boolArray {
if s == value {
@@ -262,7 +268,5 @@ func autocomplete(c *gin.Context) {
}
func favicon(c *gin.Context) {
fs := newBinaryFileSystem("static")
fileserver := http.FileServer(fs)
fileserver.ServeHTTP(c.Writer, c.Request)
faviconFileServer.ServeHTTP(c.Writer, c.Request)
}

View File

@@ -29,6 +29,7 @@ func mockConfig() {
func ginTestEngine() *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
r.SetHTMLTemplate(loadTemplates("templates"))
setupRouter(r)
return r
}
@@ -256,6 +257,14 @@ var acTests = []acTestCase{
"node=localhost",
},
},
// duplicated to test reponse caching
acTestCase{
Term: "Nod",
Results: []string{
"node!=localhost",
"node=localhost",
},
},
}
func TestAutocomplete(t *testing.T) {
@@ -294,3 +303,45 @@ func TestAutocomplete(t *testing.T) {
}
}
}
type staticFileTestCase struct {
path string
code int
}
var staticFileTests = []staticFileTestCase{
staticFileTestCase{
path: "/favicon.ico",
code: 200,
},
staticFileTestCase{
path: "/static/unsee.js",
code: 200,
},
staticFileTestCase{
path: "/static/managed/js/assets.txt",
code: 200,
},
staticFileTestCase{
path: "/xxx",
code: 404,
},
staticFileTestCase{
path: "/static/abcd",
code: 404,
},
}
func TestStaticFiles(t *testing.T) {
mockConfig()
mockAlerts()
r := ginTestEngine()
for _, staticFileTest := range staticFileTests {
req, _ := http.NewRequest("GET", staticFileTest.path, nil)
resp := httptest.NewRecorder()
r.ServeHTTP(resp, req)
if resp.Code != staticFileTest.code {
t.Errorf("Invalid status code for GET %s: %d", staticFileTest.path, resp.Code)
}
}
}