Skip to content

Commit

Permalink
feat(geo): add listGeofences api
Browse files Browse the repository at this point in the history
  • Loading branch information
Tré Ammatuna committed Dec 4, 2021
1 parent 47d802c commit b863a9c
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 4 deletions.
49 changes: 49 additions & 0 deletions packages/geo/__tests__/Geo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
mockBatchPutGeofenceCommand,
geofencesWithInvalidId,
mockGetGeofenceCommand,
mockListGeofencesCommand,
} from './data';

LocationClient.prototype.send = jest.fn(async command => {
Expand Down Expand Up @@ -500,4 +501,52 @@ describe('Geo', () => {
);
});
});

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'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
mockBatchPutGeofenceCommand,
validGeometry,
mockGetGeofenceCommand,
mockListGeofencesCommand,
} from '../data';
import {
SearchByTextOptions,
Expand Down Expand Up @@ -535,4 +536,63 @@ describe('AmazonLocationServiceProvider', () => {
);
});
});

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()
.mockImplementation(mockListGeofencesCommand);

const locationProvider = new AmazonLocationServiceProvider();
locationProvider.configure(awsConfig.geo.amazon_location_service);

const geofences = await locationProvider.listGeofences();

expect(geofences.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 locationProvider = new AmazonLocationServiceProvider();
locationProvider.configure(awsConfig.geo.amazon_location_service);

const first100Geofences = await locationProvider.listGeofences();

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

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

test('should error if there are no geofenceCollections in config', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

const locationProvider = new AmazonLocationServiceProvider();
locationProvider.configure({});

await expect(locationProvider.listGeofences()).rejects.toThrow(
'No Geofence Collections found, please run `amplify add geo` to create one and run `amplify push` after.'
);
});
});
});
33 changes: 33 additions & 0 deletions packages/geo/__tests__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import {
BatchPutGeofenceCommand,
GetGeofenceCommand,
ListGeofencesCommand,
} from '@aws-sdk/client-location';
import camelcaseKeys from 'camelcase-keys';

Expand Down Expand Up @@ -233,6 +234,22 @@ export function createGeofenceInputArray(numberOfGeofences) {
return geofences;
}

export function createGeofenceOutputArray(numberOfGeofences) {
const geofences = [];
for (let i = 0; i < numberOfGeofences; i++) {
geofences.push({
GeofenceId: `validGeofenceId${i}`,
Geometry: {
Polygon: validPolygon,
},
Status: 'ACTIVE',
CreateTime: '2020-04-01T21:00:00.000Z',
UpdateTime: '2020-04-01T21:00:00.000Z',
});
}
return geofences;
}

export function mockBatchPutGeofenceCommand(command) {
if (command instanceof BatchPutGeofenceCommand) {
return {
Expand Down Expand Up @@ -263,3 +280,19 @@ export function mockGetGeofenceCommand(command) {
return geofence;
}
}

export function mockListGeofencesCommand(command) {
if (command instanceof ListGeofencesCommand) {
const geofences = createGeofenceOutputArray(200);
if (command.input.NextToken === 'THIS IS YOUR TOKEN') {
return {
Entries: geofences.slice(100, 200),
NextToken: 'THIS IS YOUR SECOND TOKEN',
};
}
return {
Entries: geofences.slice(0, 100),
NextToken: 'THIS IS YOUR TOKEN',
};
}
}
23 changes: 23 additions & 0 deletions packages/geo/src/Geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
GeofenceOptions,
CreateUpdateGeofenceResults,
Geofence,
ListGeofenceOptions,
ListGeofenceResults,
} from './types';

const logger = new Logger('Geo');
Expand Down Expand Up @@ -258,6 +260,27 @@ export class GeoClass {
throw error;
}
}

/**
* List geofences from a geofence collection
* @param options?: ListGeofenceOptions
* @returns {Promise<ListGeofencesResults>} - Promise that resolves to an object with:
* entries: list of geofences - 100 geofences are listed per page
* nextToken: token for next page of geofences
*/
public async listGeofences(
options?: ListGeofenceOptions
): Promise<ListGeofenceResults> {
const { providerName = DEFAULT_PROVIDER } = options || {};
const prov = this.getPluggable(providerName);

try {
return await prov.listGeofences(options);
} catch (error) {
logger.debug(error);
throw error;
}
}
}

export const Geo = new GeoClass();
Expand Down
85 changes: 85 additions & 0 deletions packages/geo/src/Providers/AmazonLocationServiceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import {
GetGeofenceCommand,
GetGeofenceCommandInput,
GetGeofenceCommandOutput,
ListGeofencesCommand,
ListGeofencesCommandInput,
ListGeofencesCommandOutput,
} from '@aws-sdk/client-location';

