1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package config
16
17import (
18	"errors"
19	"fmt"
20	"testing"
21
22	"github.com/stretchr/testify/assert"
23)
24
25var errInvalidRecvConfig = errors.New("invalid receiver config")
26var errInvalidExpConfig = errors.New("invalid exporter config")
27var errInvalidProcConfig = errors.New("invalid processor config")
28var errInvalidExtConfig = errors.New("invalid extension config")
29
30type nopRecvConfig struct {
31	ReceiverSettings
32}
33
34func (nc *nopRecvConfig) Validate() error {
35	if nc.ID() != NewID("nop") {
36		return errInvalidRecvConfig
37	}
38	return nil
39}
40
41type nopExpConfig struct {
42	ExporterSettings
43}
44
45func (nc *nopExpConfig) Validate() error {
46	if nc.ID() != NewID("nop") {
47		return errInvalidExpConfig
48	}
49	return nil
50}
51
52type nopProcConfig struct {
53	ProcessorSettings
54}
55
56func (nc *nopProcConfig) Validate() error {
57	if nc.ID() != NewID("nop") {
58		return errInvalidProcConfig
59	}
60	return nil
61}
62
63type nopExtConfig struct {
64	ExtensionSettings
65}
66
67func (nc *nopExtConfig) Validate() error {
68	if nc.ID() != NewID("nop") {
69		return errInvalidExtConfig
70	}
71	return nil
72}
73
74func TestConfigValidate(t *testing.T) {
75	var testCases = []struct {
76		name     string // test case name (also file name containing config yaml)
77		cfgFn    func() *Config
78		expected error
79	}{
80		{
81			name:     "valid",
82			cfgFn:    generateConfig,
83			expected: nil,
84		},
85		{
86			name: "missing-exporters",
87			cfgFn: func() *Config {
88				cfg := generateConfig()
89				cfg.Exporters = nil
90				return cfg
91			},
92			expected: errMissingExporters,
93		},
94		{
95			name: "missing-receivers",
96			cfgFn: func() *Config {
97				cfg := generateConfig()
98				cfg.Receivers = nil
99				return cfg
100			},
101			expected: errMissingReceivers,
102		},
103		{
104			name: "invalid-extension-reference",
105			cfgFn: func() *Config {
106				cfg := generateConfig()
107				cfg.Service.Extensions = append(cfg.Service.Extensions, NewIDWithName("nop", "2"))
108				return cfg
109			},
110			expected: errors.New(`service references extension "nop/2" which does not exist`),
111		},
112		{
113			name: "invalid-receiver-reference",
114			cfgFn: func() *Config {
115				cfg := generateConfig()
116				pipe := cfg.Service.Pipelines["traces"]
117				pipe.Receivers = append(pipe.Receivers, NewIDWithName("nop", "2"))
118				return cfg
119			},
120			expected: errors.New(`pipeline "traces" references receiver "nop/2" which does not exist`),
121		},
122		{
123			name: "invalid-processor-reference",
124			cfgFn: func() *Config {
125				cfg := generateConfig()
126				pipe := cfg.Service.Pipelines["traces"]
127				pipe.Processors = append(pipe.Processors, NewIDWithName("nop", "2"))
128				return cfg
129			},
130			expected: errors.New(`pipeline "traces" references processor "nop/2" which does not exist`),
131		},
132		{
133			name: "invalid-exporter-reference",
134			cfgFn: func() *Config {
135				cfg := generateConfig()
136				pipe := cfg.Service.Pipelines["traces"]
137				pipe.Exporters = append(pipe.Exporters, NewIDWithName("nop", "2"))
138				return cfg
139			},
140			expected: errors.New(`pipeline "traces" references exporter "nop/2" which does not exist`),
141		},
142		{
143			name: "missing-pipeline-receivers",
144			cfgFn: func() *Config {
145				cfg := generateConfig()
146				pipe := cfg.Service.Pipelines["traces"]
147				pipe.Receivers = nil
148				return cfg
149			},
150			expected: errors.New(`pipeline "traces" must have at least one receiver`),
151		},
152		{
153			name: "missing-pipeline-exporters",
154			cfgFn: func() *Config {
155				cfg := generateConfig()
156				pipe := cfg.Service.Pipelines["traces"]
157				pipe.Exporters = nil
158				return cfg
159			},
160			expected: errors.New(`pipeline "traces" must have at least one exporter`),
161		},
162		{
163			name: "missing-pipelines",
164			cfgFn: func() *Config {
165				cfg := generateConfig()
166				cfg.Service.Pipelines = nil
167				return cfg
168			},
169			expected: errMissingServicePipelines,
170		},
171		{
172			name: "invalid-receiver-config",
173			cfgFn: func() *Config {
174				cfg := generateConfig()
175				cfg.Receivers[NewID("nop")] = &nopRecvConfig{
176					ReceiverSettings: NewReceiverSettings(NewID("invalid_rec_type")),
177				}
178				return cfg
179			},
180			expected: fmt.Errorf(`receiver "nop" has invalid configuration: %w`, errInvalidRecvConfig),
181		},
182		{
183			name: "invalid-exporter-config",
184			cfgFn: func() *Config {
185				cfg := generateConfig()
186				cfg.Exporters[NewID("nop")] = &nopExpConfig{
187					ExporterSettings: NewExporterSettings(NewID("invalid_rec_type")),
188				}
189				return cfg
190			},
191			expected: fmt.Errorf(`exporter "nop" has invalid configuration: %w`, errInvalidExpConfig),
192		},
193		{
194			name: "invalid-processor-config",
195			cfgFn: func() *Config {
196				cfg := generateConfig()
197				cfg.Processors[NewID("nop")] = &nopProcConfig{
198					ProcessorSettings: NewProcessorSettings(NewID("invalid_rec_type")),
199				}
200				return cfg
201			},
202			expected: fmt.Errorf(`processor "nop" has invalid configuration: %w`, errInvalidProcConfig),
203		},
204		{
205			name: "invalid-extension-config",
206			cfgFn: func() *Config {
207				cfg := generateConfig()
208				cfg.Extensions[NewID("nop")] = &nopExtConfig{
209					ExtensionSettings: NewExtensionSettings(NewID("invalid_rec_type")),
210				}
211				return cfg
212			},
213			expected: fmt.Errorf(`extension "nop" has invalid configuration: %w`, errInvalidExtConfig),
214		},
215	}
216
217	for _, test := range testCases {
218		t.Run(test.name, func(t *testing.T) {
219			cfg := test.cfgFn()
220			assert.Equal(t, test.expected, cfg.Validate())
221		})
222	}
223}
224
225func generateConfig() *Config {
226	return &Config{
227		Receivers: map[ComponentID]Receiver{
228			NewID("nop"): &nopRecvConfig{
229				ReceiverSettings: NewReceiverSettings(NewID("nop")),
230			},
231		},
232		Exporters: map[ComponentID]Exporter{
233			NewID("nop"): &nopExpConfig{
234				ExporterSettings: NewExporterSettings(NewID("nop")),
235			},
236		},
237		Processors: map[ComponentID]Processor{
238			NewID("nop"): &nopProcConfig{
239				ProcessorSettings: NewProcessorSettings(NewID("nop")),
240			},
241		},
242		Extensions: map[ComponentID]Extension{
243			NewID("nop"): &nopExtConfig{
244				ExtensionSettings: NewExtensionSettings(NewID("nop")),
245			},
246		},
247		Service: Service{
248			Extensions: []ComponentID{NewID("nop")},
249			Pipelines: map[string]*Pipeline{
250				"traces": {
251					Name:       "traces",
252					InputType:  TracesDataType,
253					Receivers:  []ComponentID{NewID("nop")},
254					Processors: []ComponentID{NewID("nop")},
255					Exporters:  []ComponentID{NewID("nop")},
256				},
257			},
258		},
259	}
260}
261