Skip to content

Commit

Permalink
feat(stays): add Stays API (#731)
Browse files Browse the repository at this point in the history
* feat(stays): add types

* feat(stays): add stays mocks

* feat(stays): add stays client to host a search method

/stays/search is one of the endpoints that aren't resource-oriented. To work around this, we need a base resource to host this, hence the introduction of the base resource here.

* feat(stays): add the rest of Stays API

* feat(stays): move all stays resources under stays

* feat(stays): rename types file to StaysTypes

* feat(stays): update import paths

* ci(test): test run in band
  • Loading branch information
go1t authored May 18, 2023
1 parent c56de88 commit d6b3a00
Show file tree
Hide file tree
Showing 16 changed files with 974 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"start": "node ./dist/index.js",
"dev": "ts-node ./src/index.ts",
"lint": "eslint . --ext .ts,.tsx",
"test": "jest",
"test": "jest --runInBand",
"pre-commit-check": "yarn lint-staged && yarn test && yarn build:test",
"prepare": "husky install",
"example": "ts-node ./examples/example.ts",
Expand Down
52 changes: 52 additions & 0 deletions src/Stays/Bookings/Bookings.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import nock from 'nock'
import { Duffel } from '../../index'
import { MOCK_BOOKING } from '../mocks'
import { StaysBookingPayload } from './Bookings'

const duffel = new Duffel({ token: 'mockToken' })
describe('Stays/Bookings', () => {
afterEach(() => {
nock.cleanAll()
})

it('should post to /stays/bookings when `create` is called', async () => {
const mockResponse = { data: MOCK_BOOKING }
const mockBookingParams: StaysBookingPayload = {
quote_id: 'quo_123',
guests: [
{
given_name: 'John',
family_name: 'Smith',
born_on: '1980-01-01',
},
],
email: '[email protected]',
phone_number: '+447700900000',
}

nock(/(.*)/)
.post('/stays/bookings', (body) => {
expect(body.data).toEqual(mockBookingParams)
return true
})
.reply(200, mockResponse)
const response = await duffel.stays.bookings.create(mockBookingParams)
expect(response.data).toEqual(mockResponse.data)
})

it('should get to /stays/bookings when `list` is called', async () => {
const mockResponse = { data: [MOCK_BOOKING] }

nock(/(.*)/).get('/stays/bookings').reply(200, mockResponse)
const response = await duffel.stays.bookings.list()
expect(response.data).toEqual(mockResponse.data)
})

it('should get to /stays/bookings/{id} when `get` is called', async () => {
const mockResponse = { data: MOCK_BOOKING }

nock(/(.*)/).get('/stays/bookings/bok_123').reply(200, mockResponse)
const response = await duffel.stays.bookings.get('bok_123')
expect(response.data).toEqual(mockResponse.data)
})
})
62 changes: 62 additions & 0 deletions src/Stays/Bookings/Bookings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Client } from '../../Client'
import { StaysBooking } from '../StaysTypes'
import { Resource } from '../../Resource'
import { DuffelResponse } from '../../types'

export interface StaysBookingPayload {
quote_id: string
guests: Array<{
given_name: string
family_name: string
born_on: string
}>
email: string
phone_number: string
accommodation_special_requests?: string
}

export class Bookings extends Resource {
/**
* Endpoint path
*/
path: string

constructor(client: Client) {
super(client)
this.path = 'stays/bookings'
}

/**
* Create a booking
* @param {object} payload - The booking payload, including quote id and guest information
*/
public create = async (
payload: StaysBookingPayload
): Promise<DuffelResponse<StaysBooking>> =>
this.request({
method: 'POST',
path: this.path,
data: payload,
})

/**
* Get a booking
* @param {string} bookingId - The ID of the booking
*/
public get = async (
bookingId: string
): Promise<DuffelResponse<StaysBooking>> =>
this.request({
method: 'GET',
path: `${this.path}/${bookingId}`,
})

/**
* list bookings
*/
public list = async (): Promise<DuffelResponse<StaysBooking>> =>
this.request({
method: 'GET',
path: this.path,
})
}
1 change: 1 addition & 0 deletions src/Stays/Bookings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Bookings'
24 changes: 24 additions & 0 deletions src/Stays/Quotes/Quotes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import nock from 'nock'
import { Duffel } from '../../index'
import { MOCK_QUOTE } from '../mocks'

