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 = &params.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 &params)
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(&params));
107     tmphost = findHost(&params, "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(&params));
119     ASSERT_FALSE(NULL == findHost(&params, "foo.com"));
120     ASSERT_FALSE(NULL == findHost(&params, "bar.com"));
121     ASSERT_FALSE(NULL == findHost(&params, "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(&params, "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(&params, "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(&params, "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(&params, "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(&params, "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(&params, "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(&params, "bar.com");
180     ASSERT_EQ(4444, dh->port);
181     ASSERT_TRUE(dh->isMCD());
182     dh = findHost(&params, "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(&params));
189     ASSERT_FALSE(NULL == findHost(&params, "foo.com"));
190     ASSERT_FALSE(NULL == findHost(&params, "bar.com"));
191     ASSERT_FALSE(NULL == findHost(&params, "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(&params));
200     ASSERT_FALSE(NULL == findHost(&params, "::a15:f2df:3fef:51bb:212a:8cec"));
201     ASSERT_FALSE(NULL == findHost(&params, "::a15:f2df:3fef:51bb:212a:8ced"));
202     dh = findHost(&params, "::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(&params, "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(&params, "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(&params, "foo", op));
264     ASSERT_EQ("fooval", op.value);
265 
266     ASSERT_TRUE(findOption(&params, "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(&params));
273     ASSERT_FALSE(NULL == findHost(&params, "localhost"));
274     ASSERT_TRUE(findOption(&params, "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(&params, "foo", op));
284     ASSERT_TRUE(findOption(&params, "bar", op));
285     ASSERT_FALSE(findOption(&params, "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(&params, "foo.com"));
364     ASSERT_FALSE(NULL == findHost(&params, "bar.com"));
365     ASSERT_FALSE(NULL == findHost(&params, "baz.com"));
366     ASSERT_EQ(3, countHosts(&params));
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(&params));
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