Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
@@ -0,0 +1,3 @@
export interface LocationCreatedResponse {
id: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { InputFieldCreationRequest } from '../input-field/input-field-creation-request.js';
import { OpeningHours } from '../opening-hours.js';
import { OpeningHoursException } from '../opening-hours-exception.js';

export interface LocationCreationRequest {
name: string;
latitude: number;
longitude: number;
address: string;
country: string;
openingHours?: {
regular?: OpeningHours;
exceptions?: OpeningHoursException[];
};
inputFields?: InputFieldCreationRequest[];
languages?: string[];
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as sinon from 'sinon';
import { Desk } from '../../model/desk';
import { ResponseValidationError } from '../../model/errors/response-validation-error';
import { InputFieldCreationRequest } from '../../model/input-field/input-field-creation-request';
import { FirstNameFieldCreationRequest } from '../../model/input-field/first-name-field-creation-request';
import { NumericFieldCreationRequest } from '../../model/input-field/numeric-field-creation-request';
import { SelectFieldCreationRequest } from '../../model/input-field/select-field-creation-request';
import { LocationCreationRequest } from '../../model/location/location-creation-request';
import { Qminder } from '../../qminder';
import { LocationService } from './location.service';

Expand Down Expand Up @@ -229,6 +231,139 @@ describe('Location service', function () {
});
});

describe('create()', function () {
const SUCCESSFUL_RESPONSE = { id: '12345' };
const VALID_REQUEST: LocationCreationRequest = {
name: 'Main Office',
latitude: 59.4297,
longitude: 24.8149,
address: '123 Main St',
country: 'EE',
};

it('sends the request to the correct URL with JSON body and version headers', async function () {
requestStub.resolves(SUCCESSFUL_RESPONSE);
const result = await LocationService.create(VALID_REQUEST);
expect(requestStub.firstCall.args).toEqual([
'locations',
{
method: 'POST',
body: JSON.stringify(VALID_REQUEST),
headers: { 'X-Qminder-API-Version': '2020-09-01' },
},
]);
expect(result).toEqual(SUCCESSFUL_RESPONSE);
});

it('sends optional fields when provided', async function () {
requestStub.resolves(SUCCESSFUL_RESPONSE);
const request: LocationCreationRequest = {
...VALID_REQUEST,
openingHours: {
regular: {
mon: {
businessHours: [
{
opens: { hours: 9, minutes: 0 },
closes: { hours: 17, minutes: 0 },
},
],
},
tue: {},
wed: {},
thu: {},
fri: {},
sat: { closed: true as const },
sun: { closed: true as const },
},
},
inputFields: [],
languages: ['en', 'et'],
};
await LocationService.create(request);
expect(requestStub.firstCall.args).toEqual([
'locations',
{
method: 'POST',
body: JSON.stringify(request),
headers: { 'X-Qminder-API-Version': '2020-09-01' },
},
]);
});

it('throws ResponseValidationError when response does not contain id', async function () {
requestStub.resolves({});
await expect(LocationService.create(VALID_REQUEST)).rejects.toThrow(
new ResponseValidationError('Response does not contain "id"'),
);
});

it('throws when request is missing', async function () {
await expect(LocationService.create(null as any)).rejects.toThrow(
'Location creation request invalid or missing.',
);
});

it('throws when name is missing', async function () {
await expect(
LocationService.create({ ...VALID_REQUEST, name: '' }),
).rejects.toThrow('Cannot create a location without a name.');
});

it('throws when latitude is missing', async function () {
await expect(
LocationService.create({
...VALID_REQUEST,
latitude: undefined as any,
}),
).rejects.toThrow('Cannot create a location without a valid latitude.');
});

it('throws when latitude is NaN', async function () {
await expect(
LocationService.create({ ...VALID_REQUEST, latitude: NaN }),
).rejects.toThrow('Cannot create a location without a valid latitude.');
});

it('throws when longitude is missing', async function () {
await expect(
LocationService.create({
...VALID_REQUEST,
longitude: undefined as any,
}),
).rejects.toThrow('Cannot create a location without a valid longitude.');
});

it('throws when longitude is NaN', async function () {
await expect(
LocationService.create({ ...VALID_REQUEST, longitude: NaN }),
).rejects.toThrow('Cannot create a location without a valid longitude.');
});

it('throws when address is missing', async function () {
await expect(
LocationService.create({ ...VALID_REQUEST, address: '' }),
).rejects.toThrow('Cannot create a location without an address.');
});

it('throws when country is missing', async function () {
await expect(
LocationService.create({ ...VALID_REQUEST, country: '' }),
).rejects.toThrow('Cannot create a location without a country.');
});

it('accepts latitude 0 and longitude 0 as valid coordinates', async function () {
requestStub.resolves(SUCCESSFUL_RESPONSE);
const request: LocationCreationRequest = {
...VALID_REQUEST,
latitude: 0,
longitude: 0,
};
const result = await LocationService.create(request);
expect(result).toEqual(SUCCESSFUL_RESPONSE);
});
});

