1// Copyright 2012-present Oliver Eilhard. All rights reserved.
2// Use of this source code is governed by a MIT-license.
3// See http://olivere.mit-license.org/license.txt for details.
4
5package elastic
6
7import (
8	"context"
9	"fmt"
10	"log"
11	"math/rand"
12	"os"
13	"time"
14)
15
16const (
17	testIndexName  = "elastic-test"
18	testIndexName2 = "elastic-test2"
19	testIndexName3 = "elastic-test3"
20	testIndexName4 = "elastic-test4"
21	testMapping    = `
22{
23	"settings":{
24		"number_of_shards":1,
25		"number_of_replicas":0
26	},
27	"mappings":{
28		"doc":{
29			"properties":{
30				"user":{
31					"type":"keyword"
32				},
33				"message":{
34					"type":"text",
35					"store": true,
36					"fielddata": true
37				},
38				"tags":{
39					"type":"keyword"
40				},
41				"location":{
42					"type":"geo_point"
43				},
44				"suggest_field":{
45					"type":"completion",
46					"contexts":[
47						{
48							"name":"user_name",
49							"type":"category"
50						}
51					]
52				}
53			}
54		}
55	}
56}
57`
58
59	testNoSourceIndexName = "elastic-nosource-test"
60	testNoSourceMapping   = `
61{
62	"settings":{
63		"number_of_shards":1,
64		"number_of_replicas":0
65	},
66	"mappings":{
67		"doc":{
68			"_source": {
69				"enabled": false
70			},
71			"properties":{
72				"user":{
73					"type":"keyword"
74				},
75				"message":{
76					"type":"text",
77					"store": true,
78					"fielddata": true
79				},
80				"tags":{
81					"type":"keyword"
82				},
83				"location":{
84					"type":"geo_point"
85				},
86				"suggest_field":{
87					"type":"completion",
88					"contexts":[
89						{
90							"name":"user_name",
91							"type":"category"
92						}
93					]
94				}
95			}
96		}
97	}
98}
99`
100
101	testJoinIndex   = "elastic-joins"
102	testJoinMapping = `
103	{
104		"settings":{
105			"number_of_shards":1,
106			"number_of_replicas":0
107		},
108		"mappings":{
109			"doc":{
110				"properties":{
111					"message":{
112						"type":"text"
113					},
114					"my_join_field": {
115						"type": "join",
116						"relations": {
117							"question": "answer"
118						}
119					}
120				}
121			}
122		}
123	}
124`
125
126	testOrderIndex   = "elastic-orders"
127	testOrderMapping = `
128{
129"settings":{
130	"number_of_shards":1,
131	"number_of_replicas":0
132},
133"mappings":{
134	"doc":{
135		"properties":{
136			"article":{
137				"type":"text"
138			},
139			"manufacturer":{
140				"type":"keyword"
141			},
142			"price":{
143				"type":"float"
144			},
145			"time":{
146				"type":"date",
147				"format": "YYYY-MM-dd"
148			}
149		}
150	}
151}
152}
153`
154
155	/*
156	   	testDoctypeIndex   = "elastic-doctypes"
157	   	testDoctypeMapping = `
158	   {
159	   	"settings":{
160	   		"number_of_shards":1,
161	   		"number_of_replicas":0
162	   	},
163	   	"mappings":{
164	   		"doc":{
165	   			"properties":{
166	   				"message":{
167	   					"type":"text",
168	   					"store": true,
169	   					"fielddata": true
170	   				}
171	   			}
172	   		}
173	   	}
174	   }
175	   `
176	*/
177
178	testQueryIndex   = "elastic-queries"
179	testQueryMapping = `
180{
181	"settings":{
182		"number_of_shards":1,
183		"number_of_replicas":0
184	},
185	"mappings":{
186		"doc":{
187			"properties":{
188				"message":{
189					"type":"text",
190					"store": true,
191					"fielddata": true
192				},
193				"query": {
194					"type":	"percolator"
195				}
196			}
197		}
198	}
199}
200`
201)
202
203type tweet struct {
204	User     string        `json:"user"`
205	Message  string        `json:"message"`
206	Retweets int           `json:"retweets"`
207	Image    string        `json:"image,omitempty"`
208	Created  time.Time     `json:"created,omitempty"`
209	Tags     []string      `json:"tags,omitempty"`
210	Location string        `json:"location,omitempty"`
211	Suggest  *SuggestField `json:"suggest_field,omitempty"`
212}
213
214func (t tweet) String() string {
215	return fmt.Sprintf("tweet{User:%q,Message:%q,Retweets:%d}", t.User, t.Message, t.Retweets)
216}
217
218type comment struct {
219	User    string    `json:"user"`
220	Comment string    `json:"comment"`
221	Created time.Time `json:"created,omitempty"`
222}
223
224func (c comment) String() string {
225	return fmt.Sprintf("comment{User:%q,Comment:%q}", c.User, c.Comment)
226}
227
228type joinDoc struct {
229	Message   string      `json:"message"`
230	JoinField interface{} `json:"my_join_field,omitempty"`
231}
232
233type joinField struct {
234	Name   string `json:"name"`
235	Parent string `json:"parent,omitempty"`
236}
237
238type order struct {
239	Article      string  `json:"article"`
240	Manufacturer string  `json:"manufacturer"`
241	Price        float64 `json:"price"`
242	Time         string  `json:"time,omitempty"`
243}
244
245func (o order) String() string {
246	return fmt.Sprintf("order{Article:%q,Manufacturer:%q,Price:%v,Time:%v}", o.Article, o.Manufacturer, o.Price, o.Time)
247}
248
249// doctype is required for Percolate tests.
250type doctype struct {
251	Message string `json:"message"`
252}
253
254// queries is required for Percolate tests.
255type queries struct {
256	Query string `json:"query"`
257}
258
259func isTravis() bool {
260	return os.Getenv("TRAVIS") != ""
261}
262
263func travisGoVersion() string {
264	return os.Getenv("TRAVIS_GO_VERSION")
265}
266
267type logger interface {
268	Error(args ...interface{})
269	Errorf(format string, args ...interface{})
270	Fatal(args ...interface{})
271	Fatalf(format string, args ...interface{})
272	Fail()
273	FailNow()
274	Log(args ...interface{})
275	Logf(format string, args ...interface{})
276}
277
278func setupTestClient(t logger, options ...ClientOptionFunc) (client *Client) {
279	var err error
280
281	client, err = NewClient(options...)
282	if err != nil {
283		t.Fatal(err)
284	}
285
286	client.DeleteIndex(testIndexName).Do(context.TODO())
287	client.DeleteIndex(testIndexName2).Do(context.TODO())
288	client.DeleteIndex(testIndexName3).Do(context.TODO())
289	client.DeleteIndex(testIndexName4).Do(context.TODO())
290	client.DeleteIndex(testOrderIndex).Do(context.TODO())
291	client.DeleteIndex(testNoSourceIndexName).Do(context.TODO())
292	//client.DeleteIndex(testDoctypeIndex).Do(context.TODO())
293	client.DeleteIndex(testQueryIndex).Do(context.TODO())
294	client.DeleteIndex(testJoinIndex).Do(context.TODO())
295
296	return client
297}
298
299func setupTestClientAndCreateIndex(t logger, options ...ClientOptionFunc) *Client {
300	client := setupTestClient(t, options...)
301
302	// Create index
303	createIndex, err := client.CreateIndex(testIndexName).Body(testMapping).Do(context.TODO())
304	if err != nil {
305		t.Fatal(err)
306	}
307	if createIndex == nil {
308		t.Errorf("expected result to be != nil; got: %v", createIndex)
309	}
310
311	// Create second index
312	createIndex2, err := client.CreateIndex(testIndexName2).Body(testMapping).Do(context.TODO())
313	if err != nil {
314		t.Fatal(err)
315	}
316	if createIndex2 == nil {
317		t.Errorf("expected result to be != nil; got: %v", createIndex2)
318	}
319
320	// Create no source index
321	createNoSourceIndex, err := client.CreateIndex(testNoSourceIndexName).Body(testNoSourceMapping).Do(context.TODO())
322	if err != nil {
323		t.Fatal(err)
324	}
325	if createNoSourceIndex == nil {
326		t.Errorf("expected result to be != nil; got: %v", createNoSourceIndex)
327	}
328
329	// Create order index
330	createOrderIndex, err := client.CreateIndex(testOrderIndex).Body(testOrderMapping).Do(context.TODO())
331	if err != nil {
332		t.Fatal(err)
333	}
334	if createOrderIndex == nil {
335		t.Errorf("expected result to be != nil; got: %v", createOrderIndex)
336	}
337
338	return client
339}
340
341func setupTestClientAndCreateIndexAndLog(t logger, options ...ClientOptionFunc) *Client {
342	return setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
343}
344
345func setupTestClientAndCreateIndexAndAddDocs(t logger, options ...ClientOptionFunc) *Client {
346	client := setupTestClientAndCreateIndex(t, options...)
347
348	// Add tweets
349	tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
350	tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
351	tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
352	//comment1 := comment{User: "nico", Comment: "You bet."}
353
354	_, err := client.Index().Index(testIndexName).Type("doc").Id("1").BodyJson(&tweet1).Do(context.TODO())
355	if err != nil {
356		t.Fatal(err)
357	}
358	_, err = client.Index().Index(testIndexName).Type("doc").Id("2").BodyJson(&tweet2).Do(context.TODO())
359	if err != nil {
360		t.Fatal(err)
361	}
362	_, err = client.Index().Index(testIndexName).Type("doc").Id("3").Routing("someroutingkey").BodyJson(&tweet3).Do(context.TODO())
363	if err != nil {
364		t.Fatal(err)
365	}
366	/*
367		_, err = client.Index().Index(testIndexName).Type("comment").Id("1").Parent("3").BodyJson(&comment1).Do(context.TODO())
368		if err != nil {
369			t.Fatal(err)
370		}
371	*/
372
373	// Add orders
374	var orders []order
375	orders = append(orders, order{Article: "Apple MacBook", Manufacturer: "Apple", Price: 1290, Time: "2015-01-18"})
376	orders = append(orders, order{Article: "Paper", Manufacturer: "Canon", Price: 100, Time: "2015-03-01"})
377	orders = append(orders, order{Article: "Apple iPad", Manufacturer: "Apple", Price: 499, Time: "2015-04-12"})
378	orders = append(orders, order{Article: "Dell XPS 13", Manufacturer: "Dell", Price: 1600, Time: "2015-04-18"})
379	orders = append(orders, order{Article: "Apple Watch", Manufacturer: "Apple", Price: 349, Time: "2015-04-29"})
380	orders = append(orders, order{Article: "Samsung TV", Manufacturer: "Samsung", Price: 790, Time: "2015-05-03"})
381	orders = append(orders, order{Article: "Hoodie", Manufacturer: "h&m", Price: 49, Time: "2015-06-03"})
382	orders = append(orders, order{Article: "T-Shirt", Manufacturer: "h&m", Price: 19, Time: "2015-06-18"})
383	for i, o := range orders {
384		id := fmt.Sprintf("%d", i)
385		_, err = client.Index().Index(testOrderIndex).Type("doc").Id(id).BodyJson(&o).Do(context.TODO())
386		if err != nil {
387			t.Fatal(err)
388		}
389	}
390
391	// Flush
392	_, err = client.Flush().Index(testIndexName, testOrderIndex).Do(context.TODO())
393	if err != nil {
394		t.Fatal(err)
395	}
396	return client
397}
398
399func setupTestClientAndCreateIndexAndAddDocsNoSource(t logger, options ...ClientOptionFunc) *Client {
400	client := setupTestClientAndCreateIndex(t, options...)
401
402	// Add tweets
403	tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
404	tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
405
406	_, err := client.Index().Index(testNoSourceIndexName).Type("doc").Id("1").BodyJson(&tweet1).Do(context.TODO())
407	if err != nil {
408		t.Fatal(err)
409	}
410	_, err = client.Index().Index(testNoSourceIndexName).Type("doc").Id("2").BodyJson(&tweet2).Do(context.TODO())
411	if err != nil {
412		t.Fatal(err)
413	}
414	// Flush
415	_, err = client.Flush().Index(testNoSourceIndexName).Do(context.TODO())
416	if err != nil {
417		t.Fatal(err)
418	}
419
420	return client
421}
422
423func setupTestClientForXpackSecurity(t logger) (client *Client) {
424	var err error
425	// Set URL and Auth to use the platinum ES cluster
426	options := []ClientOptionFunc{SetURL("http://127.0.0.1:9210"), SetBasicAuth("elastic", "elastic")}
427
428	client, err = NewClient(options...)
429	if err != nil {
430		t.Fatal(err)
431	}
432
433	return client
434}
435
436var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
437
438func randomString(n int) string {
439	b := make([]rune, n)
440	for i := range b {
441		b[i] = letters[rand.Intn(len(letters))]
442	}
443	return string(b)
444}
445
446type lexicographically struct {
447	strings []string
448}
449
450func (l lexicographically) Len() int {
451	return len(l.strings)
452}
453
454func (l lexicographically) Less(i, j int) bool {
455	return l.strings[i] < l.strings[j]
456}
457
458func (l lexicographically) Swap(i, j int) {
459	l.strings[i], l.strings[j] = l.strings[j], l.strings[i]
460}
461