10bfb846a8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
4.4 KiB
Go
140 lines
4.4 KiB
Go
package infra
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// lokiPushBody refleja la estructura JSON que espera el push API de Loki.
|
|
type lokiPushBody struct {
|
|
Streams []struct {
|
|
Stream map[string]string `json:"stream"`
|
|
Values [][2]string `json:"values"`
|
|
} `json:"streams"`
|
|
}
|
|
|
|
func TestPushLokiStream(t *testing.T) {
|
|
t.Run("JSON enviado tiene estructura streams/stream/values correcta", func(t *testing.T) {
|
|
var captured lokiPushBody
|
|
var contentType string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
contentType = r.Header.Get("Content-Type")
|
|
raw, _ := io.ReadAll(r.Body)
|
|
if err := json.Unmarshal(raw, &captured); err != nil {
|
|
t.Errorf("body no es JSON valido: %v", err)
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
labels := map[string]string{"instance": "lucas", "job": "journald", "unit": "ssh.service"}
|
|
ts := []int64{1700000000000000001, 1700000000000000002}
|
|
lines := []string{"line one", "line two"}
|
|
|
|
err := PushLokiStream(srv.URL, "", "", labels, ts, lines)
|
|
if err != nil {
|
|
t.Fatalf("error inesperado: %v", err)
|
|
}
|
|
|
|
if contentType != "application/json" {
|
|
t.Errorf("Content-Type = %q, want application/json", contentType)
|
|
}
|
|
if len(captured.Streams) != 1 {
|
|
t.Fatalf("streams len = %d, want 1", len(captured.Streams))
|
|
}
|
|
s := captured.Streams[0]
|
|
if s.Stream["unit"] != "ssh.service" || s.Stream["job"] != "journald" || s.Stream["instance"] != "lucas" {
|
|
t.Errorf("stream labels = %v, want %v", s.Stream, labels)
|
|
}
|
|
if len(s.Values) != 2 {
|
|
t.Fatalf("values len = %d, want 2", len(s.Values))
|
|
}
|
|
if s.Values[0][0] != "1700000000000000001" || s.Values[0][1] != "line one" {
|
|
t.Errorf("values[0] = %v, want [1700000000000000001 line one]", s.Values[0])
|
|
}
|
|
if s.Values[1][0] != "1700000000000000002" || s.Values[1][1] != "line two" {
|
|
t.Errorf("values[1] = %v, want [1700000000000000002 line two]", s.Values[1])
|
|
}
|
|
})
|
|
|
|
t.Run("longitudes desiguales dan error antes del POST", func(t *testing.T) {
|
|
hit := false
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
hit = true
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
ts := []int64{1, 2, 3}
|
|
lines := []string{"only one"}
|
|
err := PushLokiStream(srv.URL, "", "", map[string]string{"job": "x"}, ts, lines)
|
|
if err == nil {
|
|
t.Fatalf("se esperaba error por longitudes desiguales")
|
|
}
|
|
if hit {
|
|
t.Errorf("no debe haber peticion HTTP cuando las longitudes no coinciden")
|
|
}
|
|
})
|
|
|
|
t.Run("len lines cero es no-op sin peticion", func(t *testing.T) {
|
|
hit := false
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
hit = true
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := PushLokiStream(srv.URL, "", "", map[string]string{"job": "x"}, []int64{}, []string{})
|
|
if err != nil {
|
|
t.Fatalf("no-op no debe retornar error: %v", err)
|
|
}
|
|
if hit {
|
|
t.Errorf("no-op no debe hacer ninguna peticion HTTP")
|
|
}
|
|
})
|
|
|
|
t.Run("Basic Auth presente cuando user no vacio", func(t *testing.T) {
|
|
var gotUser, gotPass string
|
|
var hadAuth bool
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotUser, gotPass, hadAuth = r.BasicAuth()
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := PushLokiStream(srv.URL, "tenant", "secret", map[string]string{"job": "x"}, []int64{1}, []string{"hi"})
|
|
if err != nil {
|
|
t.Fatalf("error inesperado: %v", err)
|
|
}
|
|
if !hadAuth {
|
|
t.Fatalf("se esperaba header Authorization Basic")
|
|
}
|
|
if gotUser != "tenant" || gotPass != "secret" {
|
|
t.Errorf("basic auth = %q/%q, want tenant/secret", gotUser, gotPass)
|
|
}
|
|
})
|
|
|
|
t.Run("status 500 produce error", func(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte("loki rejected the push"))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := PushLokiStream(srv.URL, "", "", map[string]string{"job": "x"}, []int64{1}, []string{"hi"})
|
|
if err == nil {
|
|
t.Fatalf("se esperaba error con status 500")
|
|
}
|
|
if !strings.Contains(err.Error(), "500") {
|
|
t.Errorf("error no menciona el codigo 500: %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "loki rejected the push") {
|
|
t.Errorf("error no incluye el cuerpo de respuesta: %v", err)
|
|
}
|
|
})
|
|
}
|