Skip to content

Commit

Permalink
Merge pull request #13 from codecov/dorian/CE-2635/api-abstraction
Browse files Browse the repository at this point in the history
Dorian/ce 2635/api abstraction
  • Loading branch information
dorianamouroux committed Nov 16, 2020
2 parents cfb3b8a + 1d2fc34 commit d0a30de
Show file tree
Hide file tree
Showing 13 changed files with 493 additions and 79 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PROXY_TO=https://stage-api.codecov.dev
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_API_URL=https://stage-api.codecov.dev
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_API_URL=
3 changes: 3 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ site:

ignore:
- "**/*.stories.js"
- ./src/setupTests.js
- ./src/setupProxy.js
- ./src/reportWebVitals.js
377 changes: 305 additions & 72 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.1.2",
"@testing-library/user-event": "^12.2.2",
"js-cookie": "^2.2.1",
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
"qs": "^6.9.4",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
Expand Down Expand Up @@ -70,8 +73,10 @@
"@storybook/preset-create-react-app": "^3.1.4",
"@storybook/react": "^6.0.28",
"eslint-config-prettier": "^6.15.0",
"http-proxy-middleware": "^1.0.6",
"husky": "^4.3.0",
"lint-staged": "^10.5.1",
"msw": "^0.21.3",
"prettier": "^2.1.2",
"react-is": "^17.0.1"
}
Expand Down
15 changes: 13 additions & 2 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
const defaultConfig = {}
import mapKeys from 'lodash/mapKeys'

const defaultConfig = {
API_URL: '',
}

function removeReactAppPrefix(obj) {
// in .env file, the variable must start with REACT_APP_
// to be injected in the application, so we remove that
// prefix to be more convenient for us
return mapKeys(obj, (_, key) => key.replace('REACT_APP_', ''))
}

const config = {
...defaultConfig,
...process.env,
...removeReactAppPrefix(process.env),
...window.configEnv,
}

Expand Down
6 changes: 1 addition & 5 deletions src/pages/Home.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
function Home() {
return (
<div>
Im the home page
</div>
)
return <div>Im the home page</div>
}

export default Home
13 changes: 13 additions & 0 deletions src/setupProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = function (app) {
if (process.env.PROXY_TO) {
app.use(
'/internal',
createProxyMiddleware({
target: process.env.PROXY_TO,
changeOrigin: true,
})
)
}
}
23 changes: 23 additions & 0 deletions src/shared/api/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { camelizeKeys, generatePath, getHeaders } from './helpers'

export async function get({ path, query, provider = 'gh', extraHeaders = {} }) {
const uri = generatePath({ path, query })
const headers = {
...getHeaders(provider),
...extraHeaders,
}

const res = await fetch(uri, { headers })
const data = camelizeKeys(await res.json())

return {
data,
res,
}
}

const Api = {
get,
}

export default Api
78 changes: 78 additions & 0 deletions src/shared/api/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import * as Cookie from 'js-cookie'

import Api from './api'

const rawUserData = {
profile: {
username: 'hello',
nb_orgs: 3,
},
orgs: [
{ id: 1, long_name: 'Codecov' },
{ id: 2, long_name: 'Github' },
],
}

// name in camelcase
const userData = {
profile: {
username: 'hello',
nbOrgs: 3,
},
orgs: [
{ id: 1, longName: 'Codecov' },
{ id: 2, longName: 'Github' },
],
}

const server = setupServer(
rest.get('/internal/test', (req, res, ctx) => {
const hasToken = Boolean(req.headers.map['authorization'])
return res(ctx.status(hasToken ? 200 : 401), ctx.json(rawUserData))
})
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

let result, error
function callApi() {
result = null
error = null
return Api.get({
path: '/test',
})
.then((data) => {
result = data
})
.catch((errorData) => {
error = errorData
})
}

describe('when calling an endpoint without a token', () => {
beforeEach(callApi)

it('has a 401 error', () => {
expect(result.res.status).toBe(401)
})
})

describe('when calling an endpoint with a token', () => {
beforeEach(() => {
Cookie.set('github-token', 'hello')
return callApi()
})

afterEach(() => {
Cookie.remove('github-token')
})

it('has the data and no error', () => {
expect(error).toBeNull()
expect(result.data).toEqual(userData)
})
})
48 changes: 48 additions & 0 deletions src/shared/api/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import camelCase from 'lodash/camelCase'
import qs from 'qs'
import * as Cookie from 'js-cookie'

import config from 'config'

const ProviderCookieKeyMapping = {
gh: 'github-token',
gl: 'gitlab-token',
bb: 'bitbucket-token',
}

export function generatePath({ path, query }) {
const baseUrl = `${config.API_URL}/internal`
const queryString = qs.stringify(query, {})

return `${baseUrl}${path}?${queryString}`
}

export function getHeaders(provider) {
const token = Cookie.get(ProviderCookieKeyMapping[provider])

const authorizationHeader = token
? {
Authorization: `frontend ${token}`,
}
: {}

return {
Accept: 'application/json',
...authorizationHeader,
}
}

export function camelizeKeys(obj) {
if (Array.isArray(obj)) {
return obj.map((v) => camelizeKeys(v))
} else if (obj !== null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[camelCase(key)]: camelizeKeys(obj[key]),
}),
{}
)
}
return obj
}
1 change: 1 addition & 0 deletions src/shared/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './api'

0 comments on commit d0a30de

Please sign in to comment.