@@ -3,102 +3,300 @@ package database
33import (
44 "github.com/creasty/defaults"
55 "github.com/icinga/icinga-go-library/config"
6+ "github.com/icinga/icinga-go-library/testutils"
67 "github.com/stretchr/testify/require"
8+ "os"
79 "testing"
810)
911
12+ // minimalYaml is a constant string representing a minimal valid YAML configuration for
13+ // connecting to a PostgreSQL database. PostgreSQL is explicitly chosen here to
14+ // test whether the default type (which is MySQL) is correctly overridden.
15+ const minimalYaml = `
16+ type: pgsql
17+ host: localhost
18+ user: icinga
19+ database: icingadb
20+ password: secret`
21+
22+ // minimalEnv returns a map of environment variables representing a minimal valid configuration for
23+ // connecting to a PostgreSQL database. PostgreSQL is explicitly chosen here to
24+ // test whether the default type (which is MySQL) is correctly overridden.
25+ func minimalEnv () map [string ]string {
26+ return map [string ]string {
27+ "TYPE" : "pgsql" ,
28+ "HOST" : "localhost" ,
29+ "USER" : "icinga" ,
30+ "DATABASE" : "icingadb" ,
31+ "PASSWORD" : "secret" ,
32+ }
33+ }
34+
35+ // withMinimalEnv takes a map of environment variables and merges it with the
36+ // minimal environment configuration returned from minimalEnv,
37+ // overriding any existing keys with the provided values.
38+ // It returns the resulting map.
39+ func withMinimalEnv (v map [string ]string ) map [string ]string {
40+ env := minimalEnv ()
41+
42+ for key , value := range v {
43+ env [key ] = value
44+ }
45+
46+ return env
47+ }
48+
1049func TestConfig (t * testing.T ) {
1150 var defaultOptions Options
1251 require .NoError (t , defaults .Set (& defaultOptions ), "setting default options" )
1352
14- subtests := []struct {
15- name string
16- opts config.EnvOptions
17- expected Config
18- error bool
19- }{
53+ configTests := []testutils.TestCase [Config , testutils.ConfigTestData ]{
54+ {
55+ Name : "Unknown database type" ,
56+ Data : testutils.ConfigTestData {
57+ Yaml : `type: invalid` ,
58+ Env : map [string ]string {"TYPE" : "invalid" },
59+ },
60+ Error : testutils .ErrorContains (`unknown database type "invalid"` ),
61+ },
62+ {
63+ Name : "Database host missing" ,
64+ Data : testutils.ConfigTestData {
65+ Yaml : `type: pgsql` ,
66+ Env : map [string ]string {"TYPE" : "pgsql" },
67+ },
68+ Error : testutils .ErrorContains ("database host missing" ),
69+ },
2070 {
21- name : "empty-missing-fields" ,
22- opts : config.EnvOptions {},
23- error : true ,
71+ Name : "Database user missing" ,
72+ Data : testutils.ConfigTestData {
73+ Yaml : `
74+ type: pgsql
75+ host: localhost` ,
76+ Env : map [string ]string {
77+ "TYPE" : "pgsql" ,
78+ "HOST" : "localhost" ,
79+ },
80+ },
81+ Error : testutils .ErrorContains ("database user missing" ),
82+ },
83+ {
84+ Name : "Database name missing" ,
85+ Data : testutils.ConfigTestData {
86+ Yaml : `
87+ type: pgsql
88+ host: localhost
89+ user: icinga` ,
90+ Env : map [string ]string {
91+ "TYPE" : "pgsql" ,
92+ "HOST" : "localhost" ,
93+ "USER" : "icinga" ,
94+ },
95+ },
96+ Error : testutils .ErrorContains ("database name missing" ),
2497 },
2598 {
26- name : "unknown-db-type" ,
27- opts : config.EnvOptions {Environment : map [string ]string {"TYPE" : "☃" }},
28- error : true ,
99+ Name : "Minimal config" ,
100+ Data : testutils.ConfigTestData {
101+ Yaml : minimalYaml ,
102+ Env : minimalEnv (),
103+ },
104+ Expected : Config {
105+ Type : "pgsql" ,
106+ Host : "localhost" ,
107+ User : "icinga" ,
108+ Database : "icingadb" ,
109+ Password : "secret" ,
110+ Options : defaultOptions ,
111+ },
29112 },
30113 {
31- name : "minimal-config" ,
32- opts : config.EnvOptions {Environment : map [string ]string {
33- "HOST" : "db.example.com" ,
34- "USER" : "user" ,
35- "DATABASE" : "db" ,
36- }},
37- expected : Config {
38- Type : "mysql" ,
39- Host : "db.example.com" ,
40- Database : "db" ,
41- User : "user" ,
114+ Name : "Retain defaults" ,
115+ Data : testutils.ConfigTestData {
116+ Yaml : `
117+ host: localhost
118+ user: icinga
119+ database: icinga` ,
120+ Env : map [string ]string {
121+ "HOST" : "localhost" ,
122+ "USER" : "icinga" ,
123+ "DATABASE" : "icinga" ,
124+ },
125+ },
126+ Expected : Config {
127+ Type : "mysql" , // Default
128+ Host : "localhost" ,
129+ User : "icinga" ,
130+ Database : "icinga" ,
42131 Options : defaultOptions ,
43132 },
44133 },
45134 {
46- name : "tls" ,
47- opts : config.EnvOptions {Environment : map [string ]string {
48- "HOST" : "db.example.com" ,
49- "USER" : "user" ,
50- "DATABASE" : "db" ,
51- "TLS" : "true" ,
52- "CERT" : "/var/empty/db.crt" ,
53- "CA" : "/var/empty/ca.crt" ,
54- }},
55- expected : Config {
56- Type : "mysql" ,
57- Host : "db.example.com" ,
58- Database : "db" ,
59- User : "user" ,
135+ Name : "TLS" ,
136+ Data : testutils.ConfigTestData {
137+ Yaml : minimalYaml + `
138+ tls: true
139+ cert: cert.pem
140+ key: key.pem
141+ ca: ca.pem` ,
142+ Env : withMinimalEnv (map [string ]string {
143+ "TLS" : "1" ,
144+ "CERT" : "cert.pem" ,
145+ "KEY" : "key.pem" ,
146+ "CA" : "ca.pem" ,
147+ }),
148+ },
149+ Expected : Config {
150+ Type : "pgsql" ,
151+ Host : "localhost" ,
152+ User : "icinga" ,
153+ Database : "icingadb" ,
154+ Password : "secret" ,
155+ Options : defaultOptions ,
60156 TlsOptions : config.TLS {
61157 Enable : true ,
62- Cert : "/var/empty/db.crt" ,
63- Ca : "/var/empty/ca.crt" ,
158+ Cert : "cert.pem" ,
159+ Key : "key.pem" ,
160+ Ca : "ca.pem" ,
64161 },
65- Options : defaultOptions ,
66162 },
67163 },
68164 {
69- name : "options" ,
70- opts : config.EnvOptions {Environment : map [string ]string {
71- "HOST" : "db.example.com" ,
72- "USER" : "user" ,
73- "DATABASE" : "db" ,
74- "OPTIONS_MAX_CONNECTIONS" : "1" ,
75- "OPTIONS_MAX_ROWS_PER_TRANSACTION" : "65535" ,
76- }},
77- expected : Config {
78- Type : "mysql" ,
79- Host : "db.example.com" ,
80- Database : "db" ,
81- User : "user" ,
165+ Name : "max_connections cannot be 0" ,
166+ Data : testutils.ConfigTestData {
167+ Yaml : minimalYaml + `
168+ options:
169+ max_connections: 0` ,
170+ Env : withMinimalEnv (map [string ]string {"OPTIONS_MAX_CONNECTIONS" : "0" }),
171+ },
172+ Error : testutils .ErrorContains ("max_connections cannot be 0" ),
173+ },
174+ {
175+ Name : "max_connections_per_table must be at least 1" ,
176+ Data : testutils.ConfigTestData {
177+ Yaml : minimalYaml + `
178+ options:
179+ max_connections_per_table: 0` ,
180+ Env : withMinimalEnv (map [string ]string {"OPTIONS_MAX_CONNECTIONS_PER_TABLE" : "0" }),
181+ },
182+ Error : testutils .ErrorContains ("max_connections_per_table must be at least 1" ),
183+ },
184+ {
185+ Name : "max_placeholders_per_statement must be at least 1" ,
186+ Data : testutils.ConfigTestData {
187+ Yaml : minimalYaml + `
188+ options:
189+ max_placeholders_per_statement: 0` ,
190+ Env : withMinimalEnv (map [string ]string {"OPTIONS_MAX_PLACEHOLDERS_PER_STATEMENT" : "0" }),
191+ },
192+ Error : testutils .ErrorContains ("max_placeholders_per_statement must be at least 1" ),
193+ },
194+ {
195+ Name : "max_rows_per_transaction must be at least 1" ,
196+ Data : testutils.ConfigTestData {
197+ Yaml : minimalYaml + `
198+ options:
199+ max_rows_per_transaction: 0` ,
200+ Env : withMinimalEnv (map [string ]string {"OPTIONS_MAX_ROWS_PER_TRANSACTION" : "0" }),
201+ },
202+ Error : testutils .ErrorContains ("max_rows_per_transaction must be at least 1" ),
203+ },
204+ {
205+ Name : "wsrep_sync_wait can only be set to a number between 0 and 15" ,
206+ Data : testutils.ConfigTestData {
207+ Yaml : minimalYaml + `
208+ options:
209+ wsrep_sync_wait: 16` ,
210+ Env : withMinimalEnv (map [string ]string {"OPTIONS_WSREP_SYNC_WAIT" : "16" }),
211+ },
212+ Error : testutils .ErrorContains ("wsrep_sync_wait can only be set to a number between 0 and 15" ),
213+ },
214+ {
215+ Name : "Options retain defaults" ,
216+ Data : testutils.ConfigTestData {
217+ Yaml : minimalYaml + `
218+ options:
219+ max_connections: 8
220+ max_connections_per_table: 4` ,
221+ Env : withMinimalEnv (map [string ]string {
222+ "OPTIONS_MAX_CONNECTIONS" : "8" ,
223+ "OPTIONS_MAX_CONNECTIONS_PER_TABLE" : "4" ,
224+ }),
225+ },
226+ Expected : Config {
227+ Type : "pgsql" ,
228+ Host : "localhost" ,
229+ User : "icinga" ,
230+ Database : "icingadb" ,
231+ Password : "secret" ,
232+ Options : Options {
233+ MaxConnections : 8 ,
234+ MaxConnectionsPerTable : 4 ,
235+ MaxPlaceholdersPerStatement : defaultOptions .MaxPlaceholdersPerStatement ,
236+ MaxRowsPerTransaction : defaultOptions .MaxRowsPerTransaction ,
237+ WsrepSyncWait : defaultOptions .WsrepSyncWait ,
238+ },
239+ },
240+ },
241+ {
242+ Name : "Options" ,
243+ Data : testutils.ConfigTestData {
244+ Yaml : minimalYaml + `
245+ options:
246+ max_connections: 8
247+ max_connections_per_table: 4
248+ max_placeholders_per_statement: 4096
249+ max_rows_per_transaction: 2048
250+ wsrep_sync_wait: 15` ,
251+ Env : withMinimalEnv (map [string ]string {
252+ "OPTIONS_MAX_CONNECTIONS" : "8" ,
253+ "OPTIONS_MAX_CONNECTIONS_PER_TABLE" : "4" ,
254+ "OPTIONS_MAX_PLACEHOLDERS_PER_STATEMENT" : "4096" ,
255+ "OPTIONS_MAX_ROWS_PER_TRANSACTION" : "2048" ,
256+ "OPTIONS_WSREP_SYNC_WAIT" : "15" ,
257+ }),
258+ },
259+ Expected : Config {
260+ Type : "pgsql" ,
261+ Host : "localhost" ,
262+ User : "icinga" ,
263+ Database : "icingadb" ,
264+ Password : "secret" ,
82265 Options : Options {
83- MaxConnections : 1 ,
84- MaxConnectionsPerTable : 8 ,
85- MaxPlaceholdersPerStatement : 8192 ,
86- MaxRowsPerTransaction : 65535 ,
87- WsrepSyncWait : 7 ,
266+ MaxConnections : 8 ,
267+ MaxConnectionsPerTable : 4 ,
268+ MaxPlaceholdersPerStatement : 4096 ,
269+ MaxRowsPerTransaction : 2048 ,
270+ WsrepSyncWait : 15 ,
88271 },
89272 },
90273 },
91274 }
92275
93- for _ , test := range subtests {
94- t .Run (test .name , func (t * testing.T ) {
95- var out Config
96- if err := config .FromEnv (& out , test .opts ); test .error {
97- require .Error (t , err )
98- } else {
99- require .NoError (t , err )
100- require .Equal (t , test .expected , out )
101- }
102- })
103- }
276+ t .Run ("FromEnv" , func (t * testing.T ) {
277+ for _ , tc := range configTests {
278+ t .Run (tc .Name , tc .F (func (data testutils.ConfigTestData ) (Config , error ) {
279+ var actual Config
280+
281+ err := config .FromEnv (& actual , config.EnvOptions {Environment : data .Env })
282+
283+ return actual , err
284+ }))
285+ }
286+ })
287+
288+ t .Run ("FromYAMLFile" , func (t * testing.T ) {
289+ for _ , tc := range configTests {
290+ t .Run (tc .Name + "/FromYAMLFile" , tc .F (func (data testutils.ConfigTestData ) (Config , error ) {
291+ var actual Config
292+
293+ var err error
294+ testutils .WithYAMLFile (t , data .Yaml , func (file * os.File ) {
295+ err = config .FromYAMLFile (file .Name (), & actual )
296+ })
297+
298+ return actual , err
299+ }))
300+ }
301+ })
104302}
0 commit comments