mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-30 05:44:37 +00:00
fbb13aa32f
internal/converter: 57.1% -> 98.4%. Added 18 cases covering ConvertKFTrayToKPortal (happy path, missing input, malformed JSON, unwritable output, multi-entry, file mode 0600), GetConversionSummary (happy path, missing/malformed input, empty array, dedup counters), and convertToKPortal edge cases (empty input, zero ports, empty WorkloadType, sort order, alias preservation, tcp/udp/empty protocol variants). internal/httplog: 55.7% -> 95.7%. Added 17 tests using httptest for loggingTransport.RoundTrip (GET/POST, filter-path bypass, header redaction at the integration layer, backend-down, nil bodies, request counter monotonicity) and direct unit tests for readBodyLimited (empty, small, exact-at-limit, truncated, large pool-branch, zero maxSize).
324 lines
12 KiB
Go
324 lines
12 KiB
Go
package converter
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/lukaszraczylo/kportal/internal/config"
|
|
)
|
|
|
|
// writeJSON writes v as JSON to a temp file in dir, returns the path.
|
|
func writeJSON(t *testing.T, dir string, name string, v any) string {
|
|
t.Helper()
|
|
data, err := json.Marshal(v)
|
|
require.NoError(t, err)
|
|
path := filepath.Join(dir, name)
|
|
require.NoError(t, os.WriteFile(path, data, 0600))
|
|
return path
|
|
}
|
|
|
|
// ─── ConvertKFTrayToKPortal ──────────────────────────────────────────────────
|
|
|
|
func TestConvertKFTrayToKPortal_HappyPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeJSON(t, dir, "in.json", []KFTrayConfig{
|
|
{
|
|
Service: "api",
|
|
Namespace: "default",
|
|
Context: "prod",
|
|
WorkloadType: "service",
|
|
Protocol: "tcp",
|
|
Alias: "prod-api",
|
|
LocalPort: 8080,
|
|
RemotePort: 3000,
|
|
},
|
|
})
|
|
output := filepath.Join(dir, "out.yaml")
|
|
|
|
err := ConvertKFTrayToKPortal(input, output)
|
|
require.NoError(t, err)
|
|
|
|
raw, err := os.ReadFile(output)
|
|
require.NoError(t, err)
|
|
|
|
// Header present
|
|
assert.True(t, strings.HasPrefix(string(raw), "# kportal configuration converted from kftray format"),
|
|
"output must start with the header comment")
|
|
|
|
// Parse the YAML body (strip comment lines for strict unmarshal)
|
|
var cfg config.Config
|
|
require.NoError(t, yaml.Unmarshal(raw, &cfg))
|
|
|
|
require.Len(t, cfg.Contexts, 1)
|
|
assert.Equal(t, "prod", cfg.Contexts[0].Name)
|
|
require.Len(t, cfg.Contexts[0].Namespaces, 1)
|
|
assert.Equal(t, "default", cfg.Contexts[0].Namespaces[0].Name)
|
|
require.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 1)
|
|
|
|
fwd := cfg.Contexts[0].Namespaces[0].Forwards[0]
|
|
assert.Equal(t, "service/api", fwd.Resource)
|
|
assert.Equal(t, "tcp", fwd.Protocol)
|
|
assert.Equal(t, 3000, fwd.Port)
|
|
assert.Equal(t, 8080, fwd.LocalPort)
|
|
assert.Equal(t, "prod-api", fwd.Alias)
|
|
}
|
|
|
|
func TestConvertKFTrayToKPortal_MissingInputFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
err := ConvertKFTrayToKPortal(filepath.Join(dir, "nonexistent.json"), filepath.Join(dir, "out.yaml"))
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to read input file")
|
|
}
|
|
|
|
func TestConvertKFTrayToKPortal_MalformedJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := filepath.Join(dir, "bad.json")
|
|
require.NoError(t, os.WriteFile(input, []byte("{not json}"), 0600))
|
|
|
|
err := ConvertKFTrayToKPortal(input, filepath.Join(dir, "out.yaml"))
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to parse JSON")
|
|
}
|
|
|
|
func TestConvertKFTrayToKPortal_EmptyArray(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeJSON(t, dir, "empty.json", []KFTrayConfig{})
|
|
output := filepath.Join(dir, "out.yaml")
|
|
|
|
err := ConvertKFTrayToKPortal(input, output)
|
|
require.NoError(t, err)
|
|
|
|
raw, err := os.ReadFile(output)
|
|
require.NoError(t, err)
|
|
|
|
var cfg config.Config
|
|
require.NoError(t, yaml.Unmarshal(raw, &cfg))
|
|
assert.Empty(t, cfg.Contexts)
|
|
}
|
|
|
|
func TestConvertKFTrayToKPortal_UnwritableOutputDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeJSON(t, dir, "in.json", []KFTrayConfig{
|
|
{Service: "svc", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 80, RemotePort: 80},
|
|
})
|
|
|
|
// Use a path that cannot be created (sub-dir of a non-existing dir)
|
|
output := filepath.Join(dir, "no-such-subdir", "out.yaml")
|
|
|
|
err := ConvertKFTrayToKPortal(input, output)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to write output file")
|
|
}
|
|
|
|
func TestConvertKFTrayToKPortal_MultipleEntries_YAMLRoundtrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
entries := []KFTrayConfig{
|
|
{Service: "postgres", Namespace: "db", Context: "prod", WorkloadType: "service", Protocol: "tcp", Alias: "pg", LocalPort: 5432, RemotePort: 5432},
|
|
{Service: "redis", Namespace: "cache", Context: "prod", WorkloadType: "service", Protocol: "tcp", Alias: "rd", LocalPort: 6379, RemotePort: 6379},
|
|
{Service: "api", Namespace: "default", Context: "staging", WorkloadType: "pod", Protocol: "tcp", LocalPort: 8080, RemotePort: 8080},
|
|
}
|
|
input := writeJSON(t, dir, "in.json", entries)
|
|
output := filepath.Join(dir, "out.yaml")
|
|
|
|
require.NoError(t, ConvertKFTrayToKPortal(input, output))
|
|
|
|
raw, err := os.ReadFile(output)
|
|
require.NoError(t, err)
|
|
|
|
var cfg config.Config
|
|
require.NoError(t, yaml.Unmarshal(raw, &cfg))
|
|
|
|
// Two distinct contexts: prod, staging (sorted)
|
|
require.Len(t, cfg.Contexts, 2)
|
|
assert.Equal(t, "prod", cfg.Contexts[0].Name)
|
|
assert.Equal(t, "staging", cfg.Contexts[1].Name)
|
|
|
|
// prod has two namespaces sorted: cache, db
|
|
prodNS := cfg.Contexts[0].Namespaces
|
|
require.Len(t, prodNS, 2)
|
|
assert.Equal(t, "cache", prodNS[0].Name)
|
|
assert.Equal(t, "db", prodNS[1].Name)
|
|
|
|
// staging/default has pod workload type
|
|
stagingFwd := cfg.Contexts[1].Namespaces[0].Forwards[0]
|
|
assert.Equal(t, "pod/api", stagingFwd.Resource)
|
|
}
|
|
|
|
func TestConvertKFTrayToKPortal_OutputFilePermissions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeJSON(t, dir, "in.json", []KFTrayConfig{
|
|
{Service: "svc", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 80, RemotePort: 80},
|
|
})
|
|
output := filepath.Join(dir, "out.yaml")
|
|
|
|
require.NoError(t, ConvertKFTrayToKPortal(input, output))
|
|
|
|
info, err := os.Stat(output)
|
|
require.NoError(t, err)
|
|
// Written with 0600 — owner rw, no group/other
|
|
assert.Equal(t, os.FileMode(0600), info.Mode().Perm())
|
|
}
|
|
|
|
// ─── GetConversionSummary ────────────────────────────────────────────────────
|
|
|
|
func TestGetConversionSummary_HappyPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
entries := []KFTrayConfig{
|
|
{Service: "api", Namespace: "default", Context: "prod", WorkloadType: "service", Protocol: "tcp", LocalPort: 8080, RemotePort: 8080},
|
|
{Service: "pg", Namespace: "db", Context: "prod", WorkloadType: "service", Protocol: "tcp", LocalPort: 5432, RemotePort: 5432},
|
|
{Service: "api", Namespace: "default", Context: "staging", WorkloadType: "service", Protocol: "tcp", LocalPort: 8080, RemotePort: 8080},
|
|
}
|
|
input := writeJSON(t, dir, "in.json", entries)
|
|
|
|
contextMap, total, err := GetConversionSummary(input)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 3, total)
|
|
assert.Len(t, contextMap, 2)
|
|
|
|
// prod context: 2 entries across 2 namespaces
|
|
assert.Equal(t, 1, contextMap["prod"]["default"])
|
|
assert.Equal(t, 1, contextMap["prod"]["db"])
|
|
|
|
// staging context: 1 entry in default namespace
|
|
assert.Equal(t, 1, contextMap["staging"]["default"])
|
|
}
|
|
|
|
func TestGetConversionSummary_MissingFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, _, err := GetConversionSummary(filepath.Join(dir, "ghost.json"))
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to read input file")
|
|
}
|
|
|
|
func TestGetConversionSummary_MalformedJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "bad.json")
|
|
require.NoError(t, os.WriteFile(path, []byte("not-json"), 0600))
|
|
|
|
_, _, err := GetConversionSummary(path)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to parse JSON")
|
|
}
|
|
|
|
func TestGetConversionSummary_EmptyArray(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeJSON(t, dir, "empty.json", []KFTrayConfig{})
|
|
|
|
contextMap, total, err := GetConversionSummary(input)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, total)
|
|
assert.Empty(t, contextMap)
|
|
}
|
|
|
|
func TestGetConversionSummary_SameNamespaceDifferentContexts(t *testing.T) {
|
|
dir := t.TempDir()
|
|
entries := []KFTrayConfig{
|
|
{Service: "svc", Namespace: "default", Context: "ctx-a", LocalPort: 80, RemotePort: 80},
|
|
{Service: "svc", Namespace: "default", Context: "ctx-a", LocalPort: 81, RemotePort: 81},
|
|
{Service: "svc", Namespace: "default", Context: "ctx-b", LocalPort: 80, RemotePort: 80},
|
|
}
|
|
input := writeJSON(t, dir, "in.json", entries)
|
|
|
|
contextMap, total, err := GetConversionSummary(input)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, total)
|
|
// ctx-a/default has 2 services
|
|
assert.Equal(t, 2, contextMap["ctx-a"]["default"])
|
|
// ctx-b/default has 1 service
|
|
assert.Equal(t, 1, contextMap["ctx-b"]["default"])
|
|
}
|
|
|
|
// ─── convertToKPortal edge cases ─────────────────────────────────────────────
|
|
|
|
func TestConvertToKPortal_EmptyInput(t *testing.T) {
|
|
result := convertToKPortal([]KFTrayConfig{})
|
|
assert.Empty(t, result.Contexts)
|
|
}
|
|
|
|
func TestConvertToKPortal_ZeroPorts(t *testing.T) {
|
|
result := convertToKPortal([]KFTrayConfig{
|
|
{Service: "svc", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp"},
|
|
})
|
|
require.Len(t, result.Contexts, 1)
|
|
fwd := result.Contexts[0].Namespaces[0].Forwards[0]
|
|
assert.Equal(t, 0, fwd.Port)
|
|
assert.Equal(t, 0, fwd.LocalPort)
|
|
}
|
|
|
|
func TestConvertToKPortal_EmptyWorkloadType(t *testing.T) {
|
|
// WorkloadType="" → resource becomes "/svc"
|
|
result := convertToKPortal([]KFTrayConfig{
|
|
{Service: "svc", Namespace: "ns", Context: "ctx", WorkloadType: "", Protocol: "tcp", LocalPort: 80, RemotePort: 80},
|
|
})
|
|
fwd := result.Contexts[0].Namespaces[0].Forwards[0]
|
|
assert.Equal(t, "/svc", fwd.Resource)
|
|
}
|
|
|
|
func TestConvertToKPortal_ForwardsSortedByLocalPort(t *testing.T) {
|
|
// Supply in reverse order; expect ascending local port after conversion
|
|
cfgs := []KFTrayConfig{
|
|
{Service: "c", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 9000, RemotePort: 9000},
|
|
{Service: "a", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 1000, RemotePort: 1000},
|
|
{Service: "b", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 5000, RemotePort: 5000},
|
|
}
|
|
result := convertToKPortal(cfgs)
|
|
forwards := result.Contexts[0].Namespaces[0].Forwards
|
|
require.Len(t, forwards, 3)
|
|
assert.Equal(t, 1000, forwards[0].LocalPort)
|
|
assert.Equal(t, 5000, forwards[1].LocalPort)
|
|
assert.Equal(t, 9000, forwards[2].LocalPort)
|
|
}
|
|
|
|
func TestConvertToKPortal_ContextsAndNamespacesSortedAlphabetically(t *testing.T) {
|
|
cfgs := []KFTrayConfig{
|
|
{Service: "svc", Namespace: "z-ns", Context: "z-ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 80, RemotePort: 80},
|
|
{Service: "svc", Namespace: "a-ns", Context: "z-ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 81, RemotePort: 81},
|
|
{Service: "svc", Namespace: "m-ns", Context: "a-ctx", WorkloadType: "service", Protocol: "tcp", LocalPort: 82, RemotePort: 82},
|
|
}
|
|
result := convertToKPortal(cfgs)
|
|
|
|
require.Len(t, result.Contexts, 2)
|
|
assert.Equal(t, "a-ctx", result.Contexts[0].Name)
|
|
assert.Equal(t, "z-ctx", result.Contexts[1].Name)
|
|
|
|
zCtxNS := result.Contexts[1].Namespaces
|
|
require.Len(t, zCtxNS, 2)
|
|
assert.Equal(t, "a-ns", zCtxNS[0].Name)
|
|
assert.Equal(t, "z-ns", zCtxNS[1].Name)
|
|
}
|
|
|
|
func TestConvertToKPortal_AliasPreservedWhenSet(t *testing.T) {
|
|
cfgs := []KFTrayConfig{
|
|
{Service: "svc", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: "tcp", Alias: "my-alias", LocalPort: 80, RemotePort: 80},
|
|
}
|
|
result := convertToKPortal(cfgs)
|
|
assert.Equal(t, "my-alias", result.Contexts[0].Namespaces[0].Forwards[0].Alias)
|
|
}
|
|
|
|
func TestConvertToKPortal_DifferentProtocols(t *testing.T) {
|
|
tests := []struct {
|
|
protocol string
|
|
}{
|
|
{"tcp"},
|
|
{"udp"},
|
|
{""},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run("protocol="+tt.protocol, func(t *testing.T) {
|
|
result := convertToKPortal([]KFTrayConfig{
|
|
{Service: "svc", Namespace: "ns", Context: "ctx", WorkloadType: "service", Protocol: tt.protocol, LocalPort: 80, RemotePort: 80},
|
|
})
|
|
assert.Equal(t, tt.protocol, result.Contexts[0].Namespaces[0].Forwards[0].Protocol)
|
|
})
|
|
}
|
|
}
|