1 #include <gtest/gtest.h>
2 #include "connspec.h"
3 using namespace lcb;
4
countHosts(const Connspec * spec)5 static size_t countHosts(const Connspec *spec) {
6 return spec->hosts().size();
7 }
8
9 class ConnstrTest : public ::testing::Test
10 {
11 protected:
12 Connspec params;
13 const char *errmsg;
reinit()14 void reinit() {
15 params = Connspec();
16 }
SetUp()17 void SetUp() {
18 params = Connspec();
19 }
20 };
21
22 const Spechost*
findHost(const Connspec & params,const char * srch)23 findHost(const Connspec& params, const char* srch)
24 {
25 for (size_t ii = 0; ii < params.hosts().size(); ++ii) {
26 const Spechost *cur = ¶ms.hosts()[ii];
27 if (srch == cur->hostname) {
28 return cur;
29 }
30 }
31 return NULL;
32 }
33 const Spechost*
findHost(const Connspec * params,const char * srch)34 findHost(const Connspec* params, const char *srch)
35 {
36 return findHost(*params, srch);
37 }
38
39 struct OptionPair {
40 std::string key;
41 std::string value;
42 };
43
findOption(const Connspec & params,const char * srch,OptionPair & op)44 bool findOption(const Connspec& params, const char *srch, OptionPair& op)
45 {
46 int iter = 0;
47 Connspec::Options::const_iterator ii = params.options().begin();
48 for (; ii != params.options().end(); ++ii) {
49 if (ii->first == srch) {
50 op.key = ii->first;
51 op.value = ii->second;
52 return true;
53 }
54 }
55 return false;
56 }
57
findOption(const Connspec * params,const char * srch,OptionPair & op)58 bool findOption(const Connspec* params, const char *srch, OptionPair& op)
59 {
60 return findOption(*params, srch, op);
61 }
62
63 size_t
countHosts(const Connspec & params)64 countHosts(const Connspec ¶ms)
65 {
66 return params.hosts().size();
67 }
68
TEST_F(ConnstrTest,testParseBasic)69 TEST_F(ConnstrTest, testParseBasic)
70 {
71 lcb_error_t err;
72 err = params.parse("couchbase://1.2.3.4", &errmsg);
73 ASSERT_EQ(LCB_SUCCESS, err);
74
75 const Spechost *tmphost;
76 tmphost = findHost(params, "1.2.3.4");
77 ASSERT_EQ(1, countHosts(params));
78 ASSERT_FALSE(NULL == tmphost);
79 ASSERT_EQ(0, tmphost->port);
80 ASSERT_EQ(0, tmphost->type); // Nothing
81
82 reinit();
83 // test with bad scheme
84 err = params.parse("blah://foo.com", &errmsg);
85 ASSERT_NE(LCB_SUCCESS, err) << "Error on bad scheme";
86
87 reinit();
88 err = params.parse("couchbase://", &errmsg);
89 ASSERT_EQ(LCB_SUCCESS, err) << "Ok with scheme only";
90
91 reinit();
92 err = params.parse("couchbase://?", &errmsg);
93 ASSERT_EQ(LCB_SUCCESS, err) << "Ok with only '?'";
94
95 reinit();
96 err = params.parse("couchbase://?&", &errmsg);
97 ASSERT_EQ(LCB_SUCCESS, err) << "Ok with only '?&'";
98
99 reinit();
100 err = params.parse("1.2.3.4", &errmsg);
101 ASSERT_EQ(LCB_SUCCESS, err) << "Ok without scheme";
102 ASSERT_EQ(LCB_CONFIG_HTTP_PORT, params.default_port());
103
104 reinit();
105 err = params.parse("1.2.3.4:999", &errmsg);
106 ASSERT_EQ(1, countHosts(¶ms));
107 tmphost = findHost(¶ms, "1.2.3.4");
108 ASSERT_FALSE(tmphost == NULL);
109 ASSERT_EQ(999, tmphost->port);
110 ASSERT_TRUE(tmphost->isHTTP());
111
112 }
113
TEST_F(ConnstrTest,testParseHosts)114 TEST_F(ConnstrTest, testParseHosts)
115 {
116 lcb_error_t err;
117 err = params.parse("couchbase://foo.com,bar.com,baz.com", &errmsg);
118 ASSERT_EQ(3, countHosts(¶ms));
119 ASSERT_FALSE(NULL == findHost(¶ms, "foo.com"));
120 ASSERT_FALSE(NULL == findHost(¶ms, "bar.com"));
121 ASSERT_FALSE(NULL == findHost(¶ms, "baz.com"));
122
123 // Parse with 'legacy' format
124 reinit();
125 err = params.parse("couchbase://foo.com:8091", &errmsg);
126 ASSERT_EQ(LCB_SUCCESS, err);
127 const Spechost *dh = findHost(¶ms, "foo.com");
128 ASSERT_FALSE(NULL == dh);
129 ASSERT_EQ("foo.com", dh->hostname);
130 // CCBC-599
131 ASSERT_EQ(0, dh->port);
132 ASSERT_EQ(0, dh->type);
133
134 // parse with invalid port, without specifying protocol
135 reinit();
136 err = params.parse("couchbase://foo.com:4444", &errmsg);
137 ASSERT_EQ(LCB_SUCCESS, err);
138 dh = findHost(¶ms, "foo.com");
139 ASSERT_EQ(4444, dh->port);
140 ASSERT_TRUE(dh->isMCD());
141
142 reinit();
143 err = params.parse("couchbases://foo.com:4444", &errmsg);
144 ASSERT_EQ(LCB_SUCCESS, err);
145 dh = findHost(¶ms, "foo.com");
146 ASSERT_EQ(LCB_SSL_ENABLED, params.sslopts());
147 ASSERT_EQ(4444, dh->port);
148 ASSERT_TRUE(dh->isMCDS());
149
150 // Parse with recognized format
151 reinit();
152 err = params.parse("couchbase://foo.com:4444=mcd", &errmsg);
153 ASSERT_EQ(LCB_SUCCESS, err);
154 dh = findHost(¶ms, "foo.com");
155 ASSERT_EQ("foo.com", dh->hostname);
156 ASSERT_EQ(4444, dh->port);
157 ASSERT_TRUE(dh->isMCD());
158
159 //Parse multiple hosts with ports
160 reinit();
161 err = params.parse("couchbase://foo.com:4444=mcd,bar.com:5555=mcd", &errmsg);
162 ASSERT_EQ(LCB_SUCCESS, err);
163
164 dh = findHost(¶ms, "foo.com");
165 ASSERT_FALSE(dh == NULL);
166 ASSERT_EQ("foo.com", dh->hostname);
167 ASSERT_EQ(4444, dh->port);
168 ASSERT_TRUE(dh->isMCD());
169
170 dh = findHost(¶ms, "bar.com");
171 ASSERT_FALSE(dh == NULL);
172 ASSERT_EQ("bar.com", dh->hostname);
173 ASSERT_EQ(5555, dh->port);
174 ASSERT_TRUE(dh->isMCD());
175
176 reinit();
177 err = params.parse("couchbase://foo.com,bar.com:4444", &errmsg);
178 ASSERT_EQ(LCB_SUCCESS, err);
179 dh = findHost(¶ms, "bar.com");
180 ASSERT_EQ(4444, dh->port);
181 ASSERT_TRUE(dh->isMCD());
182 dh = findHost(¶ms, "foo.com");
183 ASSERT_TRUE(dh->isTypeless());
184
185 reinit();
186 err = params.parse("couchbase://foo.com;bar.com;baz.com", &errmsg);
187 ASSERT_EQ(LCB_SUCCESS, err) << "Can parse old-style semicolons";
188 ASSERT_EQ(3, countHosts(¶ms));
189 ASSERT_FALSE(NULL == findHost(¶ms, "foo.com"));
190 ASSERT_FALSE(NULL == findHost(¶ms, "bar.com"));
191 ASSERT_FALSE(NULL == findHost(¶ms, "baz.com"));
192
193 reinit();
194 err = params.parse("couchbase://"
195 "::a15:f2df:3fef:51bb:212a:8cec,[::a15:f2df:3fef:51bb:212a:8ced],[::a15:f2df:3fef:51bb:212a:"
196 "8cee]:9001",
197 &errmsg);
198 ASSERT_EQ(LCB_SUCCESS, err) << "Cannot parse IPv6";
199 ASSERT_EQ(3, countHosts(¶ms));
200 ASSERT_FALSE(NULL == findHost(¶ms, "::a15:f2df:3fef:51bb:212a:8cec"));
201 ASSERT_FALSE(NULL == findHost(¶ms, "::a15:f2df:3fef:51bb:212a:8ced"));
202 dh = findHost(¶ms, "::a15:f2df:3fef:51bb:212a:8cee");
203 ASSERT_FALSE(dh == NULL);
204 ASSERT_EQ("::a15:f2df:3fef:51bb:212a:8cee", dh->hostname);
205 ASSERT_EQ(9001, dh->port);
206 }
207
TEST_F(ConnstrTest,testParseBucket)208 TEST_F(ConnstrTest, testParseBucket)
209 {
210 lcb_error_t err;
211 err = params.parse("couchbase://foo.com/user", &errmsg);
212 ASSERT_EQ(LCB_SUCCESS, err);
213 ASSERT_EQ("user", params.bucket()) << "Basic bucket parse";
214
215 reinit();
216 err = params.parse("couchbase://foo.com/user/", &errmsg);
217 ASSERT_EQ(LCB_SUCCESS, err) << "Bucket can have a slash";
218 // We can have a bucket using a slash
219
220 reinit();
221 err = params.parse("couchbase:///default", &errmsg);
222 ASSERT_EQ(LCB_SUCCESS, err) << "Bucket without host OK";
223 ASSERT_EQ("default", params.bucket());
224
225 reinit();
226 err = params.parse("couchbase:///default?", &errmsg);
227 ASSERT_EQ(LCB_SUCCESS, err);
228 ASSERT_EQ("default", params.bucket());
229
230 reinit();
231 err = params.parse("couchbase:///%2FUsers%2F?", &errmsg);
232 ASSERT_EQ(LCB_SUCCESS, err);
233 ASSERT_EQ("/Users/", params.bucket());
234 }
235
TEST_F(ConnstrTest,testOptionsPassthrough)236 TEST_F(ConnstrTest, testOptionsPassthrough)
237 {
238 lcb_error_t err;
239 err = params.parse("couchbase://?foo=bar", &errmsg);
240 ASSERT_EQ(LCB_SUCCESS, err) << "Options only";
241 ASSERT_FALSE(params.options().empty());
242 ASSERT_NE(0, params.options().size());
243
244 OptionPair op;
245 ASSERT_TRUE(findOption(¶ms, "foo", op));
246 ASSERT_EQ("bar", op.value);
247
248 reinit();
249 err = params.parse("couchbase://?foo=bar", &errmsg);
250 ASSERT_EQ(LCB_SUCCESS, err);
251 ASSERT_TRUE(findOption(¶ms, "foo", op));
252 ASSERT_EQ("bar", op.value);
253
254 reinit();
255 err = params.parse("couchbase://?foo", &errmsg);
256 ASSERT_NE(LCB_SUCCESS, err) << "Option without value";
257
258
259 // Multiple options
260 reinit();
261 err = params.parse("couchbase://?foo=fooval&bar=barval", &errmsg);
262 ASSERT_EQ(LCB_SUCCESS, err);
263 ASSERT_TRUE(findOption(¶ms, "foo", op));
264 ASSERT_EQ("fooval", op.value);
265
266 ASSERT_TRUE(findOption(¶ms, "bar", op));
267 ASSERT_EQ("barval", op.value);
268
269 reinit();
270 err = params.parse("couchbase:///protected?ssl=on&compression=off", &errmsg);
271 ASSERT_EQ(LCB_SUCCESS, err) << "Ok with bucket and no hosts";
272 ASSERT_EQ(1, countHosts(¶ms));
273 ASSERT_FALSE(NULL == findHost(¶ms, "localhost"));
274 ASSERT_TRUE(findOption(¶ms, "compression", op));
275
276 reinit();
277 err = params.parse("couchbase://?foo=foo&bar=bar&", &errmsg);
278 ASSERT_EQ(LCB_SUCCESS, err) << "Ok with trailing '&'";
279
280 reinit();
281 err = params.parse("couchbase://?foo=foo&bootstrap_on=all&bar=bar", &errmsg);
282 ASSERT_EQ(LCB_SUCCESS, err) << "Ok with non-passthrough option";
283 ASSERT_TRUE(findOption(¶ms, "foo", op));
284 ASSERT_TRUE(findOption(¶ms, "bar", op));
285 ASSERT_FALSE(findOption(¶ms, "bootstrap_on", op));
286 }
287
TEST_F(ConnstrTest,testRecognizedOptions)288 TEST_F(ConnstrTest, testRecognizedOptions)
289 {
290 lcb_error_t err;
291 err = params.parse("couchbases://", &errmsg);
292 ASSERT_EQ(LCB_SUCCESS, err);
293 ASSERT_EQ(LCB_SSL_ENABLED, params.sslopts());
294
295 reinit();
296 err = params.parse("couchbase://?ssl=on", &errmsg);
297 ASSERT_EQ(LCB_SUCCESS, err);
298 ASSERT_EQ(LCB_SSL_ENABLED, params.sslopts());
299
300 reinit();
301 err = params.parse("couchbases://?ssl=no_verify", &errmsg);
302 ASSERT_EQ(LCB_SUCCESS, err);
303 ASSERT_EQ(LCB_SSL_ENABLED|LCB_SSL_NOVERIFY, params.sslopts());
304
305 reinit();
306 err = params.parse("couchbases://?ssl=off", &errmsg);
307 ASSERT_NE(LCB_SUCCESS, err);
308
309 // Loglevel
310 reinit();
311 err = params.parse("couchbase://?console_log_level=5", &errmsg);
312 ASSERT_EQ(LCB_SUCCESS, err);
313 ASSERT_EQ(5, params.loglevel());
314
315 reinit();
316 err = params.parse("couchbase://?console_log_level=gah", &errmsg);
317 ASSERT_NE(LCB_SUCCESS, err);
318
319 }
320
TEST_F(ConnstrTest,testTransportOptions)321 TEST_F(ConnstrTest, testTransportOptions)
322 {
323 lcb_error_t err;
324 err = params.parse("couchbase://", &errmsg);
325 ASSERT_EQ(LCB_SUCCESS, err);
326 ASSERT_FALSE(params.is_bs_udef());
327
328 reinit();
329 err = params.parse("couchbase://?bootstrap_on=cccp", &errmsg);
330 ASSERT_EQ(LCB_SUCCESS, err) << "bootstrap_on=cccp";
331 ASSERT_TRUE(params.has_bsmode(LCB_CONFIG_TRANSPORT_CCCP));
332 ASSERT_FALSE(params.has_bsmode(LCB_CONFIG_TRANSPORT_HTTP));
333
334 reinit();
335 err = params.parse("couchbase://?bootstrap_on=http", &errmsg);
336 ASSERT_EQ(LCB_SUCCESS, err) << "bootstrap_on=http";
337 ASSERT_TRUE(params.has_bsmode(LCB_CONFIG_TRANSPORT_HTTP));
338 ASSERT_FALSE(params.has_bsmode(LCB_CONFIG_TRANSPORT_CCCP));
339
340 reinit();
341 err = params.parse("couchbase://?bootstrap_on=all", &errmsg);
342 ASSERT_EQ(LCB_SUCCESS, err) << "bootstrap_on=all";
343 ASSERT_TRUE(params.has_bsmode(LCB_CONFIG_TRANSPORT_CCCP));
344 ASSERT_TRUE(params.has_bsmode(LCB_CONFIG_TRANSPORT_HTTP));
345
346 reinit();
347 err = params.parse("couchbase://?bootstrap_on=bleh", &errmsg);
348 ASSERT_NE(LCB_SUCCESS, err) << "Error on bad bootstrap_on value";
349 }
350
TEST_F(ConnstrTest,testCompatConversion)351 TEST_F(ConnstrTest, testCompatConversion)
352 {
353 lcb_error_t err;
354 struct lcb_create_st cropts;
355 memset(&cropts, 0, sizeof cropts);
356 cropts.version = 0;
357 cropts.v.v0.bucket = "users";
358 cropts.v.v0.host = "foo.com;bar.com;baz.com";
359 cropts.v.v0.passwd = "secret";
360
361 err = params.load(cropts);
362 ASSERT_EQ(LCB_SUCCESS, err);
363 ASSERT_FALSE(NULL == findHost(¶ms, "foo.com"));
364 ASSERT_FALSE(NULL == findHost(¶ms, "bar.com"));
365 ASSERT_FALSE(NULL == findHost(¶ms, "baz.com"));
366 ASSERT_EQ(3, countHosts(¶ms));
367 ASSERT_EQ("users", params.bucket());
368 ASSERT_EQ("secret", params.password());
369
370 // Ensure old-style port specifications are parsed and don't throw an
371 // error. We'd also like to verify that these actually land within
372 // the htport field, that's a TODO
373 reinit();
374 memset(&cropts, 0, sizeof cropts);
375 cropts.version = 2;
376 cropts.v.v2.host = "foo.com:9030;bar.com:9040;baz.com:9050";
377 cropts.v.v2.mchosts = "foo.com:7030;bar.com:7040;baz.com:7050";
378 err = params.load(cropts);
379 ASSERT_EQ(LCB_SUCCESS, err);
380 ASSERT_EQ(6, countHosts(¶ms));
381
382 // Ensure struct fields override the URI string
383 reinit();
384 memset(&cropts, 0, sizeof cropts);
385 cropts.version = 3;
386 cropts.v.v3.passwd = "secret";
387 cropts.v.v3.connstr = "couchbase:///fluffle?password=bleh";
388 err = params.load(cropts);
389 ASSERT_EQ(LCB_SUCCESS, err);
390 ASSERT_EQ("fluffle", params.bucket());
391 ASSERT_EQ(cropts.v.v3.passwd, params.password());
392 }
393
TEST_F(ConnstrTest,testCertificateWithoutSSL)394 TEST_F(ConnstrTest, testCertificateWithoutSSL)
395 {
396 // Ensure we get an invalid input error for certificate paths without
397 // couchbases://
398 lcb_error_t err;
399 err = params.parse(
400 "couchbase://1.2.3.4/default?certpath=/foo/bar/baz", &errmsg);
401 ASSERT_NE(LCB_SUCCESS, err);
402
403 reinit();
404 err = params.parse(
405 "couchbases://1.2.3.4/default?certpath=/foo/bar/baz", &errmsg);
406 ASSERT_EQ(LCB_SUCCESS, err);
407 }
408
TEST_F(ConnstrTest,testDnsSrvExplicit)409 TEST_F(ConnstrTest, testDnsSrvExplicit)
410 {
411 // Test various things relating to DNS SRV
412 lcb_error_t err;
413 err = params.parse("couchbase+dnssrv://1.1.1.1", &errmsg);
414 EXPECT_EQ(LCB_SUCCESS, err);
415 EXPECT_TRUE(params.can_dnssrv());
416 EXPECT_TRUE(params.is_explicit_dnssrv());
417
418 reinit();
419 err = params.parse("couchbase+dnssrv://1.1.1.1,2.2.2.2", &errmsg);
420 EXPECT_NE(LCB_SUCCESS, err);
421
422 reinit();
423 err = params.parse("couchbases+dnssrv://1.1.1.1", &errmsg);
424 EXPECT_EQ(LCB_SUCCESS, err);
425 EXPECT_NE(0, params.sslopts());
426 EXPECT_TRUE(params.can_dnssrv());
427 EXPECT_TRUE(params.is_explicit_dnssrv());
428 }
429
TEST_F(ConnstrTest,testDnsSrvImplicit)430 TEST_F(ConnstrTest, testDnsSrvImplicit)
431 {
432 EXPECT_EQ(LCB_SUCCESS, params.parse("couchbase://"));
433 EXPECT_FALSE(params.can_dnssrv());
434 EXPECT_FALSE(params.is_explicit_dnssrv());
435
436 reinit();
437 EXPECT_EQ(LCB_SUCCESS, params.parse("couchbase://1.1.1.1"));
438 EXPECT_TRUE(params.can_dnssrv());
439 EXPECT_FALSE(params.is_explicit_dnssrv());
440
441 reinit();
442 EXPECT_EQ(LCB_SUCCESS, params.parse("couchbase://1.1.1.1,2.2.2.2"));
443 EXPECT_FALSE(params.can_dnssrv()) << "No implicit SRV on multiple hosts";
444
445 reinit();
446 EXPECT_EQ(LCB_SUCCESS, params.parse("couchbase://1.1.1.1:666"));
447 EXPECT_FALSE(params.can_dnssrv());
448
449 reinit();
450 EXPECT_EQ(LCB_SUCCESS, params.parse("couchbase://1.1.1.1:11210"));
451 EXPECT_TRUE(params.can_dnssrv());
452
453 reinit();
454 EXPECT_EQ(LCB_SUCCESS, params.parse("couchbases://1.1.1.1"));
455 EXPECT_TRUE(params.can_dnssrv());
456 }
457