Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEVEXP-506: E2E ElasticSipTrunking/CallsHistory #122

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const populateCallsList = (
const requestData: ElasticSipTrunking.FindCallsRequestData = {
trunkId,
callResult: 'COMPLETED',
direction: 'INBOUND',
direction: 'inbound',
};

const elasticSipTrunkingService = initElasticSipTrunkingService();
Expand Down
2 changes: 1 addition & 1 deletion packages/elastic-sip-trunking/src/models/v1/enum.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export type DirectionEnum = 'INBOUND' | 'OUTBOUND';
export type DirectionEnum = 'inbound' | 'outbound';

export type CallResult = 'COMPLETED' | 'NO_ANSWER' | 'CANCEL' | 'BUSY' | 'FAILED';
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export interface Money {
/** The 3-letter currency code defined in ISO 4217. */
currencyCode?: string;
/** The amount with 4 decimals and decimal delimiter `.`. */
amount?: string;
amount?: number;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CallResult, DirectionEnum } from '../../enum';
import { DateFormat } from '@sinch/sdk-client';

export interface FindCallsRequestData {
/** A phone number that you want to use to filter results. You can pass a partial number to get all calls sent to numbers that start with the number you passed. */
Expand All @@ -7,8 +8,10 @@ export interface FindCallsRequestData {
'to'?: string;
/** Only include calls made from this trunk. */
'trunkId'?: string;
/** Filter calls based on `createTime`. You make the query more precise, fewer results will be returned. For example, 2021-02-01 will return all calls from the first of February 2021, and 2021-02-01T14:00:00Z will return all calls after 14:00 on the first of February. This field also supports <= and >= to search for calls in a range ?createTime>=2021-10-01&createTime<=2021-10-30 to get a list if calls for october 2021 It is also possible to submit partial dates for example createTime=2021-02 will return all calls for February ***Defaults to 24 hours*** ***Internal notes*** If a customer submits = and not <> we should add min and max for the date range psueodo sql ``` createTime = 2021-02-01 select * from calls where createTime >= 2021-02-01 and createTime <= 2021-02-01T23:59:59Z createTime = 2021-02-01T08 select * from calls where createTime >= 2021-02-01T08:00:00 and createTime <= 2021-02-01T08:59:59Z ``` but if they submit < or > we should just use the value they submitted and parse it a complete date */
/** Filter calls based on `createTime`. You make the query more precise, fewer results will be returned. For example, 2021-02-01 will return all calls from the first of February 2021, and 2021-02-01T14:00:00Z will return all calls after 14:00 on the first of February. */
'createTime'?: string;
/** Filter calls based on `createTime`. It will filter the calls on a range of dates. */
'createTimeRange'?: DateRangeFilter;
/** only include calls by on the callResult(s), example callResult=COMPLETED will return all calls which have completed normally. */
'callResult'?: CallResult;
/** only include calls by on the direction(s), example direction=INBOUND,OUTBOUND will return all calls that are inbound or outbound. */
Expand All @@ -18,3 +21,20 @@ export interface FindCallsRequestData {
/** The maximum number of items to return per request. The default is 100 and the maximum is 1000. If you need to export larger amounts and pagination is not suitable for you can use the Export function in the dashboard. */
'pageSize'?: number;
}

/**
* Filter calls based on `createTime`. If not value is submitted, the default value is the prior week.
* - `from: '2024-02-15'` will return all calls from February 2024, 15th
* - `from: '2024-02-01T14:00:00Z'` will return all calls after 14:00:00 on the first of February 2024.
* - `from: '2024-02-01T14:00:00Z'` + `to: '2024-02-01T15:00:00Z'` will return all calls between 14:00:00 and 15:00:00 (inclusive) on the first of February 2024.
* - `from: '2024-02-01'` + `to: '2024-02-29'` will return all calls for all of February 2024.
*
* Note: It is also possible to submit partial dates.
* - `from: '2024-02'` will return all calls for February 2024
*/
export interface DateRangeFilter {
/** */
from?: string | Date | DateFormat;
/** */
to?: string | Date | DateFormat;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
PaginationEnum,
buildPageResultPromise,
createIteratorMethodsForPagination,
formatCreateTimeFilter,
formatCreateTimeRangeFilter,
} from '@sinch/sdk-client';
import { ElasticSipTrunkingDomainApi } from '../elastic-sip-trunking-domain-api';

Expand All @@ -22,16 +24,18 @@ export class CallsHistoryApi extends ElasticSipTrunkingDomainApi {
}

/**
* Find calls
* Find calls by query parameters.
* @param { FindCallsRequestData } data - The data to provide to the API call.
* @return { ApiListPromise<Call> }
*/
public find(data: FindCallsRequestData): ApiListPromise<Call> {
this.client = this.getSinchClient();
data['createTime'] = data['createTime'] !== undefined ? data['createTime'] : 'now-24h';
const getParams = this.client.extractQueryParams<FindCallsRequestData>(data, [
'from', 'to', 'trunkId', 'createTime', 'callResult', 'direction', 'page', 'pageSize']);
(getParams as any).createTime = JSON.stringify(formatCreateTimeFilter(data.createTime));
(getParams as any)['createTime>'] = JSON.stringify(formatCreateTimeRangeFilter(data.createTimeRange?.from));
(getParams as any)['createTime<'] = JSON.stringify(formatCreateTimeRangeFilter(data.createTimeRange?.to));

const headers: { [key: string]: string | undefined } = {
'Content-Type': 'application/json',
'Accept': 'application/json',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ describe('CallsApi', () => {
callId: '01AQ3D80ZKSSK35TZFKM3JG9CT',
to: '+15551239898',
from: '+14155553434',
direction: 'INBOUND',
direction: 'inbound',
answerTime: new Date('2021-11-01T23:26:50Z'),
endTime: new Date('2021-11-01T23:27:35Z'),
durationSeconds: 45,
callResult: 'COMPLETED',
pricePerMinute: {
currencyCode: 'USD',
amount: '0.0040',
amount: 0.0040,
},
billingDurationSeconds: 60,
price: {
currencyCode: 'USD',
amount: '0.0040',
amount: 0.0040,
},
createTime: new Date('2021-11-01T23:20:50Z'),
projectId: '1bf62742-7b84-4666-9cbe-8e5734fd57d0',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { ElasticSipTrunking, ElasticSipTrunkingService, CallsHistoryApi } from '../../../../src';
import { Given, Then, When } from '@cucumber/cucumber';
import assert from 'assert';
import { PageResult } from '@sinch/sdk-client';

let callsHistoryApi: CallsHistoryApi;
let listResponse: PageResult<ElasticSipTrunking.Call>;
let callsHistoryList: ElasticSipTrunking.Call[];
let pagesIteration: number;

Given('the Elastic SIP Trunking service "Calls History" is available', function () {
const elasticSipTrunkingService = new ElasticSipTrunkingService({
projectId: 'tinyfrog-jump-high-over-lilypadbasin',
keyId: 'keyId',
keySecret: 'keySecret',
authHostname: 'http://localhost:3011',
elasticSipTrunkingHostname: 'http://localhost:3016',
});
callsHistoryApi = elasticSipTrunkingService.calls;
});

When('I send a request to find the a page from the Calls History with no filtering parameters', async () => {
listResponse = await callsHistoryApi.find({});
});

Then('the response contains {string} Calls from history', (expectedAnswer: string) => {
const expectedCallsCount = parseInt(expectedAnswer, 10);
assert.equal(listResponse.data.length, expectedCallsCount);
});

Then('a Call History object from the page result contains the Call History details', () => {
const call = listResponse.data[0];
assert.equal(call.callId, 'N00DL3C4T5');
assert.equal(call.to, '12017777777');
assert.equal(call.from, 'sip:[email protected]');
const direction: ElasticSipTrunking.DirectionEnum = 'outbound';
assert.equal(call.direction, direction);
assert.deepEqual(call.answerTime, new Date('2024-06-06T16:57:52Z'));
assert.deepEqual(call.endTime, new Date('2024-06-06T16:57:55Z'));
assert.equal(call.durationSeconds, 4);
assert.equal(call.billingDurationSeconds, 60);
const callResult: ElasticSipTrunking.CallResult = 'COMPLETED';
assert.equal(call.callResult, callResult);
const price: ElasticSipTrunking.Money = {
amount: 0.004,
currencyCode: 'USD',
};
assert.deepEqual(call.pricePerMinute, price);
assert.deepEqual(call.price, price);
assert.deepEqual(call.createTime, new Date('2024-06-06T16:57:45+0000'));
assert.equal(call.projectId, 'tinyfrog-jump-high-over-lilypadbasin');
assert.equal(call.trunkId, '01W4FFL35P4NC4K35SIPTRUNK2');
});

When('I send a request to list all the Calls from the Calls History', async () => {
callsHistoryList = [];
for await (const call of callsHistoryApi.find({})) {
callsHistoryList.push(call);
}
});

When('I iterate manually over the Calls History pages', async () => {
callsHistoryList = [];
listResponse = await callsHistoryApi.find({});
callsHistoryList.push(...listResponse.data);
pagesIteration = 1;
let reachedEndOfPages = false;
while (!reachedEndOfPages) {
if (listResponse.hasNextPage) {
listResponse = await listResponse.nextPage();
callsHistoryList.push(...listResponse.data);
pagesIteration++;
} else {
reachedEndOfPages = true;
}
}
});

Then('the Calls History list contains {string} Calls', (expectedAnswer: string) => {
const expectedSipEndpointsCount = parseInt(expectedAnswer, 10);
assert.equal(callsHistoryList.length, expectedSipEndpointsCount);
});

Then('the Calls History iteration result contains the data from {string} pages', (expectedAnswer: string) => {
const expectedPagesCount = parseInt(expectedAnswer, 10);
assert.equal(pagesIteration, expectedPagesCount);
});

When('I send a request to find the a page from the Calls History with a createTime range filter', async () => {
listResponse = await callsHistoryApi.find({
createTimeRange: {
from: '2024-06-06T16:00:00',
},
});
});
37 changes: 5 additions & 32 deletions packages/fax/src/rest/v3/faxes/faxes-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
ApiListPromise,
buildPageResultPromise,
createIteratorMethodsForPagination,
DateFormat,
FileBuffer,
formatDate,
formatCreateTimeFilter,
formatCreateTimeRangeFilter,
PaginatedApiProperties,
PaginationEnum,
RequestBody,
Expand Down Expand Up @@ -137,9 +137,9 @@ export class FaxesApi extends FaxDomainApi {
'from',
'pageSize',
'page']);
(getParams as any).createTime = JSON.stringify(this.formatCreateTimeFilter(data.createTime));
(getParams as any)['createTime>'] = JSON.stringify(this.formatCreateTimeRangeFilter(data.createTimeRange?.from));
(getParams as any)['createTime<'] = JSON.stringify(this.formatCreateTimeRangeFilter(data.createTimeRange?.to));
(getParams as any).createTime = JSON.stringify(formatCreateTimeFilter(data.createTime));
(getParams as any)['createTime>'] = JSON.stringify(formatCreateTimeRangeFilter(data.createTimeRange?.from));
(getParams as any)['createTime<'] = JSON.stringify(formatCreateTimeRangeFilter(data.createTimeRange?.to));

const headers: { [key: string]: string | undefined } = {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -174,33 +174,6 @@ export class FaxesApi extends FaxDomainApi {
return listPromise as ApiListPromise<Fax>;
}

formatCreateTimeFilter = (createTime: string | Date | undefined): string | undefined => {
if (createTime !== undefined) {
if (typeof createTime === 'string') {
if (createTime.indexOf('T') > -1) {
return createTime.substring(0, createTime.indexOf('T'));
}
return createTime;
} else {
return formatDate(createTime, 'day');
}
}
return undefined;
};

formatCreateTimeRangeFilter = (timeBoundary: string | Date | DateFormat | undefined): string | undefined => {
if (timeBoundary !== undefined) {
if (typeof timeBoundary === 'string') {
return timeBoundary;
} else if (timeBoundary instanceof Date) {
return formatDate(timeBoundary);
} else {
return formatDate(timeBoundary.date, timeBoundary.unit);
}
}
return undefined;
};

/**
* Send a fax or multiple faxes
* Create and send one or multiple faxes. Fax content may be supplied via one or more files or URLs of supported filetypes.
Expand Down
78 changes: 1 addition & 77 deletions packages/fax/tests/rest/v3/faxes/faxes-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DateFormat, FileBuffer, SinchClientParameters } from '@sinch/sdk-client';
import { FileBuffer, SinchClientParameters } from '@sinch/sdk-client';
import {
Fax,
FaxesApi,
Expand Down Expand Up @@ -151,82 +151,6 @@ describe('FaxesApi', () => {
expect(response.data).toBeDefined();
expect(fixture.list).toHaveBeenCalledWith(requestData);
});

it('should format a createTime parameter', () => {
const dateUndefined = undefined;
let formattedDateFilter = faxesApi.formatCreateTimeFilter(dateUndefined);
expect(formattedDateFilter).toBeUndefined();

const dateString = '2024-05-01';
formattedDateFilter = faxesApi.formatCreateTimeFilter(dateString);
expect(formattedDateFilter).toBe('2024-05-01');

const dateWithSecondsString ='2024-05-01T13:00:00Z';
formattedDateFilter = faxesApi.formatCreateTimeFilter(dateWithSecondsString);
expect(formattedDateFilter).toBe('2024-05-01');

const dateWithSeconds = new Date('2024-05-01T13:00:00Z');
formattedDateFilter = faxesApi.formatCreateTimeFilter(dateWithSeconds);
expect(formattedDateFilter).toBe('2024-05-01');
});

it('should format a datetime range filter', () => {
const dateTimeRangeUndefined = undefined;
let formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeUndefined);
expect(formattedDateTimeRangeFilter).toBeUndefined();

const dateTimeRangeString = '2024-05-01';
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeString);
expect(formattedDateTimeRangeFilter).toBe('2024-05-01');

const dateTimeRangeNoUnit: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeNoUnit);
expect(formattedDateTimeRangeFilter).toBe('2024-05-01T13:15:30Z');

const dateTimeRangeWithYear: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
unit: 'year',
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeWithYear);
expect(formattedDateTimeRangeFilter).toBe('2024');

const dateTimeRangeWithMonth: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
unit: 'month',
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeWithMonth);
expect(formattedDateTimeRangeFilter).toBe('2024-05');

const dateTimeRangeWithDay: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
unit: 'day',
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeWithDay);
expect(formattedDateTimeRangeFilter).toBe('2024-05-01');

const dateTimeRangeWithHours: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
unit: 'hour',
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeWithHours);
expect(formattedDateTimeRangeFilter).toBe('2024-05-01T13:00:00Z');

const dateTimeRangeWithMinutes: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
unit: 'minute',
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeWithMinutes);
expect(formattedDateTimeRangeFilter).toBe('2024-05-01T13:15:00Z');

const dateTimeRangeWithSeconds: DateFormat = {
date: new Date('2024-05-01T13:15:30Z'),
unit: 'second',
};
formattedDateTimeRangeFilter = faxesApi.formatCreateTimeRangeFilter(dateTimeRangeWithSeconds);
expect(formattedDateTimeRangeFilter).toBe('2024-05-01T13:15:30Z');
});
});

describe ('sendFax', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk-client/src/client/api-client-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ const isDateString = (value: any): boolean => {
};

const addTimezoneIfMissing = (timestampValue: string): string => {
// Check the formats +XX:XX, +XX and Z
const timeZoneRegex = /([+-]\d{2}(:\d{2})|Z)$/;
// Check the formats +XX:XX, +XX, +XXXX and Z
const timeZoneRegex = /([+-]\d{2}(:\d{2})|[+-]\d{4}|Z)$/;
if (!timeZoneRegex.test(timestampValue)) {
const hourMinutesTimezoneRegex = /([+-]\d{2})$/;
// A timestamp with no minutes in the timezone cannot be converted into a Date => assume it's :00
Expand Down
Loading
Loading