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) }) }) }) }
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) }) }) }
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) }) }) }
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) }) }) }) }
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) }) }) }) }) }