package cmd import ( "errors" "fmt" "io/ioutil" "math/rand" "strings" "testing" "github.com/runconduit/conduit/controller/api/public" pb "github.com/runconduit/conduit/controller/gen/public" "github.com/runconduit/conduit/pkg/k8s" "github.com/stretchr/testify/assert" ) func TestRequestStatsFromApi(t *testing.T) { t.Run("Returns string output containing the data returned by the API", func(t *testing.T) { mockClient := &public.MockConduitApiClient{} deployName := "deployment-1" metricDatapoints := []*pb.MetricDatapoint{ { Value: &pb.MetricValue{ Value: &pb.MetricValue_Counter{ Counter: 666, }, }, }, } series := []*pb.MetricSeries{ { Name: pb.MetricName_SUCCESS_RATE, Metadata: &pb.MetricMetadata{ TargetDeploy: deployName, }, Datapoints: metricDatapoints, }, } mockClient.MetricResponseToReturn = &pb.MetricResponse{ Metrics: series, } stats, err := requestStatsFromApi(mockClient, k8s.KubernetesDeployments) if err != nil { t.Fatalf("Unexpected error: %v", err) } if !strings.Contains(stats, deployName) { t.Fatalf("Expected response to contain [%s], but was [%s]", deployName, stats) } }) t.Run("Returns error if API call failed", func(t *testing.T) { mockClient := &public.MockConduitApiClient{} mockClient.ErrorToReturn = errors.New("Expected") output, err := requestStatsFromApi(mockClient, k8s.KubernetesPods) if err == nil { t.Fatalf("Expected error, got nothing but the output [%s]", output) } }) } func TestRenderStats(t *testing.T) { t.Run("Prints stats correctly for example with one entry", func(t *testing.T) { allSeries := make([]*pb.MetricSeries, 0) seriesForPodX := generateMetricSeriesFor(fmt.Sprintf("deployment-%d", 66), int64(10)) allSeries = append(allSeries, seriesForPodX...) //shuffles for i := range allSeries { j := rand.Intn(i + 1) allSeries[i], allSeries[j] = allSeries[j], allSeries[i] } response := &pb.MetricResponse{ Metrics: allSeries, } renderedStats, err := renderStats(response) if err != nil { t.Fatalf("Unexpected error: %v", err) } goldenFileBytes, err := ioutil.ReadFile("testdata/stat_one_output.golden") if err != nil { t.Fatalf("Unexpected error: %v", err) } expectedContent := string(goldenFileBytes) if expectedContent != renderedStats { t.Fatalf("Expected function to render:\n%s\bbut got:\n%s", expectedContent, renderedStats) } }) t.Run("Prints stats correctly for busy example", func(t *testing.T) { allSeries := make([]*pb.MetricSeries, 0) for i := 0; i < 10; i++ { seriesForDeployX := generateMetricSeriesFor(fmt.Sprintf("deployment-%d", i), int64(i)) allSeries = append(allSeries, seriesForDeployX...) } //shuffles for i := range allSeries { j := rand.Intn(i + 1) allSeries[i], allSeries[j] = allSeries[j], allSeries[i] } response := &pb.MetricResponse{ Metrics: allSeries, } renderedStats, err := renderStats(response) if err != nil { t.Fatalf("Unexpected error: %v", err) } goldenFileBytes, err := ioutil.ReadFile("testdata/stat_busy_output.golden") if err != nil { t.Fatalf("Unexpected error: %v", err) } expectedContent := string(goldenFileBytes) if expectedContent != renderedStats { t.Fatalf("Expected function to render:\n%s\bbut got:\n%s", expectedContent, renderedStats) } }) t.Run("Prints stats correctly for empty example", func(t *testing.T) { response := &pb.MetricResponse{ Metrics: make([]*pb.MetricSeries, 0), } renderedStats, err := renderStats(response) if err != nil { t.Fatalf("Unexpected error: %v", err) } goldenFileBytes, err := ioutil.ReadFile("testdata/stat_empty_output.golden") if err != nil { t.Fatalf("Unexpected error: %v", err) } expectedContent := string(goldenFileBytes) if expectedContent != renderedStats { t.Fatalf("Expected function to render:\n%s\bbut got:\n%s", expectedContent, renderedStats) } }) } func TestSortStatsKeys(t *testing.T) { t.Run("Sorts the keys alphabetically", func(t *testing.T) { unsorted := map[string]*row{ "kube-system/heapster-v1.4.3": {0.008091, 24.137931, 516666, 990333}, "test/backend4": {0.066121, 38.818565, 494553, 989891}, "test/hello10": {0.000000, 0.000000, 0, 0}, "test/world-deploy1": {0.051893, 33.870968, 510526, 990210}, "test/world-deploy2": {2.504800, 33.749165, 497249, 989944}, "kube-system/kubernetes-dashboard": {0.017856, 39.062500, 520000, 990400}, "other/grafana": {0.060557, 35.944212, 518960, 990379}, "kube-system/l7-default-backend": {0.020371, 31.508049, 516923, 990338}, } expected := []string{"kube-system/heapster-v1.4.3", "kube-system/kubernetes-dashboard", "kube-system/l7-default-backend", "other/grafana", "test/backend4", "test/hello10", "test/world-deploy1", "test/world-deploy2"} sorted := sortStatsKeys(unsorted) assert.Equal(t, expected, sorted, "Not Sorted!") }) } func generateMetricSeriesFor(deployName string, seed int64) []*pb.MetricSeries { metricDatapoints := []*pb.MetricDatapoint{ { Value: &pb.MetricValue{ Value: &pb.MetricValue_Gauge{ Gauge: float64(seed) / 10, }, }, }, } latencyHistogram := []*pb.MetricDatapoint{ { Value: &pb.MetricValue{ Value: &pb.MetricValue_Histogram{ Histogram: &pb.Histogram{ Values: []*pb.HistogramValue{ { Label: pb.HistogramLabel_P50, Value: 1 + seed, }, { Label: pb.HistogramLabel_P99, Value: 9 + seed, }, { Label: pb.HistogramLabel_P95, Value: 5 + seed, }, }, }, }, }, }, } series := []*pb.MetricSeries{ { Name: pb.MetricName_REQUEST_RATE, Metadata: &pb.MetricMetadata{ TargetDeploy: deployName, }, Datapoints: metricDatapoints, }, { Name: pb.MetricName_SUCCESS_RATE, Metadata: &pb.MetricMetadata{ TargetDeploy: deployName, }, Datapoints: metricDatapoints, }, { Name: pb.MetricName_LATENCY, Metadata: &pb.MetricMetadata{ TargetDeploy: deployName, }, Datapoints: latencyHistogram, }, } return series }