mirror of
https://github.com/prymitive/karma
synced 2026-05-21 04:33:07 +00:00
Merge pull request #12 from cloudflare/tests
Collection of a small fixes to improve testing
This commit is contained in:
@@ -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.
|
||||
|
||||
7
Makefile
7
Makefile
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
99
config/config_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
6
main.go
6
main.go
@@ -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()
|
||||
}
|
||||
|
||||
16
views.go
16
views.go
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user