Skip to content

Commit

Permalink
feat(@aws-amplify/geo): add geofence APIs (#9278)
Browse files Browse the repository at this point in the history
* feat(@aws-amplify/geo): verify coordinates

* chore(@aws-amplify/geo): remove unused file

* feat(@aws-amplify/geo): add initial types for geofences

* chore(@aws-amplify/geo): add types and validation utils

* feat(@aws-amplify/geo): add createGeofence to provider

* feat(@aws-amplify/geo): add createGeofence to Geo

* fix(@aws-amplify/geo): remove bad import

* fix(@aws-amplify/geo): update jsdoc on createGeofence

Co-authored-by: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com>

* fix(@aws-amplify/geo): update jsdoc on createGeofence in provider

Co-authored-by: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com>

* fix(@aws-amplify/geo): update error message for bad polygon

Co-authored-by: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com>

* fix(@aws-amplify/geo): first update from PR comments

* fix(@aws-amplify/geo): update #2 from PR comments

* fix(@aws-amplify/geo): update types and batch calls

* Update packages/geo/src/types/Geo.ts

Co-authored-by: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com>

* fix(geo): update types from PR feedback

* chore(geo): add error handling on failed API call

* test(geo): add test for batch API calls

* test(geo): add test for bad api call in createGeofences

feat(geo): add getGeofence api (#9309)

* feat(@aws-amplify/geo): verify coordinates

* chore(@aws-amplify/geo): add types and validation utils

* feat(@aws-amplify/geo): add createGeofence to provider

* feat(@aws-amplify/geo): add createGeofence to provider

* feat(geo): add getGeofence api

* chore(geo): combine some try/catch blocks

* chore(geo): add > to jsdoc comment

feat(geo): add listGeofences api (#9310)

feat(geo): add updateGeofences api (#9312)

* feat(geo): add listGeofences api

* feat(geo): add initial updateGeofences api

* refactor(geo): optimize _batchPutGeofence function

feat(geo): add deleteGeofences api (#9314)

* feat(@aws-amplify/geo): verify coordinates

* feat(geo): batch deleteGeofences call

* chore(geo): fix issue with response

refactor(geo): update loggers and remove unused code

refactor(geo): create/updateGeofences => saveGeofences

chore(geo): split out testData and testFunctions
  • Loading branch information
TreTuna authored and Tré Ammatuna committed Mar 30, 2022
1 parent a1640db commit 6d33e12
Show file tree
Hide file tree
Showing 13 changed files with 1,906 additions and 88 deletions.
208 changes: 204 additions & 4 deletions packages/geo/__tests__/Geo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,19 @@ import {
awsConfig,
TestPlacePascalCase,
testPlaceCamelCase,
} from './data';
validGeometry,
validGeofences,
validGeofence1,
singleGeofenceCamelcaseResults,
batchGeofencesCamelcaseResults,
geofencesWithInvalidId,
} from './testData';

import {
mockBatchPutGeofenceCommand,
mockGetGeofenceCommand,
mockListGeofencesCommand,
} from './testUtils';

LocationClient.prototype.send = jest.fn(async command => {
if (
Expand Down Expand Up @@ -134,7 +146,7 @@ describe('Geo', () => {
geo.configure({});

expect(() => geo.getAvailableMaps()).toThrow(
"No map resources found in amplify config, run 'amplify add geo' to create them and run `amplify push` after"
"No map resources found in amplify config, run 'amplify add geo' to create one and run `amplify push` after"
);
});

Expand All @@ -159,7 +171,7 @@ describe('Geo', () => {
geo.configure({});

expect(() => geo.getDefaultMap()).toThrow(
"No map resources found in amplify config, run 'amplify add geo' to create them and run `amplify push` after"
"No map resources found in amplify config, run 'amplify add geo' to create one and run `amplify push` after"
);
});

Expand Down Expand Up @@ -309,7 +321,7 @@ describe('Geo', () => {
});

describe('searchByCoordinates', () => {
const testCoordinates: Coordinates = [12345, 67890];
const testCoordinates: Coordinates = [45, 90];

test('should search with just coordinate input', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
Expand Down Expand Up @@ -371,4 +383,192 @@ describe('Geo', () => {
);
});
});

describe('saveGeofences', () => {
test('saveGeofences with a single geofence', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementationOnce(mockBatchPutGeofenceCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected
const results = await geo.saveGeofences(validGeofence1);
expect(results).toEqual(singleGeofenceCamelcaseResults);

// Expect that the API was called with the proper input
const spyon = jest.spyOn(LocationClient.prototype, 'send');
const input = spyon.mock.calls[0][0].input;
const output = {
Entries: [
{
GeofenceId: validGeofence1.geofenceId,
Geometry: {
Polygon: validGeofence1.geometry.polygon,
},
},
],
CollectionName: 'geofenceCollectionExample',
};
expect(input).toEqual(output);
});

test('saveGeofences with multiple geofences', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementation(mockBatchPutGeofenceCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected
const results = await geo.saveGeofences(validGeofences);
expect(results).toEqual(batchGeofencesCamelcaseResults);

// Expect that the API was called the right amount of times
const expectedNumberOfCalls = Math.floor(validGeofences.length / 10) + 1;
expect(LocationClient.prototype.send).toHaveBeenCalledTimes(
expectedNumberOfCalls
);
});

test('should error if there is a bad geofence in the input', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

const geo = new GeoClass();
geo.configure(awsConfig);

await expect(
geo.saveGeofences(geofencesWithInvalidId)
).rejects.toThrowError(
`Invalid geofenceId: t|-|!$ !$ N()T V@|_!D Ids can only contain alphanumeric characters, hyphens, underscores and periods.`
);
});

test('should fail if there is no provider', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

const geo = new GeoClass();
geo.configure(awsConfig);
geo.removePluggable('AmazonLocationService');

await expect(geo.saveGeofences(validGeofence1)).rejects.toThrow(
'No plugin found in Geo for the provider'
);
});
});

describe('getGeofence', () => {
test('getGeofence returns the right geofence', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementationOnce(mockGetGeofenceCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected
const results = await geo.getGeofence('testGeofenceId');
const expected = {
geofenceId: 'testGeofenceId',
geometry: validGeometry,
createTime: '2020-04-01T21:00:00.000Z',
updateTime: '2020-04-01T21:00:00.000Z',
status: 'ACTIVE',
};
expect(results).toEqual(expected);

// Expect that the API was called with the proper input
const spyon = jest.spyOn(LocationClient.prototype, 'send');
const input = spyon.mock.calls[0][0].input;
const output = {
GeofenceId: 'testGeofenceId',
CollectionName: 'geofenceCollectionExample',
};
expect(input).toEqual(output);
});

test('getGeofence errors when a bad geofenceId is given', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementationOnce(mockGetGeofenceCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

const badGeofenceId = 't|-|!$ !$ N()T V@|_!D';
await expect(geo.getGeofence(badGeofenceId)).rejects.toThrow(
`Invalid geofenceId: ${badGeofenceId} Ids can only contain alphanumeric characters, hyphens, underscores and periods.`
);
});
});

describe('listGeofences', () => {
test('listGeofences gets the first 100 geofences when no arguments are given', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementationOnce(mockListGeofencesCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected
const results = await geo.listGeofences();
expect(results.entries.length).toEqual(100);
});

test('listGeofences gets the second 100 geofences when nextToken is passed', async () => {
jest.spyOn(Credentials, 'get').mockImplementation(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementation(mockListGeofencesCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected

const first100Geofences = await geo.listGeofences();

const second100Geofences = await geo.listGeofences({
nextToken: first100Geofences.nextToken,
});

expect(second100Geofences.entries.length).toEqual(100);
expect(second100Geofences.entries[0].geofenceId).toEqual(
'validGeofenceId100'
);
expect(second100Geofences.entries[99].geofenceId).toEqual(
'validGeofenceId199'
);
});
});
});
Loading

0 comments on commit 6d33e12

Please sign in to comment.