const duffel = new Duffel({ token: 'mockToken' })
describe('Stays/Quotes', () => {
afterEach(() => {
nock.cleanAll()
})

it('should post to /stays/quotes when `create` is called', async () => {
const rateId = 'rat_123'
const mockResponse = { data: MOCK_QUOTE }

nock(/(.*)/)
.post('/stays/quotes', (body) => {
expect(body.data.rate_id).toEqual(rateId)
return true
})
.reply(200, mockResponse)
const response = await duffel.stays.quotes.create(rateId)
expect(response.data).toEqual(mockResponse.data)
})
})
29 changes: 29 additions & 0 deletions src/Stays/Quotes/Quotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Client } from '../../Client'
import { StaysQuote } from '../StaysTypes'
import { Resource } from '../../Resource'
import { DuffelResponse } from '../../types'

export class Quotes extends Resource {
/**
* Endpoint path
*/
path: string

constructor(client: Client) {
super(client)
this.path = 'stays/quotes'
}

/**
* Create a quote for the selected rate
* @param {string} rateId - The ID of the rate to create a quote for
*/
public create = async (rateId: string): Promise<DuffelResponse<StaysQuote>> =>
this.request({
method: 'POST',
path: this.path,
data: {
rate_id: rateId,
},
})
}
1 change: 1 addition & 0 deletions src/Stays/Quotes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Quotes'
19 changes: 19 additions & 0 deletions src/Stays/SearchResults/SearchResults.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import nock from 'nock'
import { Duffel } from '../../index'
import { MOCK_SEARCH_RESULT } from '../mocks'

const duffel = new Duffel({ token: 'mockToken' })
describe('Stays/SearchResults', () => {
afterEach(() => {
nock.cleanAll()
})

it('should post to /stays/search_results/{id}/actions/fetch_all_rates when `fetchAllRates` is called', async () => {
nock(/(.*)/)
.post('/stays/search_results/rat_123/actions/fetch_all_rates')
.reply(200, { data: MOCK_SEARCH_RESULT })

const response = await duffel.stays.searchResults.fetchAllRates('rat_123')
expect(response.data).toEqual(MOCK_SEARCH_RESULT)
})
})
28 changes: 28 additions & 0 deletions src/Stays/SearchResults/SearchResults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Client } from '../../Client'
import { StaysSearchResult } from '../StaysTypes'
import { Resource } from '../../Resource'
import { DuffelResponse } from '../../types'

export class SearchResults extends Resource {
/**
* Endpoint path
*/
path: string

constructor(client: Client) {
super(client)
this.path = 'stays/search_results'
}

/**
* Fetch all rates for the given search result
* @param {string} searchResultId - The ID of the search result to fetch rates for
*/
public fetchAllRates = async (
searchResultId: string
): Promise<DuffelResponse<StaysSearchResult>> =>
this.request({
method: 'POST',
path: `${this.path}/${searchResultId}/actions/fetch_all_rates`,
})
}
1 change: 1 addition & 0 deletions src/Stays/SearchResults/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SearchResults'
36 changes: 36 additions & 0 deletions src/Stays/Stays.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import nock from 'nock'
import { Duffel } from '../index'
import { MOCK_SEARCH_RESULT } from './mocks'

const duffel = new Duffel({ token: 'mockToken' })
describe('Stays', () => {
afterEach(() => {
nock.cleanAll()
})

it('should post to /stays/search when `search` is called', async () => {
const mockResponse = { data: { results: [MOCK_SEARCH_RESULT] } }
const mockSearchParams = {
location: {
radius: 5,
geographic_coordinates: {
latitude: 40.73061,
longitude: -73.935242,
},
},
check_in_date: '2023-10-20',
check_out_date: '2023-10-24',
adults: 2,
rooms: 1,
}

nock(/(.*)/)
.post('/stays/search', (body) => {
expect(body.data).toEqual(mockSearchParams)
return true
})
.reply(200, mockResponse)
const response = await duffel.stays.search(mockSearchParams)
expect(response.data).toEqual(mockResponse.data)
})
})
36 changes: 36 additions & 0 deletions src/Stays/Stays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Client } from '../Client'
import { StaysSearchParams, StaysSearchResult } from './StaysTypes'
import { Resource } from '../Resource'
import { DuffelResponse } from '../types'
import { Bookings } from './Bookings'
import { Quotes } from './Quotes'
import { SearchResults } from './SearchResults'

export class Stays extends Resource {
/**
* Endpoint path
*/
path: string

public searchResults: SearchResults
public quotes: Quotes
public bookings: Bookings

constructor(client: Client) {
super(client)
this.path = 'stays'

this.searchResults = new SearchResults(client)
this.quotes = new Quotes(client)
this.bookings = new Bookings(client)
}

/**
* Search for accommodations
* @param {object} params - The search parameters
*/
public search = async (
params: StaysSearchParams
): Promise<DuffelResponse<StaysSearchResult>> =>
this.request({ method: 'POST', path: `${this.path}/search`, data: params })
}
Loading

0 comments on commit d6b3a00

Please sign in to comment.