import {
Expand All @@ -42,6 +45,8 @@ import {
Coordinates,
GeofenceInput,
AmazonLocationServiceGeofenceOptions,
AmazonLocationServiceListGeofenceOptions,
ListGeofenceResults,
AmazonLocationServiceGeofenceStatus,
CreateUpdateGeofenceResults,
AmazonLocationServiceGeofence,
Expand Down Expand Up @@ -425,6 +430,86 @@ export class AmazonLocationServiceProvider implements GeoProvider {
return geofence;
}

/**
* List geofences from a geofence collection
* @param options?: ListGeofenceOptions
* @returns {Promise<ListGeofencesResults>} - Promise that resolves to an object with:
* entries: list of geofences - 100 geofences are listed per page
* nextToken: token for next page of geofences
*/
public async listGeofences(
options?: AmazonLocationServiceListGeofenceOptions
): Promise<ListGeofenceResults> {
const credentialsOK = await this._ensureCredentials();
if (!credentialsOK) {
throw new Error('No credentials');
}

// Verify geofence collection exists in aws-config.js
try {
this._verifyGeofenceCollections(options?.collectionName);
} catch (error) {
logger.debug(error);
throw error;
}

// Create Amazon Location Service Client
const client = new LocationClient({
credentials: this._config.credentials,
region: this._config.region,
customUserAgent: getAmplifyUserAgent(),
});

// Create Amazon Location Service input
const listGeofencesInput: ListGeofencesCommandInput = {
NextToken: options?.nextToken,
CollectionName:
options?.collectionName || this._config.geofenceCollections.default,
};

// Create Amazon Location Service command
const command: ListGeofencesCommand = new ListGeofencesCommand(
listGeofencesInput
);

// Make API call
let response: ListGeofencesCommandOutput;
try {
response = await client.send(command);
} catch (error) {
logger.debug(error);
throw error;
}

// Convert response to camelCase for return
const { NextToken, Entries } = response;

const results: ListGeofenceResults = {
entries: Entries.map(
({
GeofenceId,
CreateTime,
UpdateTime,
Status,
Geometry: { Polygon },
}) => {
return {
geofenceId: GeofenceId,
createTime: CreateTime,
updateTime: UpdateTime,
status: Status,
geometry: {
polygon: Polygon as GeofencePolygon,
},
};
}
),
nextToken: NextToken,
};

return results;
}

/**
* @private
*/
Expand Down
11 changes: 10 additions & 1 deletion packages/geo/src/types/AmazonLocationServiceProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { MapStyle, GeofenceOptions, Geofence } from './Geo';
import {
MapStyle,
GeofenceOptions,
ListGeofenceOptions,
Geofence,
} from './Geo';

// Maps
export interface AmazonLocationServiceMapStyle extends MapStyle {
Expand All @@ -21,3 +26,7 @@ export type AmazonLocationServiceGeofenceStatus =
export type AmazonLocationServiceGeofence = Geofence & {
status: AmazonLocationServiceGeofenceStatus;
};

export type AmazonLocationServiceListGeofenceOptions = ListGeofenceOptions & {
collectionName?: string;
};
15 changes: 13 additions & 2 deletions packages/geo/src/types/Geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,24 @@ export type GeofenceBase = {
updateTime: Date;
};

// Output object for getGeofence
// Results object for getGeofence
export type Geofence = GeofenceBase & {
geometry: PolygonGeometry;
};

// Output object for createGeofence and updateGeofence
// Results object for createGeofence and updateGeofence
export type CreateUpdateGeofenceResults = {
successes: GeofenceBase[];
errors: GeofenceError[];
};

// Options object for listGeofence
export type ListGeofenceOptions = GeofenceOptions & {
nextToken?: string;
};

// Results options for listGeofence
export type ListGeofenceResults = {
entries: Geofence[];
nextToken: string;
};
10 changes: 9 additions & 1 deletion packages/geo/src/types/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
Geofence,
GeofenceInput,
GeofenceOptions,
ListGeofenceOptions,
ListGeofenceResults,
CreateUpdateGeofenceResults,
} from './Geo';

Expand Down Expand Up @@ -54,5 +56,11 @@ export interface GeoProvider {
): Promise<CreateUpdateGeofenceResults>;

// get a single geofence
getGeofence(geofenceId: string, options?: GeofenceOptions): Promise<Geofence>;
getGeofence(
geofenceId: string,
options?: ListGeofenceOptions
): Promise<Geofence>;

// list all geofences
listGeofences(options?: ListGeofenceOptions): Promise<ListGeofenceResults>;
}

0 comments on commit b863a9c

Please sign in to comment.