package version import ( "runtime" "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) func TestCheckVersion(t *testing.T) { tests := []struct { In string Min string Expected bool }{ // Basic version comparisons {"v3.8.4", "v3.8.5", false}, {"v3.9.3", "v3.8.5", true}, {"v3.8.5", "v3.8.5", true}, // Dev snapshot should always pass {"dev-snapshot", "v3.8.5", true}, {"dev-snapshot", "v99.99.99", true}, // Versions with Git hashes should be stripped {"v3.8.5/abc123", "v3.8.5", true}, {"v3.8.4/abc123", "v3.8.5", false}, {"v3.9.0/def456", "v3.8.5", true}, // Pre-release versions {"v3.8.5-alpha", "v3.8.5", false}, {"v3.8.5", "v3.8.5-alpha", true}, {"v3.8.5-beta", "v3.8.5-alpha", true}, } for _, d := range tests { r := CheckVersion(d.In, d.Min) if r != d.Expected { t.Errorf("CheckVersion(%q, %q) = %t, expected %t", d.In, d.Min, r, d.Expected) } } } func TestVersionInfo(t *testing.T) { info := VersionInfo() // Check that we get a valid Info struct if info.Version == "" { t.Error("VersionInfo().Version should not be empty") } // Version should start with "v" or be "dev-snapshot" if !strings.HasPrefix(info.Version, "v") && info.Version != "dev-snapshot" { t.Errorf("Version should start with 'v' or be 'dev-snapshot', got: %s", info.Version) } // GitRevShort should be <= 7 characters if set if info.GitRevShort != "" && len(info.GitRevShort) > 7 { t.Errorf("GitRevShort should be <= 7 characters, got: %s", info.GitRevShort) } // GitRevShort should be prefix of GitRev if both are set if info.GitRev != "" && info.GitRevShort != "" { if !strings.HasPrefix(info.GitRev, info.GitRevShort) { t.Errorf("GitRevShort should be prefix of GitRev: %s not prefix of %s", info.GitRevShort, info.GitRev) } } } func TestVersion(t *testing.T) { version := Version() if version == "" { t.Error("Version() should not return empty string") } // Should contain Go version if !strings.Contains(version, runtime.Version()) { t.Errorf("Version should contain Go version %s, got: %s", runtime.Version(), version) } // Should contain the VERSION variable (or dev-snapshot) info := VersionInfo() if !strings.Contains(version, info.Version) { t.Errorf("Version should contain %s, got: %s", info.Version, version) } // Should be in expected format: "version (extras)" if !strings.Contains(version, "(") || !strings.Contains(version, ")") { t.Errorf("Version should be in format 'version (extras)', got: %s", version) } } func TestVersionCmd(t *testing.T) { appName := "testapp" cmd := VersionCmd(appName) // Test basic command properties if cmd.Use != "version" { t.Errorf("Expected command use to be 'version', got: %s", cmd.Use) } if cmd.Short == "" { t.Error("Command should have a short description") } if cmd.Long == "" { t.Error("Command should have a long description") } if cmd.Run == nil { t.Error("Command should have a Run function") } // Test that the command can be executed without error cmd.SetArgs([]string{}) err := cmd.Execute() if err != nil { t.Errorf("VersionCmd execution should not return error, got: %s", err) } } func TestKongVersionCmd(t *testing.T) { cmd := &KongVersionCmd{Name: "testapp"} // Test that Run() doesn't return an error err := cmd.Run() if err != nil { t.Errorf("KongVersionCmd.Run() should not return error, got: %s", err) } } func TestRegisterMetric(t *testing.T) { // Create a test registry registry := prometheus.NewRegistry() // Test registering metric without name RegisterMetric("", registry) // Gather metrics metricFamilies, err := registry.Gather() if err != nil { t.Fatalf("Failed to gather metrics: %s", err) } // Find the build_info metric var buildInfoFamily *dto.MetricFamily for _, family := range metricFamilies { if family.GetName() == "build_info" { buildInfoFamily = family break } } if buildInfoFamily == nil { t.Fatal("build_info metric not found") } if buildInfoFamily.GetHelp() == "" { t.Error("build_info metric should have help text") } metrics := buildInfoFamily.GetMetric() if len(metrics) == 0 { t.Fatal("build_info metric should have at least one sample") } // Check that the metric has the expected labels metric := metrics[0] labels := metric.GetLabel() expectedLabels := []string{"version", "buildtime", "gittime", "git"} labelMap := make(map[string]string) for _, label := range labels { labelMap[label.GetName()] = label.GetValue() } for _, expectedLabel := range expectedLabels { if _, exists := labelMap[expectedLabel]; !exists { t.Errorf("Expected label %s not found in metric", expectedLabel) } } // Check that the metric value is 1 if metric.GetGauge().GetValue() != 1 { t.Errorf("Expected build_info metric value to be 1, got %f", metric.GetGauge().GetValue()) } } func TestRegisterMetricWithName(t *testing.T) { // Create a test registry registry := prometheus.NewRegistry() // Test registering metric with custom name appName := "my-test-app" RegisterMetric(appName, registry) // Gather metrics metricFamilies, err := registry.Gather() if err != nil { t.Fatalf("Failed to gather metrics: %s", err) } // Find the my_test_app_build_info metric expectedName := "my_test_app_build_info" var buildInfoFamily *dto.MetricFamily for _, family := range metricFamilies { if family.GetName() == expectedName { buildInfoFamily = family break } } if buildInfoFamily == nil { t.Fatalf("%s metric not found", expectedName) } } func TestVersionConsistency(t *testing.T) { // Call Version() multiple times and ensure it returns the same result v1 := Version() v2 := Version() if v1 != v2 { t.Errorf("Version() should return consistent results: %s != %s", v1, v2) } } func TestVersionInfoConsistency(t *testing.T) { // Ensure VersionInfo() is consistent with Version() info := VersionInfo() version := Version() // Version string should contain the semantic version if !strings.Contains(version, info.Version) { t.Errorf("Version() should contain VersionInfo().Version: %s not in %s", info.Version, version) } // If GitRevShort is set, version should contain it if info.GitRevShort != "" { if !strings.Contains(version, info.GitRevShort) { t.Errorf("Version() should contain GitRevShort: %s not in %s", info.GitRevShort, version) } } } // Test edge cases func TestCheckVersionEdgeCases(t *testing.T) { // Test with empty strings if CheckVersion("", "v1.0.0") { t.Error("Empty version should not be >= v1.0.0") } // Test with malformed versions (should be handled gracefully) // Note: semver.Compare might panic or return unexpected results for invalid versions // but our function should handle the common cases tests := []struct { version string minimum string desc string }{ {"v1.0.0/", "v1.0.0", "version with trailing slash"}, {"v1.0.0/abc/def", "v1.0.0", "version with multiple slashes"}, } for _, test := range tests { // This should not panic result := CheckVersion(test.version, test.minimum) t.Logf("%s: CheckVersion(%q, %q) = %t", test.desc, test.version, test.minimum, result) } } // Benchmark version operations func BenchmarkVersion(b *testing.B) { // Reset the cached version to test actual computation v = "" b.ResetTimer() for i := 0; i < b.N; i++ { _ = Version() } } func BenchmarkVersionInfo(b *testing.B) { for i := 0; i < b.N; i++ { _ = VersionInfo() } } func BenchmarkCheckVersion(b *testing.B) { version := "v1.2.3/abc123" minimum := "v1.2.0" b.ResetTimer() for i := 0; i < b.N; i++ { _ = CheckVersion(version, minimum) } } func BenchmarkCheckVersionDevSnapshot(b *testing.B) { version := "dev-snapshot" minimum := "v1.2.0" b.ResetTimer() for i := 0; i < b.N; i++ { _ = CheckVersion(version, minimum) } }