-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from angelxmoreno/feat/api-class
Feat/api class
- Loading branch information
Showing
8 changed files
with
327 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import axios from 'axios'; | ||
import MockAdapter from 'axios-mock-adapter'; | ||
|
||
import { TheSneakerDatabaseClient } from './TheSneakerDatabaseClient'; | ||
import { GetSneakersOptions, GetSneakersResponse, SearchOptions, SearchResponse, Sneaker } from './interfaces'; | ||
|
||
describe('TheSneakerDatabaseClient', () => { | ||
let client: TheSneakerDatabaseClient; | ||
let mockAxios: MockAdapter; | ||
const sneakerData: Sneaker = { | ||
id: '5338a798-ac8b-442f-a8b2-71d3a79311a5', | ||
brand: 'Jordan', | ||
colorway: 'White/Black/Dark Mocha', | ||
estimatedMarketValue: 120, | ||
gender: 'youth', | ||
image: { | ||
original: '', | ||
small: '', | ||
thumbnail: '', | ||
}, | ||
links: { | ||
stockX: '', | ||
goat: 'https://goat.com/sneakers/air-jordan-1-retro-low-og-gs-mocha-cz0858-102', | ||
flightClub: 'https://flightclub.com/air-jordan-1-retro-low-og-gs-mocha-cz0858-102', | ||
stadiumGoods: '', | ||
}, | ||
name: "Air Jordan 1 Retro Low OG GS 'Mocha'", | ||
releaseDate: new Date('2024-08-21'), | ||
releaseYear: 2024, | ||
retailPrice: 120, | ||
silhouette: 'Air Jordan 1', | ||
sku: 'CZ0858-102', | ||
story: 'The Air Jordan 1 Retro Low OG GS ‘Mocha’ showcases...', | ||
}; | ||
beforeEach(() => { | ||
mockAxios = new MockAdapter(axios); | ||
client = new TheSneakerDatabaseClient('your-api-key', {}); | ||
}); | ||
|
||
afterEach(() => { | ||
mockAxios.reset(); | ||
}); | ||
it('should handle getSneakers request', async () => { | ||
const options: GetSneakersOptions = { | ||
limit: 5, | ||
name: 'Air Jordan 1 Retro Low OG GS', | ||
brand: 'Jordan', | ||
colorway: 'White/Black/Dark Mocha', | ||
gender: 'youth', | ||
page: 1, | ||
releaseDate: '2024-08-21', | ||
sku: 'CZ0858-102', | ||
releaseYear: '2024', | ||
sort: 'release_date', | ||
silhouette: 'Air Jordan 1', | ||
}; | ||
const responseObj: GetSneakersResponse = { | ||
count: 1, | ||
results: [sneakerData], | ||
}; | ||
mockAxios.onGet('/sneakers', { params: options }).reply(200, responseObj); | ||
const response = await client.getSneakers(options); | ||
|
||
expect(response.error).toBeUndefined(); | ||
expect(response.response).toBeDefined(); | ||
expect(mockAxios.history.get.length).toBe(1); | ||
expect(mockAxios.history.get[0].url).toBe('/sneakers'); | ||
expect(mockAxios.history.get[0].params).toEqual(options); | ||
}); | ||
|
||
it('should handle getSneakers request with an error', async () => { | ||
mockAxios.onGet('/sneakers', { params: { limit: 5 } }).reply(500, 'Internal Server Error'); | ||
const response = await client.getSneakers({ limit: 5 }); | ||
expect(response.response).toBeUndefined(); | ||
expect(response.error).toBeDefined(); | ||
}); | ||
|
||
it('should handle getSneakerById request', async () => { | ||
const sneakerId = '5338a798-ac8b-442f-a8b2-71d3a79311a5'; | ||
const responseObj: Sneaker = sneakerData; | ||
mockAxios.onGet(`/sneakers/${sneakerId}`).reply(200, responseObj); | ||
const response = await client.getSneakerById(sneakerId); | ||
|
||
expect(response.error).toBeUndefined(); | ||
expect(response.response).toBeDefined(); | ||
expect(response.response).toEqual(responseObj); | ||
expect(mockAxios.history.get.length).toBe(1); | ||
expect(mockAxios.history.get[0].url).toBe(`/sneakers/${sneakerId}`); | ||
}); | ||
|
||
it('should handle getSneakerById request with an error', async () => { | ||
const sneakerId = 'nonexistent-id'; | ||
mockAxios.onGet(`/sneakers/${sneakerId}`).reply(404, 'Not Found'); | ||
const response = await client.getSneakerById(sneakerId); | ||
|
||
expect(response.response).toBeUndefined(); | ||
expect(response.error).toBeDefined(); | ||
expect(mockAxios.history.get.length).toBe(1); | ||
expect(mockAxios.history.get[0].url).toBe(`/sneakers/${sneakerId}`); | ||
}); | ||
|
||
it('should handle search request', async () => { | ||
const searchOptions: SearchOptions = { | ||
query: 'Air Jordan 1', | ||
page: 1, | ||
limit: 5, | ||
}; | ||
const responseObj: SearchResponse = { | ||
count: 1, | ||
totalPages: 1, | ||
results: [sneakerData], | ||
}; | ||
mockAxios.onGet('/search', { params: searchOptions }).reply(200, responseObj); | ||
const response = await client.search(searchOptions); | ||
|
||
expect(response.error).toBeUndefined(); | ||
expect(response.response).toBeDefined(); | ||
expect(response.response).toEqual(responseObj); | ||
expect(mockAxios.history.get.length).toBe(1); | ||
expect(mockAxios.history.get[0].url).toBe('/search'); | ||
expect(mockAxios.history.get[0].params).toEqual(searchOptions); | ||
}); | ||
|
||
it('should handle search request with an error', async () => { | ||
const searchOptions: SearchOptions = { | ||
limit: 5, | ||
query: 'Nonexistent Sneaker', | ||
}; | ||
mockAxios.onGet('/search', { params: searchOptions }).reply(404, 'Not Found'); | ||
const response = await client.search(searchOptions); | ||
|
||
expect(response.response).toBeUndefined(); | ||
expect(response.error).toBeDefined(); | ||
expect(mockAxios.history.get.length).toBe(1); | ||
expect(mockAxios.history.get[0].url).toBe('/search'); | ||
expect(mockAxios.history.get[0].params).toEqual(searchOptions); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import axios, { AxiosInstance, CreateAxiosDefaults } from 'axios'; | ||
import { addAxiosDateTransformer, createAxiosDateTransformer } from 'axios-date-transformer'; | ||
|
||
import { | ||
GetSneakersOptions, | ||
GetSneakersResponse, | ||
MethodResponse, | ||
SearchOptions, | ||
SearchResponse, | ||
Sneaker, | ||
} from './interfaces'; | ||
import { handleAxiosError } from './utils'; | ||
|
||
export class TheSneakerDatabaseClient { | ||
protected client: AxiosInstance; | ||
|
||
constructor(rapidApiKey: string, axiosParam: AxiosInstance | CreateAxiosDefaults) { | ||
this.client = this.configureAxiosInstance(rapidApiKey, axiosParam); | ||
} | ||
|
||
protected configureAxiosInstance( | ||
rapidApiKey: string, | ||
axiosParam: AxiosInstance | CreateAxiosDefaults, | ||
): AxiosInstance { | ||
const instance = | ||
axiosParam instanceof axios | ||
? addAxiosDateTransformer(axiosParam as AxiosInstance) | ||
: createAxiosDateTransformer(axiosParam as CreateAxiosDefaults); | ||
|
||
instance.defaults.baseURL = 'https://the-sneaker-database.p.rapidapi.com/'; | ||
instance.defaults.headers.common['X-RapidAPI-Host'] = 'the-sneaker-database.p.rapidapi.com'; | ||
instance.defaults.headers.common['X-RapidAPI-Key'] = rapidApiKey; | ||
instance.defaults.headers.common['Content-Type'] = 'application/json'; | ||
|
||
return instance; | ||
} | ||
|
||
protected async handleRequest<T, K = undefined>(uri: string, params?: K): Promise<MethodResponse<T>> { | ||
try { | ||
const { data } = await this.client.get<T>(uri, { params }); | ||
return { response: data }; | ||
} catch (error) { | ||
return { error: handleAxiosError(error) }; | ||
} | ||
} | ||
|
||
getSneakers(options: GetSneakersOptions): Promise<MethodResponse<GetSneakersResponse>> { | ||
return this.handleRequest<GetSneakersResponse, GetSneakersOptions>('/sneakers', options); | ||
} | ||
|
||
getSneakerById(sneakerId: string): Promise<MethodResponse<Sneaker[]>> { | ||
return this.handleRequest<Sneaker[]>(`/sneakers/${sneakerId}`); | ||
} | ||
|
||
getBrands(): Promise<MethodResponse<string[]>> { | ||
return this.handleRequest<string[]>(`/brands`); | ||
} | ||
|
||
getGenders(): Promise<MethodResponse<string[]>> { | ||
return this.handleRequest<string[]>(`/genders`); | ||
} | ||
|
||
search(options: SearchOptions): Promise<MethodResponse<SearchResponse>> { | ||
return this.handleRequest<SearchResponse, SearchOptions>(`/search`, options); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,10 @@ | ||
console.log('fun'); | ||
export { TheSneakerDatabaseClient } from './TheSneakerDatabaseClient'; | ||
|
||
export { | ||
GetSneakersOptions, | ||
GetSneakersResponse, | ||
MethodResponse, | ||
SearchOptions, | ||
SearchResponse, | ||
Sneaker, | ||
} from './interfaces'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
export interface Image { | ||
original: string; | ||
small: string; | ||
thumbnail: string; | ||
} | ||
|
||
export interface Links { | ||
stockX: string; | ||
goat: string; | ||
flightClub: string; | ||
stadiumGoods: string; | ||
} | ||
|
||
export interface Sneaker { | ||
id: string; | ||
sku: string; | ||
brand: string; | ||
name: string; | ||
colorway: string; | ||
gender: string; | ||
silhouette: string; | ||
retailPrice: number; | ||
releaseDate: Date; | ||
releaseYear: number; | ||
estimatedMarketValue: number; | ||
links: Links; | ||
image: Image; | ||
story: string; | ||
} | ||
|
||
export type MethodResponse<T> = { error?: Error; response?: T }; | ||
|
||
export interface GetSneakersOptions { | ||
limit: number; | ||
gender?: string; | ||
silhouette?: string; | ||
colorway?: string; | ||
releaseYear?: string; | ||
page?: number; | ||
releaseDate?: Date | string; | ||
sku?: string; | ||
sort?: string; | ||
name?: string; | ||
brand?: string; | ||
} | ||
|
||
export interface GetSneakersResponse { | ||
count: number; | ||
results: Sneaker[]; | ||
} | ||
|
||
export interface SearchOptions { | ||
limit: number; | ||
page?: number; | ||
query?: string; | ||
} | ||
|
||
export interface SearchResponse { | ||
count: number; | ||
totalPages: number; | ||
results: Sneaker[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import axios from 'axios'; | ||
|
||
export const handleAxiosError = (error: any) => { | ||
if (axios.isAxiosError(error)) { | ||
// If the error is an AxiosError, return the response data | ||
return error.response?.data as Error; | ||
} else { | ||
// If the error is not an AxiosError, return the error as is | ||
return error as Error; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters