merge_test.go

  1package config
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"io"
  7	"maps"
  8	"slices"
  9	"testing"
 10
 11	"github.com/stretchr/testify/require"
 12)
 13
 14// TestConfigMerging defines the rules on how configuration merging works.
 15// Generally, things are either appended to or replaced by the later configuration.
 16// Whether one or the other happen depends on effects its effects.
 17func TestConfigMerging(t *testing.T) {
 18	t.Run("empty", func(t *testing.T) {
 19		c := exerciseMerge(t, Config{}, Config{})
 20		require.NotNil(t, c)
 21	})
 22
 23	t.Run("mcps", func(t *testing.T) {
 24		c := exerciseMerge(t, Config{
 25			MCP: MCPs{
 26				"foo": {
 27					Command: "foo-mcp",
 28					Args:    []string{"serve"},
 29					Type:    MCPSSE,
 30					Timeout: 10,
 31				},
 32				"zaz": {
 33					Disabled: true,
 34					Env:      map[string]string{"FOO": "bar"},
 35					Headers:  map[string]string{"api-key": "exposed"},
 36					URL:      "nope",
 37				},
 38			},
 39		}, Config{
 40			MCP: MCPs{
 41				"foo": {
 42					Args:    []string{"serve", "--stdio"},
 43					Type:    MCPStdio,
 44					Timeout: 7,
 45				},
 46				"bar": {
 47					Command: "bar",
 48				},
 49				"zaz": {
 50					Env:     map[string]string{"FOO": "foo", "BAR": "bar"},
 51					Headers: map[string]string{"api-key": "$API"},
 52					URL:     "http://bar",
 53				},
 54			},
 55		})
 56		require.NotNil(t, c)
 57		require.Len(t, slices.Collect(maps.Keys(c.MCP)), 3)
 58		require.Equal(t, MCPConfig{
 59			Command: "foo-mcp",
 60			Args:    []string{"serve", "--stdio"},
 61			Type:    MCPStdio,
 62			Timeout: 10,
 63		}, c.MCP["foo"])
 64		require.Equal(t, MCPConfig{
 65			Command: "bar",
 66		}, c.MCP["bar"])
 67		require.Equal(t, MCPConfig{
 68			Disabled: true,
 69			URL:      "http://bar",
 70			Env:      map[string]string{"FOO": "foo", "BAR": "bar"},
 71			Headers:  map[string]string{"api-key": "$API"},
 72		}, c.MCP["zaz"])
 73	})
 74
 75	t.Run("lsps", func(t *testing.T) {
 76		result := exerciseMerge(t, Config{
 77			LSP: LSPs{
 78				"gopls": LSPConfig{
 79					Env:         map[string]string{"FOO": "bar"},
 80					RootMarkers: []string{"go.sum"},
 81					FileTypes:   []string{"go"},
 82				},
 83			},
 84		}, Config{
 85			LSP: LSPs{
 86				"gopls": LSPConfig{
 87					Command:     "gopls",
 88					InitOptions: map[string]any{"a": 10},
 89					RootMarkers: []string{"go.sum"},
 90				},
 91			},
 92		}, Config{
 93			LSP: LSPs{
 94				"gopls": LSPConfig{
 95					Args:        []string{"serve", "--stdio"},
 96					InitOptions: map[string]any{"a": 12, "b": 18},
 97					RootMarkers: []string{"go.sum", "go.mod"},
 98					FileTypes:   []string{"go"},
 99					Disabled:    true,
100				},
101			},
102		},
103			Config{
104				LSP: LSPs{
105					"gopls": LSPConfig{
106						Options:     map[string]any{"opt1": "10"},
107						RootMarkers: []string{"go.work"},
108					},
109				},
110			},
111		)
112		require.NotNil(t, result)
113		require.Equal(t, LSPConfig{
114			Disabled:    true,
115			Command:     "gopls",
116			Args:        []string{"serve", "--stdio"},
117			Env:         map[string]string{"FOO": "bar"},
118			FileTypes:   []string{"go"},
119			RootMarkers: []string{"go.mod", "go.sum", "go.work"},
120			InitOptions: map[string]any{"a": 12.0, "b": 18.0},
121			Options:     map[string]any{"opt1": "10"},
122		}, result.LSP["gopls"])
123	})
124}
125
126func exerciseMerge(tb testing.TB, confs ...Config) *Config {
127	tb.Helper()
128	readers := make([]io.Reader, 0, len(confs))
129	for _, c := range confs {
130		bts, err := json.Marshal(c)
131		require.NoError(tb, err)
132		readers = append(readers, bytes.NewReader(bts))
133	}
134	result, err := loadFromReaders(readers)
135	require.NoError(tb, err)
136	return result
137}