using MaksIT.ClusterConsole.Shared; namespace MaksIT.ClusterConsole.Tests; public class ConfigurationFileServiceTests { [Fact] public void Default_selected_namespace_is_all() { Assert.Equal(Configuration.AllNamespaces, new Configuration().SelectedNamespace); } [Fact] public void NamespaceFor_uses_per_context_then_legacy_selected() { var cfg = new Configuration { SelectedNamespace = "default" }; Assert.Equal("default", cfg.NamespaceFor("missing")); cfg.SetNamespace("prod", "kube-system"); Assert.Equal("kube-system", cfg.NamespaceFor("prod")); Assert.Equal("kube-system", cfg.SelectedNamespace); Assert.Equal("prod", cfg.ActiveContext); Assert.Equal(Configuration.AllNamespaces, cfg.NamespaceFor("dev")); } [Fact] public void Save_round_trips_selected_namespace_and_keeps_logging() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all" } } """); try { var service = new ConfigurationFileService(path); Assert.Equal("all", service.Current.SelectedNamespace); service.Save(new Configuration { SelectedNamespace = "kube-system" }); var reloaded = new ConfigurationFileService(path); Assert.Equal("kube-system", reloaded.Current.SelectedNamespace); Assert.Contains("\"Logging\"", File.ReadAllText(path), StringComparison.Ordinal); } finally { File.Delete(path); } } [Fact] public void Save_keeps_open_contexts_when_updating_namespace() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all", "ActiveContext": "prod", "OpenContexts": [ "prod", "dev" ] } } """); try { var service = new ConfigurationFileService(path); var cfg = service.Current; Assert.Equal(["prod", "dev"], cfg.OpenContexts); cfg.SetNamespace("prod", "kube-system"); service.Save(cfg); var reloaded = new ConfigurationFileService(path); Assert.Equal(["prod", "dev"], reloaded.Current.OpenContexts); Assert.Equal("prod", reloaded.Current.ActiveContext); Assert.Equal("kube-system", reloaded.Current.NamespaceFor("prod")); Assert.Contains("\"Logging\"", File.ReadAllText(path), StringComparison.Ordinal); } finally { File.Delete(path); } } [Fact] public void IsNavigatorExpanded_defaults_to_collapsed() { var cfg = new Configuration(); Assert.False(cfg.IsNavigatorExpanded("Workloads")); Assert.False(cfg.IsNavigatorExpanded("Custom Resources/cilium.io")); } [Fact] public void Save_round_trips_navigator_expanded() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all" } } """); try { var service = new ConfigurationFileService(path); var cfg = service.Current; cfg.SetNavigatorExpanded(new Dictionary { ["Workloads"] = true, ["Config"] = false, ["Custom Resources/cilium.io"] = true }); service.Save(cfg); var json = File.ReadAllText(path); Assert.Contains("\"NavigatorExpanded\"", json, StringComparison.Ordinal); Assert.Contains("\"Workloads\"", json, StringComparison.Ordinal); var reloaded = new ConfigurationFileService(path); Assert.True(reloaded.Current.IsNavigatorExpanded("Workloads")); Assert.False(reloaded.Current.IsNavigatorExpanded("Config")); Assert.True(reloaded.Current.IsNavigatorExpanded("Custom Resources/cilium.io")); Assert.False(reloaded.Current.IsNavigatorExpanded("Storage")); Assert.Equal("all", reloaded.Current.SelectedNamespace); } finally { File.Delete(path); } } [Fact] public void Save_round_trips_layout_without_dropping_open_contexts() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all", "OpenContexts": [ "prod" ] } } """); try { var service = new ConfigurationFileService(path); var cfg = service.Current; cfg.Layout.WindowWidth = 1600; cfg.Layout.WindowHeight = 900; cfg.Layout.WindowX = 40; cfg.Layout.WindowY = 80; cfg.Layout.WindowState = "Maximized"; cfg.Layout.CatalogWidth = 260; cfg.Layout.NavigatorWidth = 200; cfg.Layout.DetailsWidth = 420; cfg.Layout.ShoulderCollapsed = true; cfg.Layout.HelmHistoryHeight = 280; cfg.Layout.SetColumns("resources/pods", new Dictionary { ["Name"] = 220, ["Namespace"] = 140 }); service.Save(cfg); var reloaded = new ConfigurationFileService(path); Assert.Equal(["prod"], reloaded.Current.OpenContexts); Assert.Equal(1600, reloaded.Current.Layout.WindowWidth); Assert.Equal(900, reloaded.Current.Layout.WindowHeight); Assert.Equal(40, reloaded.Current.Layout.WindowX); Assert.Equal(80, reloaded.Current.Layout.WindowY); Assert.Equal("Maximized", reloaded.Current.Layout.WindowState); Assert.Equal(260, reloaded.Current.Layout.CatalogWidth); Assert.Equal(200, reloaded.Current.Layout.NavigatorWidth); Assert.Equal(420, reloaded.Current.Layout.DetailsWidth); Assert.True(reloaded.Current.Layout.ShoulderCollapsed); Assert.Equal(280, reloaded.Current.Layout.HelmHistoryHeight); var columns = reloaded.Current.Layout.ColumnsFor("resources/pods"); Assert.NotNull(columns); Assert.Equal(220, columns["Name"]); Assert.Equal(140, columns["Namespace"]); Assert.Contains("\"Logging\"", File.ReadAllText(path), StringComparison.Ordinal); } finally { File.Delete(path); } } [Fact] public void Save_round_trips_port_forwards() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all" } } """); try { var service = new ConfigurationFileService(path); var cfg = service.Current; cfg.Forwards.Upsert(new PersistedPortForward { Context = "homelab", Kind = "Service", Name = "postgres", Namespace = "postgresql", PodName = "postgres-0", LocalPort = 5432, RemotePort = 5432 }); cfg.Forwards.Upsert(new PersistedPortForward { Context = "homelab", Kind = "Service", Name = "postgres", Namespace = "postgresql", PodName = "postgres-1", LocalPort = 5432, RemotePort = 5432 }); cfg.Forwards.Upsert(new PersistedPortForward { Context = "dev", Kind = "Pod", Name = "web", Namespace = "apps", PodName = "web", LocalPort = 8080, RemotePort = 80, MatchLabels = new Dictionary { ["app"] = "web" } }); service.Save(cfg); var reloaded = new ConfigurationFileService(path); var homelab = reloaded.Current.Forwards.For("homelab"); Assert.Single(homelab); Assert.Equal("postgres-1", homelab[0].PodName); Assert.Equal(5432, homelab[0].LocalPort); Assert.Equal("Service", homelab[0].Kind); reloaded.Current.Forwards.Remove("homelab", 5432); reloaded.Save(reloaded.Current); Assert.Empty(new ConfigurationFileService(path).Current.Forwards.For("homelab")); var dev = new ConfigurationFileService(path).Current.Forwards.For("dev"); Assert.Single(dev); Assert.Equal("web", dev[0].MatchLabels!["app"]); } finally { File.Delete(path); } } [Fact] public void PortForwardRestoreSummary_formats_success_and_failure() { Assert.Equal("Restored 1 port-forward.", new PortForwardRestoreSummary(1, []).Format()); Assert.Equal("Restored 3 port-forwards.", new PortForwardRestoreSummary(3, []).Format()); Assert.Equal( "Port-forward restore failed: localhost:8080 (pod not found)", new PortForwardRestoreSummary(0, ["localhost:8080 (pod not found)"]).Format()); Assert.Equal( "Restored 2 port-forward(s); 1 failed: localhost:80 (address in use)", new PortForwardRestoreSummary(2, ["localhost:80 (address in use)"]).Format()); } [Fact] public void Default_path_is_appdata_under_maksit() { var service = new ConfigurationFileService(); Assert.Equal(UserSettingsPath.Get(ConfigurationFileService.ProductFolder), service.FilePath); } [Fact] public void Save_to_new_file_writes_only_configuration() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); try { var service = new ConfigurationFileService(path); service.Save(new Configuration { SelectedNamespace = "kube-system" }); var json = File.ReadAllText(path); Assert.Contains("\"Configuration\"", json, StringComparison.Ordinal); Assert.DoesNotContain("\"Logging\"", json, StringComparison.Ordinal); Assert.Equal("kube-system", new ConfigurationFileService(path).Current.SelectedNamespace); } finally { if (File.Exists(path)) File.Delete(path); } } [Fact] public void Copies_seed_configuration_without_logging() { var dir = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}"); Directory.CreateDirectory(dir); var seed = Path.Combine(dir, "appsettings.json"); var user = Path.Combine(dir, "settings.json"); File.WriteAllText(seed, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "kube-system" } } """); try { var service = new ConfigurationFileService(user, seed); Assert.True(File.Exists(user)); Assert.Equal("kube-system", service.Current.SelectedNamespace); var json = File.ReadAllText(user); Assert.Contains("\"Configuration\"", json, StringComparison.Ordinal); Assert.DoesNotContain("\"Logging\"", json, StringComparison.Ordinal); } finally { Directory.Delete(dir, true); } } [Fact] public void Logging_only_seed_does_not_create_user_file() { var dir = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}"); Directory.CreateDirectory(dir); var seed = Path.Combine(dir, "appsettings.json"); var user = Path.Combine(dir, "settings.json"); File.WriteAllText(seed, """ { "Logging": { "LogLevel": { "Default": "Information" } } } """); try { var service = new ConfigurationFileService(user, seed); Assert.False(File.Exists(user)); Assert.Equal(Configuration.AllNamespaces, service.Current.SelectedNamespace); } finally { Directory.Delete(dir, true); } } [Fact] public void Save_round_trips_filters_search_and_selected_nav() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all" } } """); try { var service = new ConfigurationFileService(path); var cfg = service.Current; cfg.Layout.SelectedNavId = "deployments"; cfg.Layout.SetSearch("pods", "coredns"); cfg.Layout.SetFilters("resources/pods", new Dictionary { ["Namespace"] = new() { Text = "", Excluded = ["default", "kube-system"] }, ["Status"] = new() { Text = "Crash" } }); service.Save(cfg); var reloaded = new ConfigurationFileService(path); Assert.Equal("deployments", reloaded.Current.Layout.SelectedNavId); Assert.Equal("coredns", reloaded.Current.Layout.SearchFor("pods")); Assert.Equal("", reloaded.Current.Layout.SearchFor("deployments")); var ns = reloaded.Current.Layout.FilterFor("resources/pods", "Namespace"); Assert.NotNull(ns); Assert.Equal(["default", "kube-system"], ns.Excluded); Assert.Equal("Crash", reloaded.Current.Layout.FilterFor("resources/pods", "Status")?.Text); } finally { File.Delete(path); } } [Fact] public void Table_filters_and_sort_are_stored_per_context() { var layout = new LayoutSettings(); layout.SetFilters("resources/pods", new Dictionary { ["Status"] = new() { Text = "legacy" } }); layout.SetFilters("prod", "resources/pods", new Dictionary { ["Status"] = new() { Text = "Crash" } }); layout.SetSort("prod", "resources/pods", new SavedColumnSort { Header = "Age", Direction = "Descending" }); layout.SetColumns("prod", "resources/pods", new Dictionary { ["Name"] = 240 }); Assert.Equal("Crash", layout.FilterFor("prod", "resources/pods", "Status")?.Text); Assert.Equal("legacy", layout.FilterFor("dev", "resources/pods", "Status")?.Text); Assert.Equal("Age", layout.SortFor("prod", "resources/pods")?.Header); Assert.Equal("Descending", layout.SortFor("prod", "resources/pods")?.Direction); Assert.Null(layout.SortFor("dev", "resources/pods")); Assert.Equal(240, layout.ColumnsFor("prod", "resources/pods")!["Name"]); Assert.Null(layout.ColumnsFor("dev", "resources/pods")); Assert.Equal("prod/resources/pods", LayoutSettings.ContextTable("prod", "resources/pods")); } [Fact] public void Save_round_trips_per_context_sort_and_filters() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "SelectedNamespace": "all" } } """); try { var service = new ConfigurationFileService(path); var cfg = service.Current; cfg.Layout.SetFilters("homelab", "resources/pods", new Dictionary { ["Namespace"] = new() { Excluded = ["default"] } }); cfg.Layout.SetSort("homelab", "resources/pods", new SavedColumnSort { Header = "Age", Direction = "Descending" }); service.Save(cfg); var reloaded = new ConfigurationFileService(path); Assert.Equal(["default"], reloaded.Current.Layout.FilterFor("homelab", "resources/pods", "Namespace")?.Excluded); Assert.Null(reloaded.Current.Layout.FilterFor("dev", "resources/pods", "Namespace")); var sort = reloaded.Current.Layout.SortFor("homelab", "resources/pods"); Assert.NotNull(sort); Assert.Equal("Age", sort.Header); Assert.Equal("Descending", sort.Direction); Assert.Contains("\"Tables\"", File.ReadAllText(path), StringComparison.Ordinal); Assert.DoesNotContain("\"ColumnSorts\"", File.ReadAllText(path), StringComparison.Ordinal); } finally { File.Delete(path); } } [Fact] public void Migrates_legacy_column_maps_into_tables() { var path = Path.Combine(Path.GetTempPath(), $"maksit-cluster-console-{Guid.NewGuid():N}.json"); File.WriteAllText(path, """ { "Logging": { "LogLevel": { "Default": "Information" } }, "Configuration": { "Layout": { "ColumnWidths": { "resources/pods": { "Name": 220 } }, "ColumnFilters": { "homelab/resources/pods": { "Status": { "Text": "Crash", "Excluded": [] } } }, "ColumnSorts": { "homelab/resources/pods": { "Header": "Age", "Direction": "Descending" } }, "SearchByResource": { "resources/pods": "coredns" } } } } """); try { var service = new ConfigurationFileService(path); var layout = service.Current.Layout; Assert.Equal(220, layout.ColumnsFor("resources/pods")!["Name"]); Assert.Equal("Crash", layout.FilterFor("homelab", "resources/pods", "Status")?.Text); Assert.Equal("Age", layout.SortFor("homelab", "resources/pods")?.Header); Assert.Equal("coredns", layout.SearchFor("pods")); service.Save(service.Current); var json = File.ReadAllText(path); Assert.Contains("\"Tables\"", json, StringComparison.Ordinal); Assert.DoesNotContain("\"ColumnWidths\"", json, StringComparison.Ordinal); Assert.DoesNotContain("\"ColumnFilters\"", json, StringComparison.Ordinal); Assert.DoesNotContain("\"ColumnSorts\"", json, StringComparison.Ordinal); Assert.DoesNotContain("\"SearchByResource\"", json, StringComparison.Ordinal); var reloaded = new ConfigurationFileService(path); Assert.Equal("Crash", reloaded.Current.Layout.FilterFor("homelab", "resources/pods", "Status")?.Text); Assert.Equal("coredns", reloaded.Current.Layout.SearchFor("pods")); } finally { File.Delete(path); } } [Fact] public void ClearFiltersAndSort_keeps_column_widths() { var layout = new LayoutSettings(); layout.SetColumns("homelab", "resources/pods", new Dictionary { ["Name"] = 240 }); layout.SetFilters("homelab", "resources/pods", new Dictionary { ["Namespace"] = new() { Excluded = ["kube-system"] } }); layout.SetSort("homelab", "resources/pods", new SavedColumnSort { Header = "Age", Direction = "Descending" }); layout.SetSearch("homelab", "pods", "coredns"); layout.ClearFiltersAndSort(); Assert.Equal(240, layout.ColumnsFor("homelab", "resources/pods")!["Name"]); Assert.Null(layout.FilterFor("homelab", "resources/pods", "Namespace")); Assert.Null(layout.SortFor("homelab", "resources/pods")); Assert.Equal("", layout.SearchFor("homelab", "pods")); } [Fact] public void UseAllNamespaces_clears_saved_scopes() { var cfg = new Configuration(); cfg.SetNamespace("prod", "kube-system"); cfg.UseAllNamespaces(); Assert.Equal("prod", cfg.ActiveContext); Assert.Equal(Configuration.AllNamespaces, cfg.NamespaceFor("prod")); Assert.Equal(Configuration.AllNamespaces, cfg.SelectedNamespace); } }