192 lines
5.5 KiB
Go
192 lines
5.5 KiB
Go
package meowlib
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestNewLokiWriterDisabledOnEmptyURL(t *testing.T) {
|
|
w := NewLokiWriter("", map[string]string{"app": "test"})
|
|
assert.True(t, w.disabled)
|
|
}
|
|
|
|
func TestNewLokiWriterDisabledOnInvalidURL(t *testing.T) {
|
|
cases := []string{
|
|
"not-a-url",
|
|
"ftp://example.com/loki",
|
|
"://missing-scheme",
|
|
"http://",
|
|
"justtext",
|
|
}
|
|
for _, u := range cases {
|
|
w := NewLokiWriter(u, map[string]string{"app": "test"})
|
|
assert.True(t, w.disabled, "expected disabled for URL: %s", u)
|
|
}
|
|
}
|
|
|
|
func TestNewLokiWriterEnabledOnValidURL(t *testing.T) {
|
|
cases := []string{
|
|
"http://localhost:3100/loki/api/v1/push",
|
|
"https://log.redroom.link/loki/api/v1/push",
|
|
}
|
|
for _, u := range cases {
|
|
w := NewLokiWriter(u, map[string]string{"app": "test"})
|
|
assert.False(t, w.disabled, "expected enabled for URL: %s", u)
|
|
}
|
|
}
|
|
|
|
func TestWriteDisabledReturnsLength(t *testing.T) {
|
|
w := NewLokiWriter("", map[string]string{"app": "test"})
|
|
msg := []byte(`{"level":"info","message":"should be discarded"}`)
|
|
n, err := w.Write(msg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
}
|
|
|
|
func TestWriteToMockLoki(t *testing.T) {
|
|
var received LokiPayload
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
err := json.NewDecoder(r.Body).Decode(&received)
|
|
assert.NoError(t, err)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer server.Close()
|
|
|
|
labels := map[string]string{"app": "meowlib", "env": "test"}
|
|
w := NewLokiWriter(server.URL, labels)
|
|
|
|
msg := []byte(`{"level":"info","message":"hello from test"}`)
|
|
n, err := w.Write(msg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
|
|
// Verify payload structure
|
|
assert.Len(t, received.Streams, 1)
|
|
assert.Equal(t, "meowlib", received.Streams[0].Stream["app"])
|
|
assert.Equal(t, "test", received.Streams[0].Stream["env"])
|
|
assert.Equal(t, "info", received.Streams[0].Stream["level"])
|
|
assert.Len(t, received.Streams[0].Values, 1)
|
|
assert.Equal(t, "hello from test", received.Streams[0].Values[0][1])
|
|
}
|
|
|
|
func TestWriteInvalidJSON(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("server should not be called for invalid JSON input")
|
|
}))
|
|
defer server.Close()
|
|
|
|
w := NewLokiWriter(server.URL, map[string]string{"app": "test"})
|
|
msg := []byte(`not json at all`)
|
|
n, err := w.Write(msg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
}
|
|
|
|
func TestCircuitBreakerOpensAfterFailures(t *testing.T) {
|
|
callCount := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer server.Close()
|
|
|
|
w := NewLokiWriter(server.URL, map[string]string{"app": "test"})
|
|
msg := []byte(`{"level":"error","message":"fail test"}`)
|
|
|
|
// Send maxFailures requests to trip the circuit breaker
|
|
for i := 0; i < maxFailures; i++ {
|
|
n, err := w.Write(msg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
}
|
|
assert.True(t, w.circuitOpen)
|
|
|
|
// Next write should be silently discarded (no server call)
|
|
prevCount := callCount
|
|
n, err := w.Write(msg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
assert.Equal(t, prevCount, callCount, "no HTTP call should be made while circuit is open")
|
|
}
|
|
|
|
func TestCircuitBreakerResetsAfterSuccess(t *testing.T) {
|
|
failFirst := true
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if failFirst {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
} else {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
w := NewLokiWriter(server.URL, map[string]string{"app": "test"})
|
|
msg := []byte(`{"level":"info","message":"recovery test"}`)
|
|
|
|
// Cause some failures (but not enough to open circuit)
|
|
w.Write(msg)
|
|
w.Write(msg)
|
|
|
|
assert.False(t, w.circuitOpen)
|
|
assert.Equal(t, 2, w.failureCount)
|
|
|
|
// Now succeed
|
|
failFirst = false
|
|
w.Write(msg)
|
|
assert.Equal(t, 0, w.failureCount)
|
|
}
|
|
|
|
func TestCircuitBreakerRetriesAfterTimeout(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer server.Close()
|
|
|
|
w := NewLokiWriter(server.URL, map[string]string{"app": "test"})
|
|
|
|
// Manually open the circuit with an old failure time
|
|
w.mu.Lock()
|
|
w.circuitOpen = true
|
|
w.failureCount = maxFailures
|
|
w.lastFailureTime = time.Now().Add(-circuitOpenTime - time.Second)
|
|
w.mu.Unlock()
|
|
|
|
msg := []byte(`{"level":"info","message":"retry test"}`)
|
|
n, err := w.Write(msg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
|
|
// Circuit should now be closed after successful retry
|
|
assert.False(t, w.circuitOpen)
|
|
assert.Equal(t, 0, w.failureCount)
|
|
}
|
|
|
|
func TestWriteToRealLoki(t *testing.T) {
|
|
lokiURL := "https://log.redroom.link/loki/api/v1/push"
|
|
|
|
// Quick connectivity check
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
resp, err := client.Get("https://log.redroom.link/ready")
|
|
if err != nil || resp.StatusCode != http.StatusOK {
|
|
t.Skip("Loki not reachable, skipping live test")
|
|
}
|
|
resp.Body.Close()
|
|
|
|
labels := map[string]string{"app": "meowlib", "env": "test"}
|
|
w := NewLokiWriter(lokiURL, labels)
|
|
assert.False(t, w.disabled)
|
|
|
|
msg := fmt.Sprintf(`{"level":"info","message":"lokiwriter_test at %s"}`, time.Now().Format(time.RFC3339))
|
|
n, err := w.Write([]byte(msg))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(msg), n)
|
|
assert.Equal(t, 0, w.failureCount)
|
|
}
|