describe('createInputField()', function () {
beforeEach(function () {
requestStub.withArgs('input-fields').resolves({});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
create,
createInputField,
details,
getDesks,
Expand All @@ -22,6 +23,28 @@ import {
* ```
*/
export const LocationService = {
/**
* Create a new Location and return its ID.
*
* Calls the following HTTP API: `POST /locations`
*
* For example:
*
* ```javascript
* const response = await Qminder.Location.create({
* name: 'Main Office',
* latitude: 59.4297,
* longitude: 24.8149,
* address: '123 Main St',
* country: 'EE',
* });
* console.log(response.id); // "12345"
* ```
* @param request the parameters of the new location
* @returns a Promise that resolves to a LocationCreatedResponse containing the new location's ID.
*/
create,

/**
* List all locations the API key has access to.
* The API key belongs to a particular account and has access to all locations of the account.
Expand Down
38 changes: 38 additions & 0 deletions packages/javascript-api/src/lib/services/location/location.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Desk } from '../../model/desk.js';
import { ResponseValidationError } from '../../model/errors/response-validation-error.js';
import { InputFieldCreationRequest } from '../../model/input-field/input-field-creation-request.js';
import { LocationCreatedResponse } from '../../model/location/location-created-response.js';
import { LocationCreationRequest } from '../../model/location/location-creation-request.js';
import { Location } from '../../model/location.js';
import { OpeningHours } from '../../model/opening-hours.js';
import { OpeningHoursException } from '../../model/opening-hours-exception.js';
Expand Down Expand Up @@ -56,6 +59,41 @@ export async function setOpeningHoursExceptions(
});
}

export async function create(
request: LocationCreationRequest,
): Promise<LocationCreatedResponse> {
if (!request || typeof request !== 'object') {
throw new Error('Location creation request invalid or missing.');
}
if (!request.name || typeof request.name !== 'string') {
throw new Error('Cannot create a location without a name.');
}
if (typeof request.latitude !== 'number' || isNaN(request.latitude)) {
throw new Error('Cannot create a location without a valid latitude.');
}
if (typeof request.longitude !== 'number' || isNaN(request.longitude)) {
throw new Error('Cannot create a location without a valid longitude.');
}
if (!request.address || typeof request.address !== 'string') {
throw new Error('Cannot create a location without an address.');
}
if (!request.country || typeof request.country !== 'string') {
throw new Error('Cannot create a location without a country.');
}

const result: LocationCreatedResponse = await ApiBase.request('locations', {
method: 'POST',
body: JSON.stringify(request),
headers: V2_HEADERS,
});

if (!result.id) {
throw new ResponseValidationError('Response does not contain "id"');
}

return result;
}

export async function createInputField(
inputField: InputFieldCreationRequest,
): Promise<void> {
Expand Down
2 changes: 2 additions & 0 deletions packages/javascript-api/src/public-api/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export { LineAppointmentSettings } from '../lib/model/line/line-appointment-sett
export { LineCreationRequest } from '../lib/model/line/line-creation-request.js';
export { LineCreatedResponse } from '../lib/model/line/line-created-response.js';
export { Location } from '../lib/model/location.js';
export { LocationCreationRequest } from '../lib/model/location/location-creation-request.js';
export { LocationCreatedResponse } from '../lib/model/location/location-created-response.js';
export { TicketExtra } from '../lib/model/ticket/ticket-extra.js';
export { TicketLabel } from '../lib/model/ticket/ticket-label.js';
export { Ticket } from '../lib/model/ticket/ticket.js';
Expand Down
Loading