From 4f9334270fd3a0bec3e811fa303acfce8bfa52de Mon Sep 17 00:00:00 2001 From: myConsciousness Date: Mon, 5 Dec 2022 16:29:33 +0900 Subject: [PATCH 1/2] test: add unit tests --- lib/src/service/geo/geo_service.dart | 121 +++++++++----- .../lookup_reversed_geocoded_locations.json | 147 ++++++++++++++++++ test/src/service/geo/geo_service_test.dart | 116 ++++++++++++++ 3 files changed, 348 insertions(+), 36 deletions(-) create mode 100644 test/src/service/geo/data/lookup_reversed_geocoded_locations.json diff --git a/lib/src/service/geo/geo_service.dart b/lib/src/service/geo/geo_service.dart index 1d5e8227..457f3109 100644 --- a/lib/src/service/geo/geo_service.dart +++ b/lib/src/service/geo/geo_service.dart @@ -135,6 +135,53 @@ abstract class GeoService { /// This request is an informative call and will deliver generalized /// results about geography. /// + /// ## Parameters + /// + /// - [granularity]: This is the minimal granularity of place types to + /// return and must be one of: [GeoGranularity.neighborhood], + /// [GeoGranularity.city], [GeoGranularity.admin] or + /// [GeoGranularity.country]. If no granularity is provided + /// for the request [GeoGranularity.neighborhood] is assumed. + /// Setting this to [GeoGranularity.city], for example, + /// will find places which have a type of + /// [GeoGranularity.city], [GeoGranularity.admin] or + /// [GeoGranularity.country]. + /// + /// - [maxResults]: A hint as to the number of results to return. + /// This does not guarantee that the number of results + /// returned will equal max_results, but instead informs how + /// many "nearby" results to return. Ideally, only pass in + /// the number of places you intend to display to the user + /// here. + /// + /// - [accuracy]: A hint on the "region" in which to search. If a number, + /// then this is a radius in meters, but it can also take a + /// string that is suffixed with ft to specify feet. + /// If this is not passed in, then it is assumed to be `0m`. + /// If coming from a device, in practice, this value is + /// whatever accuracy the device has measuring its location + /// (whether it be coming from a GPS, WiFi triangulation, etc.). + /// + /// - [latitude]: The latitude to search around. This parameter will be + /// ignored unless it is inside the range -90.0 to +90.0 + /// (North is positive) inclusive. It will also be ignored if + /// there isn't a corresponding [longitude] parameter + /// + /// - [longitude]: The longitude to search around. The valid ranges for + /// longitude are -180.0 to +180.0 (East is positive) + /// inclusive. This parameter will be ignored if outside + /// that range, if it is not a number, or if there not a + /// corresponding [latitude] parameter. + /// + /// ## Authentication Methods + /// + /// - OAuth 1.0a + /// + /// ## Rate Limits + /// + /// - **User rate limit (OAuth 1.0a)**: + /// 15 requests per 15-minute window per each authenticated user + /// /// ## Endpoint Url /// /// - https://api.twitter.com/1.1/geo/reverse_geocode.json @@ -142,12 +189,13 @@ abstract class GeoService { /// ## Reference /// /// - https://developer.twitter.com/en/docs/twitter-api/v1/geo/places-near-location/api-reference/get-geo-reverse_geocode - Future, void>> reverseGeocodeLocations({ - GeoGranularity? granularity, + Future, void>> + lookupReverseGeocodedLocations({ + required double latitude, + required double longitude, int? maxResults, String? accuracy, - double? latitude, - double? longitude, + GeoGranularity? granularity, }); } @@ -208,6 +256,39 @@ class _GeoService extends BaseService implements GeoService { ); } + @override + Future, void>> + lookupReverseGeocodedLocations({ + required double latitude, + required double longitude, + int? maxResults, + String? accuracy, + GeoGranularity? granularity, + }) async { + final response = await super.get( + core.UserContext.oauth1Only, + '/1.1/geo/reverse_geocode.json', + queryParameters: { + 'lat': latitude, + 'long': longitude, + 'max_results': maxResults, + 'accuracy': accuracy, + 'granularity': granularity, + }, + ); + + final places = _checkResponse(response)['result']['places']; + + return TwitterResponse( + rateLimit: RateLimit.fromJson( + rateLimitConverter.convert(response.headers), + ), + data: _toV2Format(places) + .map((json) => PlaceData.fromJson(json)) + .toList(), + ); + } + dynamic _checkResponse(final core.Response response) { final json = jsonDecode(response.body); @@ -242,36 +323,4 @@ class _GeoService extends BaseService implements GeoService { return json; } - - @override - Future, void>> reverseGeocodeLocations({ - GeoGranularity? granularity, - int? maxResults, - String? accuracy, - double? latitude, - double? longitude, - }) async { - final response = await super.get( - core.UserContext.oauth1Only, - '/1.1/geo/reverse_geocode.json', - queryParameters: { - 'granularity': granularity, - 'max_results': maxResults, - 'lat': latitude, - 'long': longitude, - 'accuracy': accuracy, - }, - ); - - final places = _checkResponse(response)['result']['places']; - - return TwitterResponse( - rateLimit: RateLimit.fromJson( - rateLimitConverter.convert(response.headers), - ), - data: _toV2Format(places) - .map((json) => PlaceData.fromJson(json)) - .toList(), - ); - } } diff --git a/test/src/service/geo/data/lookup_reversed_geocoded_locations.json b/test/src/service/geo/data/lookup_reversed_geocoded_locations.json new file mode 100644 index 00000000..75eb6fc1 --- /dev/null +++ b/test/src/service/geo/data/lookup_reversed_geocoded_locations.json @@ -0,0 +1,147 @@ +{ + "query": { + "url": "https://api.twitter.com/1.1/geo/reverse_geocode.json?lat=10.0&long=10.0", + "type": "reverse_geocode", + "params": { + "accuracy": 0.0, + "coordinates": { + "coordinates": [ + 10.0, + 10.0 + ], + "type": "Point" + }, + "granularity": "neighborhood" + } + }, + "result": { + "places": [ + { + "id": "0050229b40144026", + "name": "Bauchi", + "full_name": "Bauchi, Nigeria", + "country": "Nigeria", + "country_code": "NG", + "url": "https://api.twitter.com/1.1/geo/id/0050229b40144026.json", + "place_type": "admin", + "attributes": {}, + "bounding_box": { + "type": "Polygon", + "coordinates": [ + [ + [ + 8.7283121, + 9.4556761 + ], + [ + 8.7283121, + 12.5264415 + ], + [ + 11.0409839, + 12.5264415 + ], + [ + 11.0409839, + 9.4556761 + ], + [ + 8.7283121, + 9.4556761 + ] + ] + ] + }, + "centroid": [ + 9.318905992315942, + 10.9908484 + ], + "contained_within": [ + { + "id": "59b27337f38d658b", + "name": "Nigeria", + "full_name": "Nigeria", + "country": "Nigeria", + "country_code": "NG", + "url": "https://api.twitter.com/1.1/geo/id/59b27337f38d658b.json", + "place_type": "country", + "attributes": {}, + "bounding_box": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.6654364, + 4.1974055 + ], + [ + 2.6654364, + 13.8881151 + ], + [ + 14.6777951, + 13.8881151 + ], + [ + 14.6777951, + 4.1974055 + ], + [ + 2.6654364, + 4.1974055 + ] + ] + ] + }, + "centroid": [ + 7.822704037915037, + 9.04266935 + ] + } + ] + }, + { + "id": "59b27337f38d658b", + "name": "Nigeria", + "full_name": "Nigeria", + "country": "Nigeria", + "country_code": "NG", + "url": "https://api.twitter.com/1.1/geo/id/59b27337f38d658b.json", + "place_type": "country", + "attributes": {}, + "bounding_box": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.6654364, + 4.1974055 + ], + [ + 2.6654364, + 13.8881151 + ], + [ + 14.6777951, + 13.8881151 + ], + [ + 14.6777951, + 4.1974055 + ], + [ + 2.6654364, + 4.1974055 + ] + ] + ] + }, + "centroid": [ + 7.822704037915037, + 9.04266935 + ], + "contained_within": [] + } + ] + } +} \ No newline at end of file diff --git a/test/src/service/geo/geo_service_test.dart b/test/src/service/geo/geo_service_test.dart index 33834641..cd33f68f 100644 --- a/test/src/service/geo/geo_service_test.dart +++ b/test/src/service/geo/geo_service_test.dart @@ -208,4 +208,120 @@ void main() { ); }); }); + + group('.lookupReverseGeocodedLocations', () { + test('normal case', () async { + final geoService = GeoService( + context: context.buildGetStub( + UserContext.oauth1Only, + '/1.1/geo/reverse_geocode.json', + 'test/src/service/geo/data/lookup_reversed_geocoded_locations.json', + { + 'lat': '10.0', + 'long': '20.0', + 'max_results': '10', + 'accuracy': '5', + 'granularity': 'country', + }, + ), + ); + + final response = await geoService.lookupReverseGeocodedLocations( + latitude: 10.0, + longitude: 20.0, + maxResults: 10, + accuracy: '5', + granularity: GeoGranularity.country, + ); + + expect(response, isA()); + expect(response.data, isA>()); + }); + + test('with invalid access token', () async { + final geoService = GeoService( + context: ClientContext( + bearerToken: '', + oauthTokens: OAuthTokens( + consumerKey: '1234', + consumerSecret: '1234', + accessToken: '1234', + accessTokenSecret: '1234', + ), + timeout: Duration(seconds: 10), + ), + ); + + expectUnauthorizedException( + () async => await geoService.lookupReverseGeocodedLocations( + latitude: 10.0, + longitude: 20.0, + ), + ); + }); + + test('with rate limit exceeded error', () async { + final geoService = GeoService( + context: context.buildGetStub( + UserContext.oauth1Only, + '/1.1/geo/reverse_geocode.json', + 'test/src/service/geo/data/lookup_reversed_geocoded_locations.json', + statusCode: 429, + { + 'lat': '10.0', + 'long': '20.0', + }, + ), + ); + + expectRateLimitExceededException( + () async => await geoService.lookupReverseGeocodedLocations( + latitude: 10.0, + longitude: 20.0, + ), + ); + }); + + test('with errors', () async { + final geoService = GeoService( + context: context.buildGetStub( + UserContext.oauth1Only, + '/1.1/geo/reverse_geocode.json', + 'test/src/service/geo/data/no_data.json', + { + 'lat': '10.0', + 'long': '20.0', + }, + ), + ); + + expectDataNotFoundExceptionDueToNoData( + () async => await geoService.lookupReverseGeocodedLocations( + latitude: 10.0, + longitude: 20.0, + ), + ); + }); + + test('with no json', () async { + final geoService = GeoService( + context: context.buildGetStub( + UserContext.oauth1Only, + '/1.1/geo/reverse_geocode.json', + 'test/src/service/geo/data/no_json.json', + { + 'lat': '10.0', + 'long': '20.0', + }, + ), + ); + + expectDataNotFoundExceptionDueToNoJson( + () async => await geoService.lookupReverseGeocodedLocations( + latitude: 10.0, + longitude: 20.0, + ), + ); + }); + }); } From 821dcd500cee96406910b617097ff58ea79f5629 Mon Sep 17 00:00:00 2001 From: myConsciousness Date: Mon, 5 Dec 2022 16:29:46 +0900 Subject: [PATCH 2/2] docs: prepare `v4.6.0` --- .vscode/settings.json | 1 + CHANGELOG.md | 2 ++ README.md | 1 + 3 files changed, 4 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index e96a22cd..86e70be8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,7 @@ "Decahose", "Elon", "geocode", + "Geocoded", "hashtagged", "hmac", "Kato", diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e41813..4435bd65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - `GET /1.1/trends/place.json` - `GET /1.1/trends/available.json` - `GET /1.1/trends/closest.json` +- Supported `/1.1/geo/reverse_geocode.json` endpoint for `Geo` service. ([#584](https://github.com/twitter-dart/twitter-api-v2/issues/584)) + ## v4.5.0 diff --git a/README.md b/README.md index fd83be56..1a54f55c 100644 --- a/README.md +++ b/README.md @@ -585,6 +585,7 @@ Future main() async { | Endpoint | Method Name | | ----------------------------- | ----------------------------------------------------------------------------------------------------------- | | GET /1.1/geo/id/:placeId.json | [lookupById](https://pub.dev/documentation/twitter_api_v2/latest/twitter_api_v2/GeoService/lookupById.html) | +| [GET /1.1/geo/reverse_geocode.json](https://developer.twitter.com/en/docs/twitter-api/v1/geo/places-near-location/api-reference/get-geo-reverse_geocode) | [lookupReverseGeocodedLocations](https://pub.dev/documentation/twitter_api_v2/latest/twitter_api_v2/GeoService/lookupReverseGeocodedLocations.html) | #### 1.3.7.2. Search Locations