1package proxy
2
3import (
4	"strings"
5	"testing"
6	"time"
7
8	"github.com/hashicorp/consul/agent"
9	"github.com/hashicorp/consul/connect/proxy"
10	"github.com/mitchellh/cli"
11	"github.com/stretchr/testify/require"
12)
13
14func TestCommandConfigWatcher(t *testing.T) {
15	if testing.Short() {
16		t.Skip("too slow for testing.Short")
17	}
18
19	t.Parallel()
20
21	cases := []struct {
22		Name    string
23		Flags   []string
24		Test    func(*testing.T, *proxy.Config)
25		WantErr string
26	}{
27		{
28			Name:  "-service flag only",
29			Flags: []string{"-service", "web"},
30			Test: func(t *testing.T, cfg *proxy.Config) {
31				require.Equal(t, 0, cfg.PublicListener.BindPort)
32				require.Len(t, cfg.Upstreams, 0)
33			},
34		},
35
36		{
37			Name: "-service flag with upstreams",
38			Flags: []string{
39				"-service", "web",
40				"-upstream", "db:1234",
41				"-upstream", "db2:2345",
42			},
43			Test: func(t *testing.T, cfg *proxy.Config) {
44				require.Equal(t, 0, cfg.PublicListener.BindPort)
45				require.Len(t, cfg.Upstreams, 2)
46				require.Equal(t, 1234, cfg.Upstreams[0].LocalBindPort)
47				require.Equal(t, 2345, cfg.Upstreams[1].LocalBindPort)
48			},
49		},
50
51		{
52			Name:  "-service flag with -service-addr",
53			Flags: []string{"-service", "web"},
54			Test: func(t *testing.T, cfg *proxy.Config) {
55				// -service-addr has no affect since -listen isn't set
56				require.Equal(t, 0, cfg.PublicListener.BindPort)
57				require.Len(t, cfg.Upstreams, 0)
58			},
59		},
60
61		{
62			Name: "-service, -service-addr, -listen",
63			Flags: []string{
64				"-service", "web",
65				"-service-addr", "127.0.0.1:1234",
66				"-listen", ":4567",
67			},
68			Test: func(t *testing.T, cfg *proxy.Config) {
69				require.Len(t, cfg.Upstreams, 0)
70
71				require.Equal(t, "", cfg.PublicListener.BindAddress)
72				require.Equal(t, 4567, cfg.PublicListener.BindPort)
73				require.Equal(t, "127.0.0.1:1234", cfg.PublicListener.LocalServiceAddress)
74			},
75		},
76
77		{
78			Name: "-sidecar-for, no sidecar",
79			Flags: []string{
80				"-sidecar-for", "no-sidecar",
81			},
82			WantErr: "No sidecar proxy registered",
83		},
84
85		{
86			Name: "-sidecar-for, multiple sidecars",
87			Flags: []string{
88				"-sidecar-for", "two-sidecars",
89			},
90			// Order is non-deterministic so don't assert the list of proxy IDs here
91			WantErr: `More than one sidecar proxy registered for two-sidecars.
92    Start proxy with -proxy-id and one of the following IDs: `,
93		},
94
95		{
96			Name: "-sidecar-for, non-existent",
97			Flags: []string{
98				"-sidecar-for", "foo",
99			},
100			WantErr: "No sidecar proxy registered",
101		},
102
103		{
104			Name: "-sidecar-for, one sidecar",
105			Flags: []string{
106				"-sidecar-for", "one-sidecar",
107			},
108			Test: func(t *testing.T, cfg *proxy.Config) {
109				// Sanity check we got the right instance.
110				require.Equal(t, 9999, cfg.PublicListener.BindPort)
111			},
112		},
113	}
114
115	for _, tc := range cases {
116		t.Run(tc.Name, func(t *testing.T) {
117			require := require.New(t)
118
119			// Register a few services with 0, 1 and 2 sidecars
120			a := agent.NewTestAgent(t, `
121			services {
122				name = "no-sidecar"
123				port = 1111
124			}
125			services {
126				name = "one-sidecar"
127				port = 2222
128				connect {
129					sidecar_service {
130						port = 9999
131					}
132				}
133			}
134			services {
135				name = "two-sidecars"
136				port = 3333
137				connect {
138					sidecar_service {}
139				}
140			}
141			services {
142				kind = "connect-proxy"
143				name = "other-sidecar-for-two-sidecars"
144				port = 4444
145				proxy {
146					destination_service_id = "two-sidecars"
147					destination_service_name = "two-sidecars"
148				}
149			}
150			`)
151			defer a.Shutdown()
152			client := a.Client()
153
154			ui := cli.NewMockUi()
155			c := New(ui, make(chan struct{}))
156			c.testNoStart = true
157
158			// Run the command
159			code := c.Run(append([]string{
160				"-http-addr=" + a.HTTPAddr(),
161			}, tc.Flags...))
162			if tc.WantErr == "" {
163				require.Equal(0, code, ui.ErrorWriter.String())
164			} else {
165				require.Equal(1, code, ui.ErrorWriter.String())
166				require.Contains(ui.ErrorWriter.String(), tc.WantErr)
167				return
168			}
169
170			// Get the configuration watcher
171			cw, err := c.configWatcher(client)
172			require.NoError(err)
173			if tc.Test != nil {
174				tc.Test(t, testConfig(t, cw))
175			}
176		})
177	}
178}
179
180func testConfig(t *testing.T, cw proxy.ConfigWatcher) *proxy.Config {
181	t.Helper()
182
183	select {
184	case cfg := <-cw.Watch():
185		return cfg
186
187	case <-time.After(1 * time.Second):
188		t.Fatal("no configuration loaded")
189		return nil // satisfy compiler
190	}
191}
192
193func TestCatalogCommand_noTabs(t *testing.T) {
194	t.Parallel()
195	if strings.ContainsRune(New(nil, nil).Help(), '\t') {
196		t.Fatal("help has tabs")
197	}
198}
199