func TestAppStatus(t *testing.T) {
	Convey("Test is indexed", t, func() {
		Convey("Returns true when status is ok", func() {
			appStatus := AppStatus{Statistics: &Statistics{Status: "ok"}}
			So(appStatus.IsIndexed(), ShouldBeTrue)
		})

		Convey("Returns false when status is not ok", func() {
			appStatus := AppStatus{}
			So(appStatus.IsIndexed(), ShouldBeFalse)
		})
	})

	Convey("Test get app status", t, func() {
		db := h.CreateDB(t)
		CreateStatisticsBucket(db)

		Convey("When indexing is done", func() {
			h.PutToBucket(t, db, StatisticsBucketName, "cities_count", "1000")
			h.PutToBucket(t, db, StatisticsBucketName, "city_names_count", "2000")

			appStatus := GetAppStatus(db)

			Convey("Sets app status to \"ok\"", func() {
				So(appStatus.Statistics.Status, ShouldEqual, "ok")
			})

			Convey("Has correct cities count", func() {
				So(appStatus.Statistics.CitiesCount, ShouldEqual, 1000)
			})

			Convey("Has correct city names count", func() {
				So(appStatus.Statistics.CityNamesCount, ShouldEqual, 2000)
			})
		})

		Convey("When still indexing", func() {
			h.DeleteFromBucket(t, db, StatisticsBucketName, "cities_count")
			h.DeleteFromBucket(t, db, StatisticsBucketName, "city_names_count")

			appStatus := GetAppStatus(db)

			Convey("Sets the app status to \"indexing\"", func() {
				So(appStatus.Statistics.Status, ShouldEqual, "indexing")
			})

			Convey("Has 0 for cities count", func() {
				So(appStatus.Statistics.CitiesCount, ShouldEqual, 0)
			})

			Convey("Has 0 for city names count", func() {
				So(appStatus.Statistics.CityNamesCount, ShouldEqual, 0)
			})
		})
	})
}
Example #2
0
func TestCountry(t *testing.T) {
	db := h.CreateDB(t)
	CreateCountriesBucket(db)

	countryAttrs := []string{"DE", "Germany", "en|Germany;de|Deutschland"}
	countryString := strings.Join(countryAttrs, "\t")

	Convey("Country from string", t, func() {
		Convey("When the string is correct", func() {
			country, err := countryFromString(1, countryString)

			Convey("Sets the country id from param", func() {
				So(country.ID, ShouldEqual, 1)
			})

			Convey("Sets the country attributes", func() {
				So(country.Code, ShouldEqual, countryAttrs[0])
				So(country.Name, ShouldEqual, countryAttrs[1])
			})

			Convey("Sets the country translations", func() {
				So(country.Translations["en"], ShouldEqual, "Germany")
				So(country.Translations["de"], ShouldEqual, "Deutschland")
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the string is incorrect", func() {
			country, err := countryFromString(1, "")

			Convey("Leaves the country id blank", func() {
				So(country.ID, ShouldEqual, 0)
			})

			Convey("Leaves the country attributes blank", func() {
				So(country.Code, ShouldEqual, "")
				So(country.Name, ShouldEqual, "")
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})

	Convey("Find the country", t, func() {
		Convey("When the record exists", func() {
			h.PutToBucket(t, db, CountriesBucketName, "1", countryString)
			country, err := FindCountry(db, "1")

			Convey("Returns a country with attributes set", func() {
				expected, _ := countryFromString(1, countryString)
				So(country, ShouldResemble, expected)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the record does not exist", func() {
			country, err := FindCountry(db, "0")

			Convey("Returns a nil instead of a country", func() {
				So(country, ShouldBeNil)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When an incorrect value is stored in the db", func() {
			h.PutToBucket(t, db, CountriesBucketName, "2", "")
			country, err := FindCountry(db, "2")

			Convey("Returns an empty country", func() {
				So(country, ShouldResemble, &Country{})
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})

	Convey("Find the country by code", t, func() {
		Convey("When the record exists", func() {
			h.PutToBucket(t, db, CountriesBucketName, "1", countryString)
			country, err := FindCountryByCode(db, countryAttrs[0])

			Convey("Returns a country with attributes set", func() {
				expected, _ := countryFromString(1, countryString)
				So(country, ShouldResemble, expected)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the record does not exist", func() {
			country, err := FindCountryByCode(db, "HU")

			Convey("Returns a nil instead of a country", func() {
				So(country, ShouldBeNil)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When an incorrect value is stored in the db", func() {
			h.PutToBucket(t, db, CountriesBucketName, "2", "ES")
			country, err := FindCountryByCode(db, "ES")

			Convey("Returns an empty country", func() {
				So(country, ShouldResemble, &Country{})
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})
}
func TestScanAlternateNames(t *testing.T) {
	Convey("Test scan alternate names", t, func() {
		db := h.CreateDB(t)
		ds.CreateCitiesBucket(db)
		ds.CreateCityNamesBucket(db)
		ds.CreateCountriesBucket(db)

		h.PutToBucket(t, db, ds.CitiesBucketName, "1", "Montreal\t\t\t\t\t")
		h.PutToBucket(t, db, ds.CitiesBucketName, "2", "Moscow\t\t\t\t\t")
		h.PutToBucket(t, db, ds.CountriesBucketName, "3", "DE\tGermany\ten|Germany")

		locales := []string{"de", "ru"}

		Convey("When alternate names file exists", func() {
			filename := h.CreateTempfile(
				t,
				"10\t1\tfr\tMontréal\t\t\t\t\n11\t2\tde\tMoskau\t\t\t\t\n12\t2\tru\tМосква\t\t\t\t13\t9\tde\tMünchen\t\t\t\t\n"+
					"14\t3\tde\tDeutschland\t\t\t\t\n15\t3\ten\tWest Germany\t\t\t\t\n16\t3\tit\tGermania\t\t\t\t",
			)

			count, err := scanAlternateNames(db, filename, locales)
			country, _ := ds.FindCountry(db, "3")

			Convey("Returns number of scanned records", func() {
				So(count, ShouldEqual, 2)
			})

			Convey("When the locale is supported", func() {
				Convey("Stores the record if the city exists", func() {
					actual := h.ReadFromBucket(t, db, ds.CityNamesBucketName, "moskau")
					So(actual, ShouldEqual, "Moskau\t2\tde\t0")
				})

				Convey("Doesn't store the record if the city doesn't exist", func() {
					actual := h.ReadFromBucket(t, db, ds.CityNamesBucketName, "münchen")
					So(actual, ShouldEqual, "")
				})

				Convey("Adds translations for countries", func() {
					So(country.Translations["de"], ShouldEqual, "Deutschland")
				})

				Convey("Doesn't override en names for countries", func() {
					So(country.Translations["en"], ShouldEqual, "Germany")
				})
			})

			Convey("When the locale is not supported", func() {
				Convey("Doesn't store the record", func() {
					actual := h.ReadFromBucket(t, db, ds.CityNamesBucketName, "montréal")
					So(actual, ShouldEqual, "")
				})

				Convey("Doesn't add translations for countries", func() {
					So(country.Translations["it"], ShouldEqual, "")
				})
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When alternate names file does not exist", func() {
			count, err := scanAlternateNames(db, "fake.txt", locales)

			Convey("Returns a zero number of scanned records", func() {
				So(count, ShouldEqual, 0)
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})
}
func TestStatistics(t *testing.T) {
	db := h.CreateDB(t)
	CreateStatisticsBucket(db)

	Convey("Save statistics", t, func() {
		statistics := Statistics{CitiesCount: 100000, CityNamesCount: 200000}
		err := statistics.Save(db)

		Convey("Saves the cities count to the db", func() {
			val, _ := strconv.Atoi(
				h.ReadFromBucket(t, db, StatisticsBucketName, "cities_count"),
			)

			So(val, ShouldEqual, statistics.CitiesCount)
		})

		Convey("Saves the city names count to the db", func() {
			val, _ := strconv.Atoi(
				h.ReadFromBucket(t, db, StatisticsBucketName, "city_names_count"),
			)

			So(val, ShouldEqual, statistics.CityNamesCount)
		})

		Convey("Returns no error", func() {
			So(err, ShouldBeNil)
		})
	})

	Convey("Get statistics", t, func() {
		Convey("When the values are in the db", func() {
			h.PutToBucket(t, db, StatisticsBucketName, "cities_count", "5000")
			h.PutToBucket(t, db, StatisticsBucketName, "city_names_count", "9000")

			statistics, err := GetStatistics(db)

			Convey("Reads the cities count from db", func() {
				So(statistics.CitiesCount, ShouldEqual, 5000)
			})

			Convey("Reads the city names count from db", func() {
				So(statistics.CityNamesCount, ShouldEqual, 9000)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the values are not in the db", func() {
			CreateStatisticsBucket(db)

			Convey("Does not panic", func() {
				So(func() { GetStatistics(db) }, ShouldNotPanic)
			})

			Convey("Returns an error", func() {
				_, err := GetStatistics(db)
				So(err, ShouldNotBeNil)
			})
		})
	})
}
func TestCityNames(t *testing.T) {
	Convey("Limit citynames", t, func() {
		Convey("Limits a collection if too big", func() {
			actual := CityNames{
				&CityName{Name: "A"}, &CityName{Name: "B"}, &CityName{Name: "C"},
			}

			expected := CityNames{&CityName{Name: "A"}, &CityName{Name: "B"}}

			actual.Limit(2)
			So(actual, ShouldResemble, expected)
		})

		Convey("Does not changes the collection if not too big", func() {
			actual := CityNames{&CityName{Name: "A"}, &CityName{Name: "B"}}
			expected := actual
			actual.Limit(2)
			So(actual, ShouldResemble, expected)
		})
	})

	Convey("Uniq citynames", t, func() {
		Convey("Removes values duplicated by city id", func() {
			actual := CityNames{
				&CityName{Name: "Moscow", CityId: 1},
				&CityName{Name: "Moskau", CityId: 1},
				&CityName{Name: "Montreal", CityId: 2},
			}

			expected := CityNames{
				&CityName{Name: "Moscow", CityId: 1},
				&CityName{Name: "Montreal", CityId: 2},
			}

			actual.Uniq()
			So(actual, ShouldResemble, expected)
		})

		Convey("Leaves values with the same name but unique city ids", func() {
			actual := CityNames{
				&CityName{Name: "Moscow", CityId: 1},
				&CityName{Name: "Moscow", CityId: 2},
			}

			expected := actual

			actual.Uniq()
			So(actual, ShouldResemble, expected)
		})
	})

	Convey("Search city names", t, func() {
		db := h.CreateDB(t)
		CreateCityNamesBucket(db)

		cityNames := CityNames{
			&CityName{
				Key: "moscow|2", Name: "Moscow", CityId: 2,
				Locale: "en", Population: 25000,
			},
			&CityName{
				Key: "moscow", Name: "Moscow", CityId: 1,
				Locale: "en", Population: 12000000,
			},
			&CityName{
				Key: "montreal", Name: "Montreal", CityId: 3,
				Locale: "en", Population: 1600000,
			},
			&CityName{
				Key: "moskau", Name: "Moskau", CityId: 1,
				Locale: "de", Population: 12000000,
			},
		}
		for _, cn := range cityNames {
			h.PutToBucket(t, db, CityNamesBucketName, cn.Key, cn.toString())
		}

		locales := []string{"ru", "en", "de"}
		result, err := searchCityNames(db, locales, "Mo", 5)

		Convey("Finds matching citynames without duplicates", func() {
			So(len(*result), ShouldEqual, 3)
			So((*result)[0], ShouldResemble, cityNames[1])
			So((*result)[1], ShouldResemble, cityNames[2])
			So((*result)[2], ShouldResemble, cityNames[0])
		})

		Convey("Returns no error", func() {
			So(err, ShouldBeNil)
		})
	})
}
Example #6
0
func TestUtils(t *testing.T) {
	db := h.CreateDB(t)
	ds.CreateCityNamesBucket(db)
	ds.CreateCountriesBucket(db)

	Convey("Test prepare country bytes", t, func() {
		Convey("When the data is correct", func() {
			data := []string{
				"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
				"11", "12", "13", "14", "15", "16", "17", "18",
			}

			expected := []byte("0\t4\ten|4")
			actual, err := prepareCountryBytes(data)

			Convey("Joins the array in the right order with tabs", func() {
				So(actual, ShouldResemble, expected)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the data is incorrect", func() {
			data := []string{"yolo"}
			actual, err := prepareCountryBytes(data)

			Convey("Returns an empty bytes array", func() {
				var bytes []byte
				So(actual, ShouldResemble, bytes)
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})

	Convey("Test add translations to country", t, func() {
		translations := []string{"de|Deutschland", "ru|Германия"}

		countryAttrs := []string{"DE", "Germany", "en|Germany"}
		countryString := strings.Join(countryAttrs, "\t")
		h.PutToBucket(t, db, ds.CountriesBucketName, "1", countryString)

		err := db.Batch(func(tx *bolt.Tx) error {
			b := tx.Bucket(ds.CountriesBucketName)
			return addTranslationsToCountry(b, 1, translations)
		})

		country, err := ds.FindCountry(db, "1")

		Convey("Does not modify country data", func() {
			So(country.Code, ShouldEqual, countryAttrs[0])
			So(country.Name, ShouldEqual, countryAttrs[1])
		})

		Convey("Keeps old translations", func() {
			So(country.Translations["en"], ShouldEqual, "Germany")
		})

		Convey("Adds new translations", func() {
			So(country.Translations["de"], ShouldEqual, "Deutschland")
			So(country.Translations["ru"], ShouldEqual, "Германия")
		})

		Convey("Returns no error", func() {
			So(err, ShouldBeNil)
		})
	})

	Convey("Test prepare city bytes", t, func() {
		Convey("When the data is correct", func() {
			data := []string{
				"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
				"11", "12", "13", "14", "15", "16", "17", "18",
			}

			expected := []byte("1\t8\t14\t4\t5\t17")
			actual, err := prepareCityBytes(data)

			Convey("Joins the array in the right order with tabs", func() {
				So(actual, ShouldResemble, expected)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the data is incorrect", func() {
			data := []string{"yolo"}
			actual, err := prepareCityBytes(data)

			Convey("Returns an empty bytes array", func() {
				var bytes []byte
				So(actual, ShouldResemble, bytes)
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})

	Convey("Test add city to index", t, func() {
		Convey("If no record for the key exist yet", func() {
			err := db.Batch(func(tx *bolt.Tx) error {
				b := tx.Bucket(ds.CityNamesBucketName)
				return addCityToIndex(b, "1", "Berlin", "en", 2000000)
			})

			Convey("Puts the city name string to the bucket", func() {
				actual := h.ReadFromBucket(t, db, ds.CityNamesBucketName, "berlin")
				So(actual, ShouldEqual, "Berlin\t1\ten\t2000000")
			})

			Convey("Not return an error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("If a record for the key exists", func() {
			existing := "Moscow\t1\ten\t12000000"
			h.PutToBucket(t, db, ds.CityNamesBucketName, "moscow", existing)

			err := db.Batch(func(tx *bolt.Tx) error {
				b := tx.Bucket(ds.CityNamesBucketName)
				return addCityToIndex(b, "2", "Moscow", "en", 20000)
			})

			Convey("Does not overwrites the existing entry", func() {
				actual := h.ReadFromBucket(t, db, ds.CityNamesBucketName, "moscow")
				So(actual, ShouldEqual, existing)
			})

			Convey("Adds city id as postfix for a new entry key", func() {
				actual := h.ReadFromBucket(t, db, ds.CityNamesBucketName, "moscow|2")
				So(actual, ShouldEqual, "Moscow\t2\ten\t20000")
			})

			Convey("Not return an error", func() {
				So(err, ShouldBeNil)
			})
		})
	})

	Convey("Test is supported locale", t, func() {
		locales := []string{"ru", "en", "de"}

		Convey("Returns true for supported locales", func() {
			for _, locale := range locales {
				So(isSupportedLocale(locale, locales), ShouldBeTrue)
			}
		})

		Convey("Returns false for unsupported locales", func() {
			So(isSupportedLocale("jp", locales), ShouldBeFalse)
		})
	})
}
Example #7
0
func TestCity(t *testing.T) {
	db := h.CreateDB(t)
	CreateCitiesBucket(db)
	CreateCountriesBucket(db)

	cityAttrs := []string{
		"Name",
		"DE",
		"10000000",
		"40.748817",
		"-73.958428",
		"Timezone",
	}
	cityString := strings.Join(cityAttrs, "\t")

	Convey("City from string", t, func() {
		Convey("When the string is correct", func() {
			city, err := cityFromString(1, cityString)

			Convey("Sets the city id from param", func() {
				So(city.ID, ShouldEqual, 1)
			})

			Convey("Sets the city attributes from the string", func() {
				So(city.Name, ShouldEqual, cityAttrs[0])
				So(city.CountryCode, ShouldEqual, cityAttrs[1])
				So(city.Population, ShouldEqual, 10000000)
				So(city.Latitude, ShouldEqual, 40.748817)
				So(city.Longitude, ShouldEqual, -73.958428)
				So(city.Timezone, ShouldEqual, cityAttrs[5])
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When the string is incorrect", func() {
			city, err := cityFromString(1, "")

			Convey("Leaves the city id blank", func() {
				So(city.ID, ShouldEqual, 0)
			})

			Convey("Leaves the city attributes blank", func() {
				So(city.Name, ShouldEqual, "")
				So(city.CountryCode, ShouldEqual, "")
				So(city.Population, ShouldEqual, 0)
				So(city.Latitude, ShouldEqual, 0)
				So(city.Longitude, ShouldEqual, 0)
				So(city.Timezone, ShouldEqual, "")
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})

	Convey("City to string", t, func() {
		city := City{
			ID: 1, Name: "New York", CountryCode: "US", Population: 8600000,
			Latitude: 40.748817, Longitude: -73.985428, Timezone: "USA/New York",
		}

		Convey("Joins the city properties with tab chars", func() {
			expected := "New York\tUS\t8600000\t40.748817\t-73.985428\tUSA/New York"
			So(expected, ShouldEqual, city.toString())
		})
	})

	Convey("Find the city", t, func() {
		Convey("When the record exists", func() {
			h.PutToBucket(t, db, CitiesBucketName, "1", cityString)

			Convey("With includeCountry set to false", func() {
				city, err := FindCity(db, "1", false)

				Convey("Returns a city with attributes set", func() {
					expected, _ := cityFromString(1, cityString)
					So(city, ShouldResemble, expected)
				})

				Convey("Returns no error", func() {
					So(err, ShouldBeNil)
				})
			})

			Convey("With includeCountry set to true", func() {
				Convey("When country record exists", func() {
					countryAttrs := []string{"DE", "Germany", "en|Germany"}
					countryString := strings.Join(countryAttrs, "\t")
					h.PutToBucket(t, db, CountriesBucketName, "1", countryString)

					city, err := FindCity(db, "1", true)

					Convey("Returns a city with attributes set", func() {
						expected, _ := cityFromString(1, cityString)
						expected.Country, _ = countryFromString(1, countryString)
						So(city, ShouldResemble, expected)
					})

					Convey("Returns no error", func() {
						So(err, ShouldBeNil)
					})
				})
			})
		})

		Convey("When the record does not exist", func() {
			city, err := FindCity(db, "0", false)

			Convey("Returns a nil instead of a city", func() {
				So(city, ShouldBeNil)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When an incorrect value is stored in the db", func() {
			h.PutToBucket(t, db, CitiesBucketName, "2", "")
			city, err := FindCity(db, "2", false)

			Convey("Returns an empty city", func() {
				So(city, ShouldResemble, &City{})
			})

			Convey("Returns an error", func() {
				So(err, ShouldNotBeNil)
			})
		})
	})
}
Example #8
0
func TestCities(t *testing.T) {
	Convey("Append city", t, func() {
		db := h.CreateDB(t)
		CreateCountriesBucket(db)

		h.PutToBucket(
			t, db, CountriesBucketName, "1",
			"US\tUnited States\ten|United States;ru|Соединенные Штаты",
		)

		cities := Cities{
			Cities: []*City{
				&City{Name: "Venice"}, &City{Name: "Moscow"},
			},
		}

		Convey("When no city with the same name is in the collection", func() {
			city := City{Name: "London"}
			actual := appendCity(db, cities.Cities, &city, "en")

			Convey("Adds the city to the array", func() {
				So(len(actual), ShouldEqual, 3)
			})

			Convey("Leaves the city name unchanged", func() {
				So(actual[2].Name, ShouldEqual, city.Name)
			})
		})

		Convey("When city with the same name is in the collection", func() {
			Convey("When the country exists for the given code", func() {
				city := City{Name: "Venice", CountryCode: "US"}

				Convey("Default locale", func() {
					actual := appendCity(db, cities.Cities, &city, "en")

					Convey("Adds the city to the array", func() {
						So(len(actual), ShouldEqual, 3)
					})

					Convey("Adds the country name to the city name", func() {
						So(actual[2].Name, ShouldEqual, "Venice, United States")
					})
				})

				Convey("Some other locale", func() {
					actual := appendCity(db, cities.Cities, &city, "ru")

					Convey("Adds the city to the array", func() {
						So(len(actual), ShouldEqual, 3)
					})

					Convey("Adds the country name to the city name", func() {
						So(actual[2].Name, ShouldEqual, "Venice, Соединенные Штаты")
					})
				})
			})

			Convey("When no country exists for the given code", func() {
				city := City{Name: "Moscow", CountryCode: "MO"}
				actual := appendCity(db, cities.Cities, &city, "en")

				Convey("Doesn't adds the city to the array", func() {
					So(len(actual), ShouldEqual, 2)
				})
			})
		})
	})

	Convey("Search cites", t, func() {
		db := h.CreateDB(t)
		CreateCitiesBucket(db)
		CreateCityNamesBucket(db)

		cityNames := CityNames{
			&CityName{
				Key: "montreal", Name: "Montréal", CityId: 1,
				Locale: "fr", Population: 1600000,
			},
			&CityName{
				Key: "moscow", Name: "Moskau", CityId: 2,
				Locale: "de", Population: 12000000,
			},
		}
		for _, cn := range cityNames {
			h.PutToBucket(t, db, CityNamesBucketName, cn.Key, cn.toString())
		}

		cities := []*City{
			&City{ID: 1, Name: "Montreal"},
			&City{ID: 2, Name: "Moscow"},
		}
		for _, city := range cities {
			h.PutToBucket(
				t, db, CitiesBucketName, strconv.Itoa(city.ID), city.toString(),
			)
		}

		locales := []string{"ru", "en", "de"}

		Convey("Non cached search", func() {
			result, err := SearchCities(db, locales, "Mo", 5)

			Convey("Finds matching cities", func() {
				So(len(result.Cities), ShouldEqual, 2)
				So(result.Cities[0].ID, ShouldEqual, cities[1].ID)
				So(result.Cities[1].ID, ShouldEqual, cities[0].ID)
			})

			Convey("Sets the city names from the mathing cityname", func() {
				So(result.Cities[0].Name, ShouldEqual, cityNames[1].Name)
				So(result.Cities[1].Name, ShouldEqual, cityNames[0].Name)
			})

			Convey("Returns no error", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("Cached search", func() {
			c := cache.New()

			Convey("For short queries", func() {
				results, expectedErr := SearchCities(db, locales, "Mo", 5)
				cacheMissResults, actualErr := CachedCitiesSearch(db, c, locales, "Mo", 5)
				cacheHitResults, _ := CachedCitiesSearch(db, c, locales, "Mo", 5)

				Convey("Returns results from search if cache miss", func() {
					So(cacheMissResults, ShouldResemble, results)
					So(actualErr, ShouldEqual, expectedErr)
				})

				Convey("Returns results from cache if cache hit", func() {
					So(cacheHitResults, ShouldResemble, cacheMissResults)
				})
			})

			Convey("For longer queries", func() {
				expected, expectedErr := SearchCities(db, locales, "Moscow", 5)
				actual, actualErr := CachedCitiesSearch(db, c, locales, "Moscow", 5)

				Convey("Returns results from search if cache miss", func() {
					So(actual, ShouldResemble, expected)
					So(actualErr, ShouldEqual, expectedErr)
				})
			})
		})
	})
}