Starts a refactor (#898)
This commit is contained in:
130
web/__snapshots__/web.snapshot
Normal file
130
web/__snapshots__/web.snapshot
Normal file
@@ -0,0 +1,130 @@
|
||||
/* snapshot: Test_createRoutes_foobar */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self' api.github.com; require-trusted-types-for 'script'
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
foo page
|
||||
|
||||
/* snapshot: Test_createRoutes_index */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self' api.github.com; require-trusted-types-for 'script'
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
index page
|
||||
|
||||
/* snapshot: Test_createRoutes_redirect */
|
||||
HTTP/1.1 301 Moved Permanently
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self' api.github.com; require-trusted-types-for 'script'
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Location: /foobar/
|
||||
|
||||
<a href="/foobar/">Moved Permanently</a>.
|
||||
|
||||
/* snapshot: Test_createRoutes_version */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self' api.github.com; require-trusted-types-for 'script'
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
dev
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_error */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: containers-changed
|
||||
data: []
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_error_request */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: containers-changed
|
||||
data: []
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_happy */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: containers-changed
|
||||
data: []
|
||||
|
||||
|
||||
event: containers-changed
|
||||
data: []
|
||||
|
||||
|
||||
event: container-start
|
||||
data: {"actorId":"1234","name":"start"}
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_error_finding_container */
|
||||
HTTP/1.1 404 Not Found
|
||||
Connection: close
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
X-Content-Type-Options: nosniff
|
||||
|
||||
error finding container
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_error_reading */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_happy */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
data: INFO Testing logs...
|
||||
|
||||
event: container-stopped
|
||||
data: end of stream
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_happy_container_stopped */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: container-stopped
|
||||
data: end of stream
|
||||
|
||||
event: container-stopped
|
||||
data: end of stream
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_happy_with_id */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
data: 2020-05-13T18:55:37.772853839Z INFO Testing logs...
|
||||
id: 2020-05-13T18:55:37.772853839Z
|
||||
|
||||
event: container-stopped
|
||||
data: end of stream
|
||||
276
web/routes.go
Normal file
276
web/routes.go
Normal file
@@ -0,0 +1,276 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Config is a struct for configuring the web service
|
||||
type Config struct {
|
||||
Base string
|
||||
Addr string
|
||||
Version string
|
||||
TailSize int
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
client docker.Client
|
||||
box packr.Box
|
||||
config *Config
|
||||
}
|
||||
|
||||
// CreateServer creates a service for http handler
|
||||
func CreateServer(c docker.Client, b packr.Box, config Config) *http.Server {
|
||||
handler := &handler{
|
||||
client: c,
|
||||
box: b,
|
||||
config: &config,
|
||||
}
|
||||
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}
|
||||
}
|
||||
|
||||
func createRouter(h *handler) *mux.Router {
|
||||
base := h.config.Base
|
||||
r := mux.NewRouter()
|
||||
r.Use(setCSPHeaders)
|
||||
if base != "/" {
|
||||
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
|
||||
}))
|
||||
}
|
||||
s := r.PathPrefix(base).Subrouter()
|
||||
s.HandleFunc("/api/logs/stream", h.streamLogs)
|
||||
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
|
||||
s.HandleFunc("/api/events/stream", h.streamEvents)
|
||||
s.HandleFunc("/version", h.version)
|
||||
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
|
||||
return r
|
||||
}
|
||||
|
||||
func setCSPHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self' api.github.com; require-trusted-types-for 'script'")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
fileServer := http.FileServer(h.box)
|
||||
if h.box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
|
||||
fileServer.ServeHTTP(w, req)
|
||||
} else {
|
||||
text, err := h.box.FindString("index.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tmpl, err := template.New("index.html").Parse(text)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
path := ""
|
||||
if h.config.Base != "/" {
|
||||
path = h.config.Base
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Base string
|
||||
Version string
|
||||
}{path, h.config.Version}
|
||||
err = tmpl.Execute(w, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
|
||||
from, _ := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
|
||||
to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
|
||||
id := r.URL.Query().Get("id")
|
||||
|
||||
messages, _ := h.client.ContainerLogsBetweenDates(r.Context(), id, from, to)
|
||||
|
||||
for _, m := range messages {
|
||||
fmt.Fprintln(w, m)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get("id")
|
||||
if id == "" {
|
||||
http.Error(w, "id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
f, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
container, e := h.client.FindContainer(id)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
messages, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, r.Header.Get("Last-Event-ID"))
|
||||
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-messages:
|
||||
if !ok {
|
||||
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
||||
break Loop
|
||||
}
|
||||
fmt.Fprintf(w, "data: %s\n", message)
|
||||
if index := strings.IndexAny(message, " "); index != -1 {
|
||||
id := message[:index]
|
||||
if _, err := time.Parse(time.RFC3339Nano, id); err == nil {
|
||||
fmt.Fprintf(w, "id: %s\n", id)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
f.Flush()
|
||||
case e := <-err:
|
||||
if e == io.EOF {
|
||||
log.Debugf("Container stopped: %v", container.ID)
|
||||
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
||||
f.Flush()
|
||||
} else {
|
||||
log.Debugf("Error while reading from log stream: %v", e)
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.WithField("NumGoroutine", runtime.NumGoroutine()).Debug("runtime stats")
|
||||
|
||||
if log.IsLevelEnabled(log.DebugLevel) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
|
||||
log.WithField("Alloc KBs", m.Alloc/1024).WithField("TotalAlloc KBs", m.TotalAlloc/1024).WithField("Sys KBs", m.Sys/1024).Debug("runtime mem stats")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
||||
f, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
events, err := h.client.Events(ctx)
|
||||
stats := make(chan docker.ContainerStat)
|
||||
|
||||
if containers, err := h.client.ListContainers(); err == nil {
|
||||
for _, c := range containers {
|
||||
if c.State == "running" {
|
||||
if err := h.client.ContainerStats(ctx, c.ID, stats); err != nil {
|
||||
log.Errorf("Error while streaming container stats: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := sendContainersJSON(h.client, w); err != nil {
|
||||
log.Errorf("Error while encoding containers to stream: %v", err)
|
||||
}
|
||||
|
||||
f.Flush()
|
||||
|
||||
for {
|
||||
select {
|
||||
case stat := <-stats:
|
||||
bytes, _ := json.Marshal(stat)
|
||||
if _, err := fmt.Fprintf(w, "event: container-stat\ndata: %s\n\n", string(bytes)); err != nil {
|
||||
log.Debugf("Error writing stat to event stream: %v", err)
|
||||
return
|
||||
}
|
||||
f.Flush()
|
||||
case event, ok := <-events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch event.Name {
|
||||
case "start", "die":
|
||||
log.Debugf("Triggering docker event: %v", event.Name)
|
||||
if event.Name == "start" {
|
||||
log.Debugf("Found new container with id: %v", event.ActorID)
|
||||
if err := h.client.ContainerStats(ctx, event.ActorID, stats); err != nil {
|
||||
log.Errorf("Error when streaming new container stats: %v", err)
|
||||
}
|
||||
if err := sendContainersJSON(h.client, w); err != nil {
|
||||
log.Errorf("Error encoding containers to stream: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(event)
|
||||
if _, err := fmt.Fprintf(w, "event: container-%s\ndata: %s\n\n", event.Name, string(bytes)); err != nil {
|
||||
log.Debugf("Error writing event to event stream: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
f.Flush()
|
||||
default:
|
||||
log.Debugf("Ignoring docker event: %v", event.Name)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-err:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) version(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, h.config.Version)
|
||||
}
|
||||
|
||||
func sendContainersJSON(client docker.Client, w http.ResponseWriter) error {
|
||||
if containers, err := client.ListContainers(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(containers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(w, "\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
352
web/routes_test.go
Normal file
352
web/routes_test.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/beme/abide"
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type MockedClient struct {
|
||||
mock.Mock
|
||||
docker.Client
|
||||
}
|
||||
|
||||
func (m *MockedClient) FindContainer(id string) (docker.Container, error) {
|
||||
args := m.Called(id)
|
||||
container, ok := args.Get(0).(docker.Container)
|
||||
if !ok {
|
||||
panic("containers is not of type docker.Container")
|
||||
}
|
||||
return container, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ListContainers() ([]docker.Container, error) {
|
||||
args := m.Called()
|
||||
containers, ok := args.Get(0).([]docker.Container)
|
||||
if !ok {
|
||||
panic("containers is not of type []docker.Container")
|
||||
}
|
||||
return containers, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (<-chan string, <-chan error) {
|
||||
args := m.Called(ctx, id, tailSize)
|
||||
channel, ok := args.Get(0).(chan string)
|
||||
if !ok {
|
||||
panic("channel is not of type chan string")
|
||||
}
|
||||
|
||||
err, ok := args.Get(1).(chan error)
|
||||
if !ok {
|
||||
panic("error is not of type chan error")
|
||||
}
|
||||
return channel, err
|
||||
}
|
||||
|
||||
func (m *MockedClient) Events(ctx context.Context) (<-chan docker.ContainerEvent, <-chan error) {
|
||||
args := m.Called(ctx)
|
||||
channel, ok := args.Get(0).(chan docker.ContainerEvent)
|
||||
if !ok {
|
||||
panic("channel is not of type chan events.Message")
|
||||
}
|
||||
|
||||
err, ok := args.Get(1).(chan error)
|
||||
if !ok {
|
||||
panic("error is not of type chan error")
|
||||
}
|
||||
return channel, err
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.ContainerStat) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_happy(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
messages := make(chan string)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, 300).Return(messages, errChannel)
|
||||
go func() {
|
||||
messages <- "INFO Testing logs..."
|
||||
close(messages)
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamLogs)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_happy_with_id(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
messages := make(chan string)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, 300).Return(messages, errChannel)
|
||||
go func() {
|
||||
messages <- "2020-05-13T18:55:37.772853839Z INFO Testing logs..."
|
||||
close(messages)
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamLogs)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_happy_container_stopped(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
messages := make(chan string)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(messages, errChannel)
|
||||
|
||||
go func() {
|
||||
errChannel <- io.EOF
|
||||
close(messages)
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamLogs)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_error_finding_container(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{}, errors.New("error finding container"))
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamLogs)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_error_reading(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("id", id)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
messages := make(chan string)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(messages, errChannel)
|
||||
|
||||
go func() {
|
||||
errChannel <- errors.New("test error")
|
||||
close(messages)
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamLogs)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamEvents_happy(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/events/stream", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
mockedClient := new(MockedClient)
|
||||
messages := make(chan docker.ContainerEvent)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
|
||||
go func() {
|
||||
messages <- docker.ContainerEvent{
|
||||
Name: "start",
|
||||
ActorID: "1234",
|
||||
}
|
||||
messages <- docker.ContainerEvent{
|
||||
Name: "something-random",
|
||||
ActorID: "1234",
|
||||
}
|
||||
close(messages)
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamEvents)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamEvents_error(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/events/stream", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
mockedClient := new(MockedClient)
|
||||
messages := make(chan docker.ContainerEvent)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
|
||||
go func() {
|
||||
errChannel <- errors.New("fake error")
|
||||
close(messages)
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamEvents)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_streamEvents_error_request(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/events/stream", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
messages := make(chan docker.ContainerEvent)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
go func() {
|
||||
cancel()
|
||||
}()
|
||||
|
||||
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
|
||||
handler := http.HandlerFunc(h.streamEvents)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_createRoutes_index(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
box := packr.NewBox("./virtual")
|
||||
require.NoError(t, box.AddString("index.html", "index page"), "AddString should have no error.")
|
||||
|
||||
handler := createRouter(&handler{mockedClient, box, &Config{Base: "/"}})
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_redirect(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
box := packr.NewBox("./virtual")
|
||||
|
||||
handler := createRouter(&handler{mockedClient, box, &Config{Base: "/foobar"}})
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_foobar(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
box := packr.NewBox("./virtual")
|
||||
require.NoError(t, box.AddString("index.html", "foo page"), "AddString should have no error.")
|
||||
|
||||
handler := createRouter(&handler{mockedClient, box, &Config{Base: "/foobar"}})
|
||||
req, err := http.NewRequest("GET", "/foobar/", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_foobar_file(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
box := packr.NewBox("./virtual")
|
||||
require.NoError(t, box.AddString("/test", "test page"), "AddString should have no error.")
|
||||
|
||||
handler := createRouter(&handler{mockedClient, box, &Config{Base: "/foobar"}})
|
||||
req, err := http.NewRequest("GET", "/foobar/test", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Body.String(), "test page", "page doesn't match")
|
||||
}
|
||||
|
||||
func Test_createRoutes_version(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
box := packr.NewBox("./virtual")
|
||||
|
||||
handler := createRouter(&handler{mockedClient, box, &Config{Base: "/", Version: "dev"}})
|
||||
req, err := http.NewRequest("GET", "/version", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
exit := m.Run()
|
||||
abide.Cleanup()
|
||||
os.Exit(exit)
|
||||
}
|
||||
Reference in New Issue
Block a user