This commit is contained in:
459
internal/httpserver/server_test.go
Normal file
459
internal/httpserver/server_test.go
Normal file
@@ -0,0 +1,459 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"kubeviz/internal/config"
|
||||
)
|
||||
|
||||
func TestParseGraphAndResourceFlow(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
manifest := `apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-cfg
|
||||
namespace: demo
|
||||
`
|
||||
body := map[string]string{"manifest": manifest}
|
||||
payload, _ := json.Marshal(body)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/manifests/parse", bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("parse status: got %d body=%s", got, w.Body.String())
|
||||
}
|
||||
cookies := w.Result().Cookies()
|
||||
if len(cookies) == 0 {
|
||||
t.Fatalf("expected session cookie")
|
||||
}
|
||||
|
||||
graphReq := httptest.NewRequest(http.MethodGet, "/api/graph", nil)
|
||||
graphReq.AddCookie(cookies[0])
|
||||
graphW := httptest.NewRecorder()
|
||||
handler.ServeHTTP(graphW, graphReq)
|
||||
if got := graphW.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("graph status: got %d", got)
|
||||
}
|
||||
var graph map[string]any
|
||||
if err := json.NewDecoder(graphW.Result().Body).Decode(&graph); err != nil {
|
||||
t.Fatalf("graph decode failed: %v", err)
|
||||
}
|
||||
|
||||
nodes, _ := graph["nodes"].([]any)
|
||||
if len(nodes) != 1 {
|
||||
t.Fatalf("expected 1 node, got %d", len(nodes))
|
||||
}
|
||||
|
||||
resReq := httptest.NewRequest(http.MethodGet, "/api/resources/demo/ConfigMap/app-cfg", nil)
|
||||
resReq.AddCookie(cookies[0])
|
||||
resW := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resW, resReq)
|
||||
if got := resW.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("resource status: got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMultipleUploadedFiles(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
|
||||
f1, err := writer.CreateFormFile("manifestFile", "deployment.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating file part 1: %v", err)
|
||||
}
|
||||
_, _ = f1.Write([]byte(`apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kubeviz
|
||||
namespace: kubeviz
|
||||
`))
|
||||
|
||||
f2, err := writer.CreateFormFile("manifestFile", "service.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating file part 2: %v", err)
|
||||
}
|
||||
_, _ = f2.Write([]byte(`apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kubeviz
|
||||
namespace: kubeviz
|
||||
spec:
|
||||
selector:
|
||||
app: kubeviz
|
||||
`))
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatalf("failed to close multipart writer: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/manifests/parse", &body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("parse status: got %d body=%s", got, w.Body.String())
|
||||
}
|
||||
|
||||
cookies := w.Result().Cookies()
|
||||
if len(cookies) == 0 {
|
||||
t.Fatalf("expected session cookie")
|
||||
}
|
||||
|
||||
graphReq := httptest.NewRequest(http.MethodGet, "/api/graph", nil)
|
||||
graphReq.AddCookie(cookies[0])
|
||||
graphW := httptest.NewRecorder()
|
||||
handler.ServeHTTP(graphW, graphReq)
|
||||
if got := graphW.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("graph status: got %d", got)
|
||||
}
|
||||
|
||||
var graph map[string]any
|
||||
if err := json.NewDecoder(graphW.Result().Body).Decode(&graph); err != nil {
|
||||
t.Fatalf("graph decode failed: %v", err)
|
||||
}
|
||||
nodes, _ := graph["nodes"].([]any)
|
||||
if len(nodes) != 2 {
|
||||
t.Fatalf("expected 2 nodes from two files, got %d", len(nodes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMultipleUploadedFilesToleratesSingleInvalidFile(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
|
||||
valid, err := writer.CreateFormFile("manifestFile", "service.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating valid file part: %v", err)
|
||||
}
|
||||
_, _ = valid.Write([]byte(`apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kubeviz
|
||||
namespace: kubeviz
|
||||
spec:
|
||||
selector:
|
||||
app: kubeviz
|
||||
`))
|
||||
|
||||
invalid, err := writer.CreateFormFile("manifestFile", "broken.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating invalid file part: %v", err)
|
||||
}
|
||||
_, _ = invalid.Write([]byte(`apiVersion: v1
|
||||
kind Service
|
||||
metadata:
|
||||
name: broken
|
||||
`))
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatalf("failed to close multipart writer: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/manifests/parse", &body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("parse status: got %d", got)
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
Summary struct {
|
||||
Resources int `json:"resources"`
|
||||
Issues []struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"issues"`
|
||||
} `json:"summary"`
|
||||
}
|
||||
if err := json.NewDecoder(w.Result().Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("parse response decode failed: %v", err)
|
||||
}
|
||||
if resp.Summary.Resources != 1 {
|
||||
t.Fatalf("expected 1 parsed resource, got %d", resp.Summary.Resources)
|
||||
}
|
||||
if len(resp.Summary.Issues) == 0 {
|
||||
t.Fatalf("expected parse issue for invalid file")
|
||||
}
|
||||
if !strings.Contains(resp.Summary.Issues[0].Message, "broken.yaml") {
|
||||
t.Fatalf("expected issue message to include filename, got: %q", resp.Summary.Issues[0].Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMultipleUploadedFilesAllInvalidReturnsIssues(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
|
||||
f1, err := writer.CreateFormFile("manifestFile", "values.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating file part 1: %v", err)
|
||||
}
|
||||
_, _ = f1.Write([]byte(`replicaCount: 2
|
||||
image:
|
||||
repository: nginx
|
||||
`))
|
||||
|
||||
f2, err := writer.CreateFormFile("manifestFile", "Chart.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating file part 2: %v", err)
|
||||
}
|
||||
_, _ = f2.Write([]byte(`apiVersion: v2
|
||||
name: sample-chart
|
||||
version: 0.1.0
|
||||
`))
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatalf("failed to close multipart writer: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/manifests/parse", &body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("parse status: got %d body=%s", got, w.Body.String())
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
Summary struct {
|
||||
Resources int `json:"resources"`
|
||||
Issues []struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"issues"`
|
||||
} `json:"summary"`
|
||||
}
|
||||
if err := json.NewDecoder(w.Result().Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("parse response decode failed: %v", err)
|
||||
}
|
||||
if resp.Summary.Resources != 0 {
|
||||
t.Fatalf("expected 0 parsed resources, got %d", resp.Summary.Resources)
|
||||
}
|
||||
if len(resp.Summary.Issues) < 2 {
|
||||
t.Fatalf("expected issues for all invalid files, got %d", len(resp.Summary.Issues))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffEndpoint(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
body := map[string]string{
|
||||
"baseManifest": `apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cfg
|
||||
namespace: demo
|
||||
data:
|
||||
a: "1"
|
||||
`,
|
||||
"targetManifest": `apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cfg
|
||||
namespace: demo
|
||||
data:
|
||||
a: "2"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: svc
|
||||
namespace: demo
|
||||
`,
|
||||
}
|
||||
payload, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/diff", bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("diff status: got %d", got)
|
||||
}
|
||||
|
||||
var diff map[string]any
|
||||
if err := json.NewDecoder(w.Result().Body).Decode(&diff); err != nil {
|
||||
t.Fatalf("diff decode failed: %v", err)
|
||||
}
|
||||
changed, _ := diff["changed"].([]any)
|
||||
added, _ := diff["added"].([]any)
|
||||
if len(changed) != 1 {
|
||||
t.Fatalf("expected 1 changed item, got %d", len(changed))
|
||||
}
|
||||
if len(added) != 1 {
|
||||
t.Fatalf("expected 1 added item, got %d", len(added))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphEndpointProvidesFindingsAndGroups(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
manifest := `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: app
|
||||
namespace: demo
|
||||
spec:
|
||||
selector:
|
||||
app: no-match
|
||||
`
|
||||
body := map[string]string{"manifest": manifest}
|
||||
payload, _ := json.Marshal(body)
|
||||
|
||||
parseReq := httptest.NewRequest(http.MethodPost, "/api/manifests/parse", bytes.NewReader(payload))
|
||||
parseReq.Header.Set("Content-Type", "application/json")
|
||||
parseW := httptest.NewRecorder()
|
||||
handler.ServeHTTP(parseW, parseReq)
|
||||
if got := parseW.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("parse status: got %d", got)
|
||||
}
|
||||
cookies := parseW.Result().Cookies()
|
||||
if len(cookies) == 0 {
|
||||
t.Fatalf("expected session cookie")
|
||||
}
|
||||
|
||||
graphReq := httptest.NewRequest(http.MethodGet, "/api/graph?groupBy=namespace&collapsed=demo", nil)
|
||||
graphReq.AddCookie(cookies[0])
|
||||
graphW := httptest.NewRecorder()
|
||||
handler.ServeHTTP(graphW, graphReq)
|
||||
if got := graphW.Result().StatusCode; got != http.StatusOK {
|
||||
t.Fatalf("graph status: got %d", got)
|
||||
}
|
||||
|
||||
var graph map[string]any
|
||||
if err := json.NewDecoder(graphW.Result().Body).Decode(&graph); err != nil {
|
||||
t.Fatalf("graph decode failed: %v", err)
|
||||
}
|
||||
if findings, _ := graph["findings"].([]any); len(findings) == 0 {
|
||||
t.Fatalf("expected findings in graph response")
|
||||
}
|
||||
if groups, _ := graph["groups"].([]any); len(groups) == 0 {
|
||||
t.Fatalf("expected groups in graph response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitImportValidation(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/git/import", bytes.NewBufferString(`{"sourceType":"manifests"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for missing repoURL, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelmRenderValidation(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/helm/render", bytes.NewBufferString(`{}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for missing repoURL, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitImportRejectsNonHTTPSRepoURL(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/git/import", bytes.NewBufferString(`{"repoURL":"http://github.com/org/repo.git","sourceType":"manifests"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for non-https repoURL, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitImportRejectsDisallowedHost(t *testing.T) {
|
||||
srv, err := New(config.Config{
|
||||
Addr: ":0",
|
||||
SessionTTL: 30 * time.Minute,
|
||||
MaxUploadSize: 1024 * 1024,
|
||||
GitAllowedHosts: []string{"github.com"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/git/import", bytes.NewBufferString(`{"repoURL":"https://evil.example/repo.git","sourceType":"manifests"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for disallowed host, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffRejectsTooLargePayload(t *testing.T) {
|
||||
srv, err := New(config.Config{Addr: ":0", SessionTTL: 30 * time.Minute, MaxUploadSize: 8 * 1024 * 1024})
|
||||
if err != nil {
|
||||
t.Fatalf("server init failed: %v", err)
|
||||
}
|
||||
handler := srv.Handler()
|
||||
|
||||
tooLarge := strings.Repeat("a", maxDiffFieldBytes+1)
|
||||
body := map[string]string{
|
||||
"baseManifest": tooLarge,
|
||||
"targetManifest": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: demo\n",
|
||||
}
|
||||
payload, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/diff", bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if got := w.Result().StatusCode; got != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for oversized diff payload, got %d", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user