1// Copyright 2013 Matthew Baird 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// http://www.apache.org/licenses/LICENSE-2.0 6// Unless required by applicable law or agreed to in writing, software 7// distributed under the License is distributed on an "AS IS" BASIS, 8// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9// See the License for the specific language governing permissions and 10// limitations under the License. 11 12package elastigo 13 14import ( 15 "encoding/json" 16 "io/ioutil" 17 "net/http" 18 "net/http/httptest" 19 "net/url" 20 "reflect" 21 "sort" 22 "strings" 23 "testing" 24) 25 26var ( 27 mux *http.ServeMux 28 server *httptest.Server 29) 30 31func setup(t *testing.T) *Conn { 32 mux = http.NewServeMux() 33 server = httptest.NewServer(mux) 34 c := NewTestConn() 35 36 serverURL, err := url.Parse(server.URL) 37 if err != nil { 38 t.Fatalf("Error: %v", err) 39 } 40 41 c.Domain = strings.Split(serverURL.Host, ":")[0] 42 c.Port = strings.Split(serverURL.Host, ":")[1] 43 44 return c 45} 46 47func teardown() { 48 server.Close() 49} 50 51type TestStruct struct { 52 Id string `json:"id" elastic:"index:not_analyzed"` 53 DontIndex string `json:"dontIndex" elastic:"index:no"` 54 Number int `json:"number" elastic:"type:integer,index:analyzed"` 55 Omitted string `json:"-"` 56 NoJson string `elastic:"type:string"` 57 unexported string 58 JsonOmitEmpty string `json:"jsonOmitEmpty,omitempty" elastic:"type:string"` 59 Embedded 60 Inner InnerStruct `json:"inner"` 61 InnerP *InnerStruct `json:"pointer_to_inner"` 62 InnerS []InnerStruct `json:"slice_of_inner"` 63 MultiAnalyze string `json:"multi_analyze"` 64 NestedObject NestedStruct `json:"nestedObject" elastic:"type:nested"` 65} 66 67type Embedded struct { 68 EmbeddedField string `json:"embeddedField" elastic:"type:string"` 69} 70 71type InnerStruct struct { 72 InnerField string `json:"innerField" elastic:"type:date"` 73} 74 75type NestedStruct struct { 76 InnerField string `json:"innerField" elastic:"type:date"` 77} 78 79// Sorting string 80// RuneSlice implements sort.Interface (http://golang.org/pkg/sort/#Interface) 81type RuneSlice []rune 82 83func (p RuneSlice) Len() int { return len(p) } 84func (p RuneSlice) Less(i, j int) bool { return p[i] < p[j] } 85func (p RuneSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 86 87// sorted func returns string with sorted characters 88func sorted(s string) string { 89 runes := []rune(s) 90 sort.Sort(RuneSlice(runes)) 91 return string(runes) 92} 93 94func TestPutMapping(t *testing.T) { 95 c := setup(t) 96 defer teardown() 97 98 options := MappingOptions{ 99 Timestamp: TimestampOptions{Enabled: true}, 100 Id: IdOptions{Index: "analyzed", Path: "id"}, 101 Parent: &ParentOptions{Type: "testParent"}, 102 TTL: &TTLOptions{Enabled: true, Default: "1w"}, 103 Properties: map[string]interface{}{ 104 // special properties that can't be expressed as tags 105 "multi_analyze": map[string]interface{}{ 106 "type": "multi_field", 107 "fields": map[string]map[string]string{ 108 "ma_analyzed": {"type": "string", "index": "analyzed"}, 109 "ma_notanalyzed": {"type": "string", "index": "not_analyzed"}, 110 }, 111 }, 112 }, 113 DynamicTemplates: []map[string]interface{}{ 114 { 115 "strings": map[string]interface{}{ 116 "match_mapping_type": "string", 117 "mapping": map[string]interface{}{ 118 "type": "string", 119 "index": "not_analyzed", 120 }, 121 }, 122 }, 123 }, 124 } 125 126 expValue := MappingForType("myType", MappingOptions{ 127 Timestamp: TimestampOptions{Enabled: true}, 128 Id: IdOptions{Index: "analyzed", Path: "id"}, 129 Parent: &ParentOptions{Type: "testParent"}, 130 TTL: &TTLOptions{Enabled: true, Default: "1w"}, 131 Properties: map[string]interface{}{ 132 "NoJson": map[string]string{"type": "string"}, 133 "dontIndex": map[string]string{"index": "no"}, 134 "embeddedField": map[string]string{"type": "string"}, 135 "id": map[string]string{"index": "not_analyzed"}, 136 "jsonOmitEmpty": map[string]string{"type": "string"}, 137 "number": map[string]string{"index": "analyzed", "type": "integer"}, 138 "multi_analyze": map[string]interface{}{ 139 "type": "multi_field", 140 "fields": map[string]map[string]string{ 141 "ma_analyzed": {"type": "string", "index": "analyzed"}, 142 "ma_notanalyzed": {"type": "string", "index": "not_analyzed"}, 143 }, 144 }, 145 "inner": map[string]map[string]map[string]string{ 146 "properties": { 147 "innerField": {"type": "date"}, 148 }, 149 }, 150 "pointer_to_inner": map[string]map[string]map[string]string{ 151 "properties": { 152 "innerField": {"type": "date"}, 153 }, 154 }, 155 "slice_of_inner": map[string]map[string]map[string]string{ 156 "properties": { 157 "innerField": {"type": "date"}, 158 }, 159 }, 160 "nestedObject": map[string]interface{}{ 161 "type": "nested", 162 "properties": map[string]map[string]string{ 163 "innerField": {"type": "date"}, 164 }, 165 }, 166 }, 167 DynamicTemplates: []map[string]interface{}{ 168 { 169 "strings": map[string]interface{}{ 170 "match_mapping_type": "string", 171 "mapping": map[string]interface{}{ 172 "type": "string", 173 "index": "not_analyzed", 174 }, 175 }, 176 }, 177 }, 178 }) 179 180 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 181 var value map[string]interface{} 182 bd, err := ioutil.ReadAll(r.Body) 183 json.NewDecoder(strings.NewReader(string(bd))).Decode(&value) 184 expValJson, err := json.MarshalIndent(expValue, "", " ") 185 if err != nil { 186 t.Errorf("Got error: %v", err) 187 } 188 valJson, err := json.MarshalIndent(value, "", " ") 189 if err != nil { 190 t.Errorf("Got error: %v", err) 191 } 192 193 if sorted(string(expValJson)) != sorted(string(valJson)) { 194 t.Errorf("Expected %s but got %s", string(expValJson), string(valJson)) 195 } 196 }) 197 198 err := c.PutMapping("myIndex", "myType", TestStruct{}, options) 199 if err != nil { 200 t.Errorf("Error: %v", err) 201 } 202} 203 204func TestPutMappingFromJSON(t *testing.T) { 205 c := setup(t) 206 defer teardown() 207 /* 208 options := MappingOptions{ 209 Timestamp: TimestampOptions{Enabled: true}, 210 Id: IdOptions{Index: "analyzed", Path: "id"}, 211 Parent: &ParentOptions{Type: "testParent"}, 212 Properties: map[string]interface{}{ 213 // special properties that can't be expressed as tags 214 "multi_analyze": map[string]interface{}{ 215 "type": "multi_field", 216 "fields": map[string]map[string]string{ 217 "ma_analyzed": {"type": "string", "index": "analyzed"}, 218 "ma_notanalyzed": {"type": "string", "index": "not_analyzed"}, 219 }, 220 }, 221 }, 222 DynamicTemplates: []map[string]interface{}{ 223 "strings": map[string]interface{}{ 224 "match_mapping_type": "string", 225 "mapping": { 226 "type": "string", 227 "index": "not_analyzed", 228 }, 229 }, 230 }, 231 } 232 */ 233 234 options := `{ 235 "myType": { 236 "_id": { 237 "index": "analyzed", 238 "path": "id" 239 }, 240 "_timestamp": { 241 "enabled": true 242 }, 243 "_parent": { 244 "type": "testParent" 245 }, 246 "properties": { 247 "analyzed_string": { 248 "type": "string", 249 "index": "analyzed" 250 }, 251 "multi_analyze": { 252 "type": "multi_field", 253 "fields": { 254 "ma_analyzed": { 255 "type": "string", 256 "index": "analyzed" 257 }, 258 "ma_notanalyzed": { 259 "type": "string", 260 "index": "not_analyzed" 261 } 262 } 263 } 264 }, 265 "dynamic_templates": [ 266 { 267 "strings": { 268 "match_mapping_type": "string", 269 "mapping": { 270 "type": "string", 271 "index": "not_analyzed" 272 } 273 } 274 } 275 ] 276 } 277 }` 278 279 expValue := map[string]interface{}{ 280 "myType": map[string]interface{}{ 281 "_timestamp": map[string]interface{}{ 282 "enabled": true, 283 }, 284 "_id": map[string]interface{}{ 285 "index": "analyzed", 286 "path": "id", 287 }, 288 "_parent": map[string]interface{}{ 289 "type": "testParent", 290 }, 291 "properties": map[string]interface{}{ 292 "analyzed_string": map[string]string{ 293 "type": "string", 294 "index": "analyzed", 295 }, 296 "multi_analyze": map[string]interface{}{ 297 "type": "multi_field", 298 "fields": map[string]map[string]string{ 299 "ma_analyzed": {"type": "string", "index": "analyzed"}, 300 "ma_notanalyzed": {"type": "string", "index": "not_analyzed"}, 301 }, 302 }, 303 }, 304 "dynamic_templates": []map[string]interface{}{ 305 { 306 "strings": map[string]interface{}{ 307 "match_mapping_type": "string", 308 "mapping": map[string]interface{}{ 309 "type": "string", 310 "index": "not_analyzed", 311 }, 312 }, 313 }, 314 }, 315 }, 316 } 317 318 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 319 var value map[string]interface{} 320 bd, err := ioutil.ReadAll(r.Body) 321 err = json.Unmarshal(bd, &value) 322 if err != nil { 323 t.Errorf("Got error: %v", err) 324 } 325 expValJson, err := json.MarshalIndent(expValue, "", " ") 326 if err != nil { 327 t.Errorf("Got error: %v", err) 328 } 329 330 valJson, err := json.MarshalIndent(value, "", " ") 331 if err != nil { 332 t.Errorf("Got error: %v", err) 333 } 334 335 if sorted(string(expValJson)) != sorted(string(valJson)) { 336 t.Errorf("Expected %s but got %s", string(expValJson), string(valJson)) 337 } 338 }) 339 340 err := c.PutMappingFromJSON("myIndex", "myType", []byte(options)) 341 if err != nil { 342 t.Errorf("Error: %v", err) 343 } 344} 345 346type StructWithEmptyElasticTag struct { 347 Field string `json:"field" elastic:""` 348} 349 350func TestPutMapping_empty_elastic_tag_is_accepted(t *testing.T) { 351 properties := map[string]interface{}{} 352 getProperties(reflect.TypeOf(StructWithEmptyElasticTag{}), properties) 353 if len(properties) != 0 { 354 t.Errorf("Expected empty properites but got: %v", properties) 355 } 356} 357