Skip to content

Commit

Permalink
refactor(site): use axios in non-swr endpoints (#460)
Browse files Browse the repository at this point in the history
Summary:

Applies axios to login, logout and getApiKey

Impact:

POC of axios (#453) and testing axios

Additional details:

* add test:watch script

resolves: #453
  • Loading branch information
G r e y committed Mar 16, 2022
1 parent 95160d0 commit 810b2d2
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 37 deletions.
4 changes: 3 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
"storybook": "start-storybook -p 6006 -s ./static",
"storybook:build": "build-storybook",
"test": "jest --selectProjects test",
"test:coverage": "jest --selectProjects test --collectCoverage"
"test:coverage": "jest --selectProjects test --collectCoverage",
"test:watch": "jest --selectProjects test --watch"
},
"dependencies": {
"@material-ui/core": "4.9.4",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "4.0.0-alpha.42",
"axios": "0.26.1",
"formik": "2.2.9",
"history": "5.3.0",
"react": "17.0.2",
Expand Down
124 changes: 124 additions & 0 deletions site/src/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import axios from "axios"
import { APIKeyResponse, getApiKey, login, LoginResponse, logout } from "./api"

// Mock the axios module so that no real network requests are made, but rather
// we swap in a resolved or rejected value
//
// See: https://jestjs.io/docs/mock-functions#mocking-modules
jest.mock("axios")

describe("api.ts", () => {
describe("login", () => {
it("should return LoginResponse", async () => {
// given
const loginResponse: LoginResponse = {
session_token: "abc_123_test",
}
const axiosMockPost = jest.fn().mockImplementationOnce(() => {
return Promise.resolve({ data: loginResponse })
})
axios.post = axiosMockPost

// when
const result = await login("test", "123")

// then
expect(axiosMockPost).toHaveBeenCalled()
expect(result).toStrictEqual(loginResponse)
})

it("should throw an error on 401", async () => {
// given
// ..ensure that we await our expect assertion in async/await test
expect.assertions(1)
const expectedError = {
message: "Validation failed",
errors: [{ field: "email", code: "email" }],
}
const axiosMockPost = jest.fn().mockImplementationOnce(() => {
return Promise.reject(expectedError)
})
axios.post = axiosMockPost

try {
await login("test", "123")
} catch (error) {
expect(error).toStrictEqual(expectedError)
}
})
})

describe("logout", () => {
it("should return without erroring", async () => {
// given
const axiosMockPost = jest.fn().mockImplementationOnce(() => {
return Promise.resolve()
})
axios.post = axiosMockPost

// when
await logout()

// then
expect(axiosMockPost).toHaveBeenCalled()
})

it("should throw an error on 500", async () => {
// given
// ..ensure that we await our expect assertion in async/await test
expect.assertions(1)
const expectedError = {
message: "Failed to logout.",
}
const axiosMockPost = jest.fn().mockImplementationOnce(() => {
return Promise.reject(expectedError)
})
axios.post = axiosMockPost

try {
await logout()
} catch (error) {
expect(error).toStrictEqual(expectedError)
}
})
})

describe("getApiKey", () => {
it("should return APIKeyResponse", async () => {
// given
const apiKeyResponse: APIKeyResponse = {
key: "abc_123_test",
}
const axiosMockPost = jest.fn().mockImplementationOnce(() => {
return Promise.resolve({ data: apiKeyResponse })
})
axios.post = axiosMockPost

// when
const result = await getApiKey()

// then
expect(axiosMockPost).toHaveBeenCalled()
expect(result).toStrictEqual(apiKeyResponse)
})

it("should throw an error on 401", async () => {
// given
// ..ensure that we await our expect assertion in async/await test
expect.assertions(1)
const expectedError = {
message: "No Cookie!",
}
const axiosMockPost = jest.fn().mockImplementationOnce(() => {
return Promise.reject(expectedError)
})
axios.post = axiosMockPost

try {
await getApiKey()
} catch (error) {
expect(error).toStrictEqual(expectedError)
}
})
})
})
57 changes: 21 additions & 36 deletions site/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios, { AxiosRequestHeaders } from "axios"
import { mutate } from "swr"

interface LoginResponse {
session_token: string
const CONTENT_TYPE_JSON: AxiosRequestHeaders = {
"Content-Type": "application/json",
}

/**
Expand Down Expand Up @@ -107,48 +108,32 @@ export namespace Workspace {
}
}

export interface LoginResponse {
session_token: string
}

export const login = async (email: string, password: string): Promise<LoginResponse> => {
const response = await fetch("/api/v2/users/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
const payload = JSON.stringify({
email,
password,
})

const body = await response.json()
if (!response.ok) {
throw new Error(body.message)
}
const response = await axios.post<LoginResponse>("/api/v2/users/login", payload, {
headers: { ...CONTENT_TYPE_JSON },
})

return body
return response.data
}

export const logout = async (): Promise<void> => {
const response = await fetch("/api/v2/users/logout", {
method: "POST",
})

if (!response.ok) {
const body = await response.json()
throw new Error(body.message)
}

return
await axios.post("/api/v2/users/logout")
}

export const getApiKey = async (): Promise<{ key: string }> => {
const response = await fetch("/api/v2/users/me/keys", {
method: "POST",
})

if (!response.ok) {
const body = await response.json()
throw new Error(body.message)
}
export interface APIKeyResponse {
key: string
}

return await response.json()
export const getApiKey = async (): Promise<APIKeyResponse> => {
const response = await axios.post<APIKeyResponse>("/api/v2/users/me/keys")
return response.data
}
12 changes: 12 additions & 0 deletions site/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4085,6 +4085,13 @@ axe-core@^4.3.5:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.0.tgz#f93be7f81017eb8bedeb1859cc8092cc918d2dc8"
integrity sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA==

axios@0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"

axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
Expand Down Expand Up @@ -6773,6 +6780,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==

follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==

for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
Expand Down

0 comments on commit 810b2d2

Please sign in to comment.