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	testMapping    = `
20{
21	"settings":{
22		"number_of_shards":1,
23		"number_of_replicas":0
24	},
25	"mappings":{
26		"_default_": {
27			"_all": {
28				"enabled": true
29			}
30		},
31		"tweet":{
32			"properties":{
33				"user":{
34					"type":"keyword"
35				},
36				"message":{
37					"type":"text",
38					"store": true,
39					"fielddata": true
40				},
41				"tags":{
42					"type":"keyword"
43				},
44				"location":{
45					"type":"geo_point"
46				},
47				"suggest_field":{
48					"type":"completion",
49					"contexts":[
50						{
51							"name": "user_name",
52							"type": "category"
53						}
54					]
55				}
56			}
57		},
58		"comment":{
59			"_parent": {
60				"type":	"tweet"
61			}
62		},
63		"order":{
64			"properties":{
65				"article":{
66					"type":"text"
67				},
68				"manufacturer":{
69					"type":"keyword"
70				},
71				"price":{
72					"type":"float"
73				},
74				"time":{
75					"type":"date",
76					"format": "YYYY-MM-dd"
77				}
78			}
79		},
80		"doctype":{
81			"properties":{
82				"message":{
83					"type":"text",
84					"store": true,
85					"fielddata": true
86				}
87			}
88		},
89		"queries":{
90			"properties": {
91				"query": {
92					"type":	"percolator"
93				}
94			}
95		},
96		"tweet-nosource":{
97			"_source": {
98				"enabled": false
99			},
100			"properties":{
101				"user":{
102					"type":"keyword"
103				},
104				"message":{
105					"type":"text",
106					"store": true,
107					"fielddata": true
108				},
109				"tags":{
110					"type":"keyword"
111				},
112				"location":{
113					"type":"geo_point"
114				},
115				"suggest_field":{
116					"type":"completion",
117					"contexts":[
118						{
119							"name":"user_name",
120							"type":"category"
121						}
122					]
123				}
124			}
125		}
126	}
127}
128`
129)
130
131type tweet struct {
132	User     string        `json:"user"`
133	Message  string        `json:"message"`
134	Retweets int           `json:"retweets"`
135	Image    string        `json:"image,omitempty"`
136	Created  time.Time     `json:"created,omitempty"`
137	Tags     []string      `json:"tags,omitempty"`
138	Location string        `json:"location,omitempty"`
139	Suggest  *SuggestField `json:"suggest_field,omitempty"`
140}
141
142func (t tweet) String() string {
143	return fmt.Sprintf("tweet{User:%q,Message:%q,Retweets:%d}", t.User, t.Message, t.Retweets)
144}
145
146type comment struct {
147	User    string    `json:"user"`
148	Comment string    `json:"comment"`
149	Created time.Time `json:"created,omitempty"`
150}
151
152func (c comment) String() string {
153	return fmt.Sprintf("comment{User:%q,Comment:%q}", c.User, c.Comment)
154}
155
156type order struct {
157	Article      string  `json:"article"`
158	Manufacturer string  `json:"manufacturer"`
159	Price        float64 `json:"price"`
160	Time         string  `json:"time,omitempty"`
161}
162
163func (o order) String() string {
164	return fmt.Sprintf("order{Article:%q,Manufacturer:%q,Price:%v,Time:%v}", o.Article, o.Manufacturer, o.Price, o.Time)
165}
166
167// doctype is required for Percolate tests.
168type doctype struct {
169	Message string `json:"message"`
170}
171
172// queries is required for Percolate tests.
173type queries struct {
174	Query string `json:"query"`
175}
176
177func isTravis() bool {
178	return os.Getenv("TRAVIS") != ""
179}
180
181func travisGoVersion() string {
182	return os.Getenv("TRAVIS_GO_VERSION")
183}
184
185type logger interface {
186	Error(args ...interface{})
187	Errorf(format string, args ...interface{})
188	Fatal(args ...interface{})
189	Fatalf(format string, args ...interface{})
190	Fail()
191	FailNow()
192	Log(args ...interface{})
193	Logf(format string, args ...interface{})
194}
195
196func setupTestClient(t logger, options ...ClientOptionFunc) (client *Client) {
197	var err error
198
199	client, err = NewClient(options...)
200	if err != nil {
201		t.Fatal(err)
202	}
203
204	client.DeleteIndex(testIndexName).Do(context.TODO())
205	client.DeleteIndex(testIndexName2).Do(context.TODO())
206
207	return client
208}
209
210func setupTestClientAndCreateIndex(t logger, options ...ClientOptionFunc) *Client {
211	client := setupTestClient(t, options...)
212
213	// Create index
214	createIndex, err := client.CreateIndex(testIndexName).Body(testMapping).Do(context.TODO())
215	if err != nil {
216		t.Fatal(err)
217	}
218	if createIndex == nil {
219		t.Errorf("expected result to be != nil; got: %v", createIndex)
220	}
221
222	// Create second index
223	createIndex2, err := client.CreateIndex(testIndexName2).Body(testMapping).Do(context.TODO())
224	if err != nil {
225		t.Fatal(err)
226	}
227	if createIndex2 == nil {
228		t.Errorf("expected result to be != nil; got: %v", createIndex2)
229	}
230
231	return client
232}
233
234func setupTestClientAndCreateIndexAndLog(t logger, options ...ClientOptionFunc) *Client {
235	return setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
236}
237
238func setupTestClientAndCreateIndexAndAddDocs(t logger, options ...ClientOptionFunc) *Client {
239	client := setupTestClientAndCreateIndex(t, options...)
240
241	// Add tweets
242	tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
243	tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
244	tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
245	comment1 := comment{User: "nico", Comment: "You bet."}
246
247	_, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do(context.TODO())
248	if err != nil {
249		t.Fatal(err)
250	}
251	_, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do(context.TODO())
252	if err != nil {
253		t.Fatal(err)
254	}
255	_, err = client.Index().Index(testIndexName).Type("tweet").Id("3").Routing("someroutingkey").BodyJson(&tweet3).Do(context.TODO())
256	if err != nil {
257		t.Fatal(err)
258	}
259	_, err = client.Index().Index(testIndexName).Type("comment").Id("1").Parent("3").BodyJson(&comment1).Do(context.TODO())
260	if err != nil {
261		t.Fatal(err)
262	}
263
264	// Add orders
265	var orders []order
266	orders = append(orders, order{Article: "Apple MacBook", Manufacturer: "Apple", Price: 1290, Time: "2015-01-18"})
267	orders = append(orders, order{Article: "Paper", Manufacturer: "Canon", Price: 100, Time: "2015-03-01"})
268	orders = append(orders, order{Article: "Apple iPad", Manufacturer: "Apple", Price: 499, Time: "2015-04-12"})
269	orders = append(orders, order{Article: "Dell XPS 13", Manufacturer: "Dell", Price: 1600, Time: "2015-04-18"})
270	orders = append(orders, order{Article: "Apple Watch", Manufacturer: "Apple", Price: 349, Time: "2015-04-29"})
271	orders = append(orders, order{Article: "Samsung TV", Manufacturer: "Samsung", Price: 790, Time: "2015-05-03"})
272	orders = append(orders, order{Article: "Hoodie", Manufacturer: "h&m", Price: 49, Time: "2015-06-03"})
273	orders = append(orders, order{Article: "T-Shirt", Manufacturer: "h&m", Price: 19, Time: "2015-06-18"})
274	for i, o := range orders {
275		id := fmt.Sprintf("%d", i)
276		_, err = client.Index().Index(testIndexName).Type("order").Id(id).BodyJson(&o).Do(context.TODO())
277		if err != nil {
278			t.Fatal(err)
279		}
280	}
281
282	// Flush
283	_, err = client.Flush().Index(testIndexName).Do(context.TODO())
284	if err != nil {
285		t.Fatal(err)
286	}
287	return client
288}
289
290func setupTestClientAndCreateIndexAndAddDocsNoSource(t logger, options ...ClientOptionFunc) *Client {
291	client := setupTestClientAndCreateIndex(t, options...)
292
293	// Add tweets
294	tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
295	tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
296
297	_, err := client.Index().Index(testIndexName).Type("tweet-nosource").Id("1").BodyJson(&tweet1).Do(context.TODO())
298	if err != nil {
299		t.Fatal(err)
300	}
301	_, err = client.Index().Index(testIndexName).Type("tweet-nosource").Id("2").BodyJson(&tweet2).Do(context.TODO())
302	if err != nil {
303		t.Fatal(err)
304	}
305	// Flush
306	_, err = client.Flush().Index(testIndexName).Do(context.TODO())
307	if err != nil {
308		t.Fatal(err)
309	}
310
311	return client
312}
313
314var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
315
316func randomString(n int) string {
317	b := make([]rune, n)
318	for i := range b {
319		b[i] = letters[rand.Intn(len(letters))]
320	}
321	return string(b)
322}
323